diff --git a/gulp/babel.js b/gulp/babel.js
index 3c33996..3ef77d3 100644
--- a/gulp/babel.js
+++ b/gulp/babel.js
@@ -12,10 +12,18 @@ const SRC_DIR = path.join(PROJECT_DIR, 'js');
const STATIC_SUFFIX = 'dist/js/';
const CORE_DIR = path.join(PROJECT_DIR, 'news', 'core', 'static', 'core');
+const COLLECTION_DIR = path.join(
+ PROJECT_DIR,
+ 'news',
+ 'collection',
+ 'static',
+ 'collection'
+);
const taskMappings = [
{ name: 'homepage', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
+ { name: 'rules', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
];
const babelTask = done => {
diff --git a/gulp/sass.js b/gulp/sass.js
index 2a71455..46cd291 100644
--- a/gulp/sass.js
+++ b/gulp/sass.js
@@ -10,6 +10,13 @@ const STATIC_SUFFIX = 'dist/css/';
export const ACCOUNTS_DIR = path.join(PROJECT_DIR, 'accounts', 'static', 'accounts');
export const CORE_DIR = path.join(PROJECT_DIR, 'news', 'core', 'static', 'core');
+export const COLLECTION_DIR = path.join(
+ PROJECT_DIR,
+ 'news',
+ 'collection',
+ 'static',
+ 'collection'
+);
const taskMappings = [
{ name: 'login', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
@@ -19,6 +26,8 @@ const taskMappings = [
{ name: 'homepage', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'category', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
+ { name: 'rules', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
+ { name: 'rule', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
];
export const sassTask = done => {
diff --git a/src/newsreader/js/components/Messages.js b/src/newsreader/js/components/Messages.js
index fe8cb20..a985381 100644
--- a/src/newsreader/js/components/Messages.js
+++ b/src/newsreader/js/components/Messages.js
@@ -1,9 +1,11 @@
import React from 'react';
const Messages = props => {
- const messages = props.messages.map(message => {
+ const messages = props.messages.map((index, message) => {
return (
-
{message.text}
+
+ {message.text}
+
);
});
diff --git a/src/newsreader/js/pages/categories/components/CategoryCard.js b/src/newsreader/js/pages/categories/components/CategoryCard.js
index 04fccef..5c58a51 100644
--- a/src/newsreader/js/pages/categories/components/CategoryCard.js
+++ b/src/newsreader/js/pages/categories/components/CategoryCard.js
@@ -19,9 +19,7 @@ const CategoryCard = props => {
const cardHeader = (
<>
{category.name}
-
- {category.created}
-
+ {category.created}
>
);
const cardContent = <>{category.rules && }>;
diff --git a/src/newsreader/js/pages/rules/App.js b/src/newsreader/js/pages/rules/App.js
new file mode 100644
index 0000000..fd01229
--- /dev/null
+++ b/src/newsreader/js/pages/rules/App.js
@@ -0,0 +1,100 @@
+import React from 'react';
+
+import Cookies from 'js-cookie';
+
+import Card from '../../components/Card.js';
+import RuleCard from './components/RuleCard.js';
+import RuleModal from './components/RuleModal.js';
+import Messages from '../../components/Messages.js';
+
+class App extends React.Component {
+ selectRule = ::this.selectRule;
+ deselectRule = ::this.deselectRule;
+ deleteRule = ::this.deleteRule;
+
+ constructor(props) {
+ super(props);
+
+ this.token = Cookies.get('csrftoken');
+ this.state = {
+ rules: props.rules,
+ selectedRuleId: null,
+ message: null,
+ };
+ }
+
+ selectRule(ruleId) {
+ this.setState({ selectedRuleId: ruleId });
+ }
+
+ deselectRule() {
+ this.setState({ selectedRuleId: null });
+ }
+
+ deleteRule(ruleId) {
+ const url = `/api/rules/${ruleId}/`;
+ const options = {
+ method: 'DELETE',
+ headers: {
+ 'X-CSRFToken': this.token,
+ },
+ };
+
+ fetch(url, options).then(response => {
+ if (response.ok) {
+ const rules = this.state.rules.filter(rule => {
+ return rule.pk != ruleId;
+ });
+
+ return this.setState({
+ rules: rules,
+ selectedRuleId: null,
+ message: null,
+ });
+ }
+ });
+
+ const message = {
+ type: 'error',
+ text: 'Unable to remove rule, try again later',
+ };
+ return this.setState({ selectedRuleId: null, message: message });
+ }
+
+ render() {
+ const { rules } = this.state;
+ const cards = rules.map(rule => {
+ return ;
+ });
+
+ const selectedRule = rules.find(rule => {
+ return rule.pk === this.state.selectedRuleId;
+ });
+
+ const pageHeader = (
+ <>
+ Rules
+
+ Create rule
+
+ >
+ );
+
+ return (
+ <>
+ {this.state.message && }
+
+ {cards}
+ {selectedRule && (
+
+ )}
+ >
+ );
+ }
+}
+
+export default App;
diff --git a/src/newsreader/js/pages/rules/components/RuleCard.js b/src/newsreader/js/pages/rules/components/RuleCard.js
new file mode 100644
index 0000000..c94144e
--- /dev/null
+++ b/src/newsreader/js/pages/rules/components/RuleCard.js
@@ -0,0 +1,62 @@
+import React from 'react';
+
+import Card from '../../../components/Card.js';
+
+const RuleCard = props => {
+ const { rule } = props;
+
+ const faviconUrl = rule.favicon ? rule.favicon : '/static/picture.svg';
+ const stateIcon = rule.succeeded
+ ? '/static/checkmark-circle.svg'
+ : '/static/warning.svg';
+
+ const cardHeader = (
+ <>
+
+

+
{rule.name}
+
+
+ >
+ );
+
+ const cardContent = (
+ <>
+
+ - {rule.category}
+ -
+
+ {rule.url}
+
+
+ - {rule.created}
+ - {rule.timezone}
+
+ {!rule.succeeded && (
+
+ )}
+ >
+ );
+
+ const cardFooter = (
+ <>
+
+ Edit
+
+
+ >
+ );
+
+ return ;
+};
+
+export default RuleCard;
diff --git a/src/newsreader/js/pages/rules/components/RuleModal.js b/src/newsreader/js/pages/rules/components/RuleModal.js
new file mode 100644
index 0000000..5d6f09b
--- /dev/null
+++ b/src/newsreader/js/pages/rules/components/RuleModal.js
@@ -0,0 +1,33 @@
+import React from 'react';
+
+import Modal from '../../../components/Modal.js';
+
+const RuleModal = props => {
+ const content = (
+
+
+
Delete rule
+
+
+
+
Are you sure you want to delete {props.rule.name}?
+
+
+
+
+
+
+
+ );
+
+ return ;
+};
+
+export default RuleModal;
diff --git a/src/newsreader/js/pages/rules/index.js b/src/newsreader/js/pages/rules/index.js
new file mode 100644
index 0000000..7fc7a5c
--- /dev/null
+++ b/src/newsreader/js/pages/rules/index.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import App from './App.js';
+
+const dataScript = document.getElementById('rules-data');
+const rules = JSON.parse(dataScript.textContent);
+
+ReactDOM.render(, document.getElementsByClassName('content')[0]);
diff --git a/src/newsreader/news/collection/forms.py b/src/newsreader/news/collection/forms.py
new file mode 100644
index 0000000..89c72e9
--- /dev/null
+++ b/src/newsreader/news/collection/forms.py
@@ -0,0 +1,30 @@
+from django import forms
+
+from newsreader.news.collection.models import CollectionRule
+from newsreader.news.core.models import Category
+
+
+class CollectionRuleForm(forms.ModelForm):
+ category = forms.ModelChoiceField(required=False, queryset=Category.objects.all())
+
+ def __init__(self, *args, **kwargs) -> None:
+ self.user = kwargs.pop("user")
+
+ super().__init__(*args, **kwargs)
+
+ if self.user:
+ self.fields["category"].queryset = Category.objects.filter(user=self.user)
+
+ def save(self, commit=True) -> CollectionRule:
+ instance = super().save(commit=False)
+ instance.user = self.user
+
+ if commit:
+ instance.save()
+ self.save_m2m()
+
+ return instance
+
+ class Meta:
+ model = CollectionRule
+ fields = ("name", "url", "timezone", "favicon", "category")
diff --git a/src/newsreader/news/collection/templates/collection/rule-create.html b/src/newsreader/news/collection/templates/collection/rule-create.html
new file mode 100644
index 0000000..4bb242a
--- /dev/null
+++ b/src/newsreader/news/collection/templates/collection/rule-create.html
@@ -0,0 +1,31 @@
+{% extends "collection/rule.html" %}
+
+{% block form-header %}
+
+{% endblock %}
+
+{% block name-input %}
+
+{% endblock %}
+
+{% block category-input %}
+
+{% endblock %}
+
+{% block url-input %}
+
+{% endblock %}
+
+{% block favicon-input %}
+
+{% endblock %}
+
+{% block timezone-input %}
+
+{% endblock %}
+
+{% block confirm-button %}
+
+{% endblock %}
diff --git a/src/newsreader/news/collection/templates/collection/rule-update.html b/src/newsreader/news/collection/templates/collection/rule-update.html
new file mode 100644
index 0000000..7c0a4ba
--- /dev/null
+++ b/src/newsreader/news/collection/templates/collection/rule-update.html
@@ -0,0 +1,34 @@
+{% extends "collection/rule.html" %}
+
+{% block form-header %}
+
+{% endblock %}
+
+{% block name-input %}
+
+{% endblock %}
+
+{% block category-input %}
+
+{% endblock %}
+
+{% block url-input %}
+
+{% endblock %}
+
+{% block favicon-input %}
+
+{% endblock %}
+
+{% block timezone-input %}
+
+{% endblock %}
+
+{% block confirm-button %}
+
+{% endblock %}
diff --git a/src/newsreader/news/collection/templates/collection/rule.html b/src/newsreader/news/collection/templates/collection/rule.html
new file mode 100644
index 0000000..0d67e08
--- /dev/null
+++ b/src/newsreader/news/collection/templates/collection/rule.html
@@ -0,0 +1,70 @@
+{% extends "base.html" %}
+
+{% load static %}
+
+{% block head %}
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/src/newsreader/news/collection/templates/collection/rules.html b/src/newsreader/news/collection/templates/collection/rules.html
new file mode 100644
index 0000000..844e27b
--- /dev/null
+++ b/src/newsreader/news/collection/templates/collection/rules.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+
+{% load static %}
+
+{% block head %}
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
+
+{% block scripts %}
+
+
+{% endblock %}
diff --git a/src/newsreader/news/collection/tests/test_views.py b/src/newsreader/news/collection/tests/test_views.py
new file mode 100644
index 0000000..d6a478e
--- /dev/null
+++ b/src/newsreader/news/collection/tests/test_views.py
@@ -0,0 +1,148 @@
+from django.test import Client, TestCase
+from django.urls import reverse
+
+import pytz
+
+from newsreader.accounts.tests.factories import UserFactory
+from newsreader.news.collection.models import CollectionRule
+from newsreader.news.collection.tests.factories import CollectionRuleFactory
+from newsreader.news.core.tests.factories import CategoryFactory
+
+
+class CollectionRuleViewTestCase:
+ def setUp(self):
+ self.user = UserFactory(password="test")
+ self.client.login(email=self.user.email, password="test")
+
+ self.category = CategoryFactory(user=self.user)
+ self.form_data = {"name": "", "category": "", "url": "", "timezone": ""}
+
+ def test_simple(self):
+ response = self.client.get(self.url)
+
+ self.assertEquals(response.status_code, 200)
+
+ def test_no_category(self):
+ self.form_data.update(category="")
+
+ response = self.client.post(self.url, self.form_data)
+ self.assertEquals(response.status_code, 302)
+
+ rule = CollectionRule.objects.get()
+
+ self.assertEquals(rule.category, None)
+
+ def test_categories_only_from_user(self):
+ other_user = UserFactory()
+ other_categories = CategoryFactory.create_batch(size=4, user=other_user)
+
+ response = self.client.get(self.url)
+
+ for category in other_categories:
+ self.assertNotContains(response, category.name)
+
+ def test_category_of_other_user(self):
+ other_user = UserFactory()
+ other_rule = CollectionRuleFactory(name="other rule", user=other_user)
+
+ self.form_data.update(
+ name="new name",
+ category=other_rule.category,
+ url=other_rule.url,
+ timezone=other_rule.timezone,
+ )
+
+ other_url = reverse("rule-update", args=[other_rule.pk])
+ response = self.client.post(other_url, self.form_data)
+
+ self.assertEquals(response.status_code, 404)
+
+ other_rule.refresh_from_db()
+
+ self.assertEquals(other_rule.name, "other rule")
+
+ def test_with_other_user_rules(self):
+ other_user = UserFactory()
+ other_categories = CategoryFactory.create_batch(size=4, user=other_user)
+
+ self.form_data.update(category=other_categories[2].pk)
+
+ response = self.client.post(self.url, self.form_data)
+
+ self.assertContains(response, "not one of the available choices")
+
+
+class CollectionRuleCreateViewTestCase(CollectionRuleViewTestCase, TestCase):
+ def setUp(self):
+ super().setUp()
+
+ self.url = reverse("rule-create")
+
+ self.form_data.update(
+ name="new rule",
+ url="https://www.rss.com/rss",
+ timezone=pytz.utc,
+ category=str(self.category.pk),
+ )
+
+ def test_creation(self):
+ response = self.client.post(self.url, self.form_data)
+
+ self.assertEquals(response.status_code, 302)
+
+ rule = CollectionRule.objects.get(name="new rule")
+
+ self.assertEquals(rule.url, "https://www.rss.com/rss")
+ self.assertEquals(rule.timezone, str(pytz.utc))
+ self.assertEquals(rule.favicon, None)
+ self.assertEquals(rule.category.pk, self.category.pk)
+ self.assertEquals(rule.user.pk, self.user.pk)
+
+
+class CollectionRuleUpdateViewTestCase(CollectionRuleViewTestCase, TestCase):
+ def setUp(self):
+ super().setUp()
+
+ self.rule = CollectionRuleFactory(
+ name="collection rule", user=self.user, category=self.category
+ )
+ self.url = reverse("rule-update", args=[self.rule.pk])
+
+ self.form_data.update(
+ name=self.rule.name,
+ category=self.rule.category.pk,
+ url=self.rule.url,
+ timezone=self.rule.timezone,
+ )
+
+ def test_name_change(self):
+ self.form_data.update(name="new name")
+
+ response = self.client.post(self.url, self.form_data)
+ self.assertEquals(response.status_code, 302)
+
+ self.rule.refresh_from_db()
+
+ self.assertEquals(self.rule.name, "new name")
+
+ def test_category_change(self):
+ new_category = CategoryFactory(user=self.user)
+
+ self.form_data.update(category=new_category.pk)
+
+ response = self.client.post(self.url, self.form_data)
+ self.assertEquals(response.status_code, 302)
+
+ self.rule.refresh_from_db()
+
+ self.assertEquals(self.rule.category.pk, new_category.pk)
+
+ def test_category_removal(self):
+ self.form_data.update(category="")
+
+ response = self.client.post(self.url, self.form_data)
+ self.assertEquals(response.status_code, 302)
+
+ self.rule.refresh_from_db()
+
+ self.assertEquals(self.rule.category, None)
diff --git a/src/newsreader/news/collection/urls.py b/src/newsreader/news/collection/urls.py
index 606ec3a..16c80b7 100644
--- a/src/newsreader/news/collection/urls.py
+++ b/src/newsreader/news/collection/urls.py
@@ -1,3 +1,4 @@
+from django.contrib.auth.decorators import login_required
from django.urls import path
from newsreader.news.collection.endpoints import (
@@ -6,11 +7,30 @@ from newsreader.news.collection.endpoints import (
NestedRuleView,
RuleReadView,
)
+from newsreader.news.collection.views import (
+ CollectionRuleCreateView,
+ CollectionRuleListView,
+ CollectionRuleUpdateView,
+)
endpoints = [
- path("rules/", DetailRuleView.as_view(), name="rules-detail"),
+ path("rules//", DetailRuleView.as_view(), name="rules-detail"),
path("rules//posts/", NestedRuleView.as_view(), name="rules-nested-posts"),
path("rules//read/", RuleReadView.as_view(), name="rules-read"),
path("rules/", ListRuleView.as_view(), name="rules-list"),
]
+
+urlpatterns = [
+ path("rules/", login_required(CollectionRuleListView.as_view()), name="rules"),
+ path(
+ "rules//",
+ login_required(CollectionRuleUpdateView.as_view()),
+ name="rule-update",
+ ),
+ path(
+ "rules/create/",
+ login_required(CollectionRuleCreateView.as_view()),
+ name="rule-create",
+ ),
+]
diff --git a/src/newsreader/news/collection/views.py b/src/newsreader/news/collection/views.py
index e69de29..8919ddf 100644
--- a/src/newsreader/news/collection/views.py
+++ b/src/newsreader/news/collection/views.py
@@ -0,0 +1,58 @@
+from typing import Dict, Iterable
+
+from django.urls import reverse_lazy
+from django.views.generic.edit import CreateView, UpdateView
+from django.views.generic.list import ListView
+
+import pytz
+
+from newsreader.news.collection.forms import CollectionRuleForm
+from newsreader.news.collection.models import CollectionRule
+from newsreader.news.core.models import Category
+
+
+class CollectionRuleViewMixin:
+ queryset = CollectionRule.objects.order_by("name")
+
+ def get_queryset(self) -> Iterable:
+ user = self.request.user
+ return self.queryset.filter(user=user)
+
+
+class CollectionRuleDetailMixin:
+ success_url = reverse_lazy("rules")
+ form_class = CollectionRuleForm
+
+ def get_context_data(self, **kwargs) -> Dict:
+ context_data = super().get_context_data(**kwargs)
+
+ rules = Category.objects.filter(user=self.request.user).order_by("name")
+ timezones = [timezone for timezone in pytz.all_timezones]
+
+ context_data["categories"] = rules
+ context_data["timezones"] = timezones
+
+ return context_data
+
+ def get_form_kwargs(self) -> Dict:
+ kwargs = super().get_form_kwargs()
+ kwargs["user"] = self.request.user
+ return kwargs
+
+
+class CollectionRuleListView(CollectionRuleViewMixin, ListView):
+ template_name = "collection/rules.html"
+ context_object_name = "rules"
+
+
+class CollectionRuleUpdateView(
+ CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView
+):
+ template_name = "collection/rule-update.html"
+ context_object_name = "rule"
+
+
+class CollectionRuleCreateView(
+ CollectionRuleViewMixin, CollectionRuleDetailMixin, CreateView
+):
+ template_name = "collection/rule-create.html"
diff --git a/src/newsreader/news/core/forms.py b/src/newsreader/news/core/forms.py
index 38e03f4..8fa2221 100644
--- a/src/newsreader/news/core/forms.py
+++ b/src/newsreader/news/core/forms.py
@@ -6,8 +6,8 @@ from newsreader.news.core.models import Category
class CategoryForm(forms.ModelForm):
rules = forms.ModelMultipleChoiceField(
- queryset=CollectionRule.objects.all(),
required=False,
+ queryset=CollectionRule.objects.all(),
widget=forms.widgets.CheckboxSelectMultiple,
)
@@ -17,7 +17,9 @@ class CategoryForm(forms.ModelForm):
super().__init__(*args, **kwargs)
if self.user:
- self.fields["rules"].queryset = CollectionRule.objects.filter(user=self.user)
+ self.fields["rules"].queryset = CollectionRule.objects.filter(
+ user=self.user
+ )
def save(self, commit=True) -> Category:
instance = super().save(commit=False)
diff --git a/src/newsreader/scss/components/card/_card.scss b/src/newsreader/scss/components/card/_card.scss
index d979eeb..23a8079 100644
--- a/src/newsreader/scss/components/card/_card.scss
+++ b/src/newsreader/scss/components/card/_card.scss
@@ -13,6 +13,7 @@
&__header {
display: flex;
justify-content: space-between;
+ align-items: center;
padding: 15px 0;
diff --git a/src/newsreader/scss/components/errorlist/; b/src/newsreader/scss/components/errorlist/;
deleted file mode 100644
index efcee40..0000000
--- a/src/newsreader/scss/components/errorlist/;
+++ /dev/null
@@ -1,3 +0,0 @@
-.errorlist {
-
-}
diff --git a/src/newsreader/scss/pages/rule/components/index.scss b/src/newsreader/scss/pages/rule/components/index.scss
new file mode 100644
index 0000000..de2a031
--- /dev/null
+++ b/src/newsreader/scss/pages/rule/components/index.scss
@@ -0,0 +1 @@
+@import "rule-form/index";
diff --git a/src/newsreader/scss/pages/rule/components/rule-form/_rule-form.scss b/src/newsreader/scss/pages/rule/components/rule-form/_rule-form.scss
new file mode 100644
index 0000000..95a2388
--- /dev/null
+++ b/src/newsreader/scss/pages/rule/components/rule-form/_rule-form.scss
@@ -0,0 +1,27 @@
+.rule-form {
+ margin: 20px 0;
+
+ &__section:last-child {
+
+ & .rule-form__fieldset {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ }
+ }
+
+ &__select[name=category] {
+ width: 50%;
+
+ padding: 0 10px;
+ }
+
+ &__select[name=timezone] {
+ max-height: 200px;
+ width: 50%;
+
+ margin: 0 15px;
+ padding: 0 10px;
+
+ }
+}
diff --git a/src/newsreader/scss/pages/rule/components/rule-form/index.scss b/src/newsreader/scss/pages/rule/components/rule-form/index.scss
new file mode 100644
index 0000000..4c7fbee
--- /dev/null
+++ b/src/newsreader/scss/pages/rule/components/rule-form/index.scss
@@ -0,0 +1 @@
+@import "rule-form";
diff --git a/src/newsreader/scss/pages/rule/elements/index.scss b/src/newsreader/scss/pages/rule/elements/index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/newsreader/scss/pages/rule/index.scss b/src/newsreader/scss/pages/rule/index.scss
new file mode 100644
index 0000000..16b6493
--- /dev/null
+++ b/src/newsreader/scss/pages/rule/index.scss
@@ -0,0 +1,8 @@
+// General imports
+@import "../../partials/variables";
+@import "../../components/index";
+@import "../../elements/index";
+
+// Page specific
+@import "./components/index";
+@import "./elements/index";
diff --git a/src/newsreader/scss/pages/rules/components/card/_card.scss b/src/newsreader/scss/pages/rules/components/card/_card.scss
new file mode 100644
index 0000000..ec09189
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/card/_card.scss
@@ -0,0 +1,20 @@
+.card {
+ &__header {
+ & div {
+ display: flex;
+ flex-direction: row;
+
+ & img {
+ padding: 0 10px;
+ }
+ }
+ }
+
+ &__content {
+ flex-direction: column;
+ }
+
+ &__footer > *:last-child {
+ margin: 0 0 0 10px;
+ }
+}
diff --git a/src/newsreader/scss/pages/rules/components/card/index.scss b/src/newsreader/scss/pages/rules/components/card/index.scss
new file mode 100644
index 0000000..484e154
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/card/index.scss
@@ -0,0 +1 @@
+@import "card";
diff --git a/src/newsreader/scss/pages/rules/components/index.scss b/src/newsreader/scss/pages/rules/components/index.scss
new file mode 100644
index 0000000..7e96fec
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/index.scss
@@ -0,0 +1,3 @@
+@import "card/index";
+@import "rules/index";
+@import "rule-modal/index";
diff --git a/src/newsreader/scss/pages/rules/components/rule-modal/_rule-modal.scss b/src/newsreader/scss/pages/rules/components/rule-modal/_rule-modal.scss
new file mode 100644
index 0000000..0dc53bb
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/rule-modal/_rule-modal.scss
@@ -0,0 +1,27 @@
+.rule-modal {
+ display: flex;
+ flex-direction: column;
+ align-self: center;
+
+ margin: 20px 0;
+ width: 50%;
+
+ border-radius: 2px;
+ background-color: $white;
+
+ &__header {
+ padding: 5px 20px;
+ }
+
+ &__content {
+ padding: 10px 30px;
+ }
+
+ &__footer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ padding: 10px;
+ }
+}
diff --git a/src/newsreader/scss/pages/rules/components/rule-modal/index.scss b/src/newsreader/scss/pages/rules/components/rule-modal/index.scss
new file mode 100644
index 0000000..c19060f
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/rule-modal/index.scss
@@ -0,0 +1 @@
+@import "rule-modal";
diff --git a/src/newsreader/scss/pages/rules/components/rules/_rules.scss b/src/newsreader/scss/pages/rules/components/rules/_rules.scss
new file mode 100644
index 0000000..3fd4c22
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/rules/_rules.scss
@@ -0,0 +1,7 @@
+.rules {
+ &__item {
+ & > * {
+ margin: 0;
+ }
+ }
+}
diff --git a/src/newsreader/scss/pages/rules/components/rules/index.scss b/src/newsreader/scss/pages/rules/components/rules/index.scss
new file mode 100644
index 0000000..e6a0ebf
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/components/rules/index.scss
@@ -0,0 +1 @@
+@import "rules";
diff --git a/src/newsreader/scss/pages/rules/elements/index.scss b/src/newsreader/scss/pages/rules/elements/index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/newsreader/scss/pages/rules/index.scss b/src/newsreader/scss/pages/rules/index.scss
new file mode 100644
index 0000000..16b6493
--- /dev/null
+++ b/src/newsreader/scss/pages/rules/index.scss
@@ -0,0 +1,8 @@
+// General imports
+@import "../../partials/variables";
+@import "../../components/index";
+@import "../../elements/index";
+
+// Page specific
+@import "./components/index";
+@import "./elements/index";
diff --git a/src/newsreader/templates/base.html b/src/newsreader/templates/base.html
index 445d507..087aca7 100644
--- a/src/newsreader/templates/base.html
+++ b/src/newsreader/templates/base.html
@@ -11,7 +11,7 @@
{% if request.user.is_authenticated %}
Home
Categories
- Feeds
+ Feeds
Settings
Logout
{% else %}
diff --git a/src/newsreader/urls.py b/src/newsreader/urls.py
index be59ad1..fb68235 100644
--- a/src/newsreader/urls.py
+++ b/src/newsreader/urls.py
@@ -6,8 +6,9 @@ from rest_framework_swagger.views import get_swagger_view
from newsreader.accounts.urls import urlpatterns as login_urls
from newsreader.news.collection.urls import endpoints as collection_endpoints
+from newsreader.news.collection.urls import urlpatterns as collection_patterns
from newsreader.news.core.urls import endpoints as core_endpoints
-from newsreader.news.core.urls import urlpatterns
+from newsreader.news.core.urls import urlpatterns as core_patterns
schema_view = get_swagger_view(title="Newsreader API")
@@ -18,7 +19,8 @@ endpoints = [
]
urlpatterns = [
- path("", include(urlpatterns)),
+ path("", include(core_patterns)),
+ path("", include(collection_patterns)),
path("accounts/", include((login_urls, "accounts")), name="accounts"),
path("admin/", admin.site.urls, name="admin"),
path("api/", include((endpoints, "api")), name="api"),