Merge static refactor

This commit is contained in:
sonny 2020-02-22 22:44:09 +01:00
parent 5976870d38
commit 3045702a1e
158 changed files with 549 additions and 757 deletions

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'accounts/dist/css/login.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="main">
<main id="login--page" class="main">
<form class="form login-form" method="POST" action="{% url 'accounts:login' %}">
{% csrf_token %}
<div class="form__header">

View file

@ -116,7 +116,7 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = "/static/"
STATICFILES_DIRS = ["src/newsreader/static/icons"]
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
# https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-STATICFILES_FINDERS
STATICFILES_FINDERS = [

View file

@ -1,7 +1,11 @@
import React from 'react';
const Modal = props => {
return <div className="modal">{props.content}</div>;
return (
<div className="modal">
<div className="modal__item">{props.content}</div>
</div>
);
};
export default Modal;

View file

@ -0,0 +1,3 @@
import './pages/homepage/index.js';
import './pages/rules/index.js';
import './pages/categories/index.js';

View file

@ -6,7 +6,7 @@ const CategoryCard = props => {
const { category } = props;
const categoryRules = category.rules.map(rule => {
const faviconUrl = rule.favicon ? rule.favicon : '/static/picture.svg';
const faviconUrl = rule.favicon ? rule.favicon : '/static/icons/picture.svg';
return (
<li key={rule.pk} className="list__item">

View file

@ -4,12 +4,12 @@ import Modal from '../../../components/Modal.js';
const CategoryModal = props => {
const content = (
<div className="category-modal">
<div className="category-modal__header">
<h1 className="h1 category-modal__title">Delete category</h1>
<>
<div className="modal__header">
<h1 className="h1 modal__title">Delete category</h1>
</div>
<div className="category-modal__content">
<div className="modal__content">
<p className="p">Are you sure you want to delete {props.category.name}?</p>
<small className="small">
Collection rules coupled to this category will not be deleted but will have no
@ -17,7 +17,7 @@ const CategoryModal = props => {
</small>
</div>
<div className="category-modal__footer">
<div className="modal__footer">
<button className="button button--confirm" onClick={props.handleCancel}>
Cancel
</button>
@ -28,7 +28,7 @@ const CategoryModal = props => {
Delete category
</button>
</div>
</div>
</>
);
return <Modal content={content} />;

View file

@ -3,10 +3,8 @@ import ReactDOM from 'react-dom';
import App from './App.js';
const content = document.getElementById('categories--page');
const dataScript = document.getElementById('categories-data');
const categories = JSON.parse(dataScript.textContent);
ReactDOM.render(
<App categories={categories} />,
document.getElementsByClassName('content')[0]
);
ReactDOM.render(<App categories={categories} />, content);

View file

@ -17,18 +17,16 @@ class App extends React.Component {
render() {
return (
<>
<main className="main">
<Sidebar />
<FeedList />
<Sidebar />
<FeedList />
{!isEqual(this.props.post, {}) && (
<PostModal
post={this.props.post}
rule={this.props.rule}
category={this.props.category}
/>
)}
</main>
{!isEqual(this.props.post, {}) && (
<PostModal
post={this.props.post}
rule={this.props.rule}
category={this.props.category}
/>
)}
</>
);
}

View file

@ -6,6 +6,7 @@ import { unSelectPost, markPostRead } from '../actions/posts.js';
import { formatDatetime } from '../../../utils.js';
class PostModal extends React.Component {
modalListener = ::this.modalListener;
readTimer = null;
componentDidMount() {
@ -16,6 +17,8 @@ class PostModal extends React.Component {
if (!post.read) {
this.readTimer = setTimeout(markPostRead, 3000, post, token);
}
window.addEventListener('click', this.modalListener);
}
componentWillUnmount() {
@ -24,6 +27,16 @@ class PostModal extends React.Component {
}
this.readTimer = null;
window.removeEventListener('click', this.modalListener);
}
modalListener(e) {
const targetClassName = e.target.className;
if (this.props.post && targetClassName == 'modal post-modal') {
this.props.unSelectPost();
}
}
render() {
@ -32,7 +45,7 @@ class PostModal extends React.Component {
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
return (
<div className="modal">
<div className="modal post-modal">
<div className="post">
<button
className="button post__close-button"
@ -40,7 +53,7 @@ class PostModal extends React.Component {
>
Close{' '}
<span>
<img src="/static/cross.svg" width="20" />
<img src="/static/icons/cross.svg" width="20" />
</span>
</button>
<div className="post__header">
@ -53,7 +66,7 @@ class PostModal extends React.Component {
target="_blank"
rel="noopener noreferrer"
>
<img src="/static/link.svg" />
<img src="/static/icons/link.svg" />
</a>
</div>
</div>

View file

@ -52,7 +52,7 @@ class FeedList extends React.Component {
return (
<div className="post-message">
<div className="post-message__block">
<img src="/static/arrow-left.svg" height="28" width="28" />
<img src="/static/icons/arrow-left.svg" height="28" width="28" />
<p className="post-message__text">
Select a category or rule to show its unread posts
</p>

View file

@ -34,7 +34,7 @@ class PostItem extends React.Component {
target="_blank"
rel="noopener noreferrer"
>
<img src="/static/link.svg" />
<img src="/static/icons/link.svg" />
</a>
</div>
</li>

View file

@ -25,8 +25,8 @@ class CategoryItem extends React.Component {
render() {
const imageSrc = this.state.open
? '/static/chevron-down.svg'
: '/static/chevron-right.svg';
? '/static/icons/chevron-down.svg'
: '/static/icons/chevron-right.svg';
const selected = isSelected(this.props.category, this.props.selected, CATEGORY_TYPE);
const className = selected ? 'category category--selected' : 'category';
@ -35,7 +35,7 @@ class CategoryItem extends React.Component {
});
return (
<li className="categories__item">
<li className="sidebar__item">
<div className={className}>
<div className="category__menu" onClick={() => this.toggleRules()}>
<img src={imageSrc} width="20" heigth="20" />

View file

@ -21,15 +21,15 @@ class RuleItem extends React.Component {
const className = `rules__item ${selected ? 'rules__item--selected' : ''}`;
const favicon = this.props.rule.favicon
? this.props.rule.favicon
: '/static/picture.svg';
: '/static/icons/picture.svg';
return (
<li className={className} onClick={() => this.handleSelect()}>
<div className="rule">
<div className="rules__info">
<span>
<img className="icon" width="20" src={favicon} />
</span>
<h5 className="rule__title" title={this.props.rule.name}>
<h5 className="rules__title" title={this.props.rule.name}>
{this.props.rule.name}
</h5>
</div>

View file

@ -28,13 +28,11 @@ class Sidebar extends React.Component {
return (
<div className="sidebar">
<nav className="categories">
{(this.props.categories.isFetching || this.props.rules.isFetching) && (
<LoadingIndicator />
)}
{(this.props.categories.isFetching || this.props.rules.isFetching) && (
<LoadingIndicator />
)}
<ul>{items}</ul>
</nav>
<ul className="sidebar__nav">{items}</ul>
{!isEqual(this.props.selected.item, {}) && <ReadButton />}
</div>

View file

@ -6,11 +6,12 @@ import configureStore from './configureStore.js';
import App from './App.js';
const content = document.getElementById('homepage--page');
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementsByClassName('content')[0]
content
);

View file

@ -5,10 +5,10 @@ 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 faviconUrl = rule.favicon ? rule.favicon : '/static/icons/picture.svg';
const stateIcon = !rule.error
? '/static/icons/checkmark-circle.svg'
: '/static/icons/warning.svg';
const cardHeader = (
<>
@ -23,20 +23,21 @@ const RuleCard = props => {
const cardContent = (
<>
<ul className="list rules">
<li className="list__item rules__item">{rule.category}</li>
<li className="list__item rules__item">
{rule.error && (
<ul className="list errorlist">
<li className="list__item errorlist__item">{rule.error}</li>
</ul>
)}
{rule.category && <li className="list__item">{rule.category}</li>}
<li className="list__item">
<a className="link" target="_blank" rel="noopener noreferrer" href={rule.url}>
{rule.url}
</a>
</li>
<li className="list__item rules__item">{rule.created}</li>
<li className="list__item rules__item">{rule.timezone}</li>
<li className="list__item">{rule.created}</li>
<li className="list__item">{rule.timezone}</li>
</ul>
{!rule.succeeded && (
<ul className="list errorlist">
<li className="list__item errorlist__item">{rule.error}</li>
</ul>
)}
</>
);

View file

@ -4,27 +4,29 @@ import Modal from '../../../components/Modal.js';
const RuleModal = props => {
const content = (
<div className="rule-modal">
<div className="rule-modal__header">
<h1 className="h1 rule-modal__title">Delete rule</h1>
</div>
<>
<div>
<div className="modal__header">
<h1 className="h1 modal__title">Delete rule</h1>
</div>
<div className="rule-modal__content">
<p className="p">Are you sure you want to delete {props.rule.name}?</p>
</div>
<div className="modal__content">
<p className="p">Are you sure you want to delete {props.rule.name}?</p>
</div>
<div className="rule-modal__footer">
<button className="button button--confirm" onClick={props.handleCancel}>
Cancel
</button>
<button
className="button button--error"
onClick={() => props.handleDelete(props.rule.pk)}
>
Delete rule
</button>
<div className="modal__footer">
<button className="button button--confirm" onClick={props.handleCancel}>
Cancel
</button>
<button
className="button button--error"
onClick={() => props.handleDelete(props.rule.pk)}
>
Delete rule
</button>
</div>
</div>
</div>
</>
);
return <Modal content={content} />;

View file

@ -3,7 +3,8 @@ import ReactDOM from 'react-dom';
import App from './App.js';
const content = document.getElementById('rules--page');
const dataScript = document.getElementById('rules-data');
const rules = JSON.parse(dataScript.textContent);
ReactDOM.render(<App rules={rules} />, document.getElementsByClassName('content')[0]);
ReactDOM.render(<App rules={rules} />, content);

View file

@ -1,11 +1,17 @@
from django import forms
import pytz
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())
timezone = forms.ChoiceField(
widget=forms.Select(attrs={"size": len(pytz.all_timezones)}),
choices=((timezone, timezone) for timezone in pytz.all_timezones),
)
def __init__(self, *args, **kwargs) -> None:
self.user = kwargs.pop("user")

View file

@ -2,12 +2,8 @@
{% load static i18n %}
{% block head %}
<link href="{% static 'collection/dist/css/import.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content">
<main id="import--page" class="main">
<form class="form import-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.non_field_errors }}
@ -37,5 +33,5 @@
</fieldset>
</section>
</form>
</div>
</main>
{% endblock %}

View file

@ -4,28 +4,6 @@
<h1 class="h1 form__title">Create a rule</h1>
{% endblock %}
{% block name-input %}
<input class="input rule-form__input" type="text" name="name" required />
{% endblock %}
{% block category-input %}
<option class="option rule-form__option" value="{{ category.pk }}">
{{ category.name }}
</option>
{% endblock %}
{% block url-input %}
<input class="input rule-form__input" type="url" name="url" required />
{% endblock %}
{% block favicon-input %}
<input class="input rule-form__input" type="url" name="favicon" />
{% endblock %}
{% block timezone-input %}
<option class="option rule-form__option">{{ timezone }}</option>
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Create rule</button>
{% endblock %}

View file

@ -4,31 +4,6 @@
<h1 class="h1 form__title">Update rule</h1>
{% endblock %}
{% block name-input %}
<input class="input rule-form__input" type="text" name="name"
value="{{ rule.name }}" required />
{% endblock %}
{% block category-input %}
<option class="option rule-form__option" value="{{ category.pk }}" {% if rule.category and rule.category.pk == category.pk %}selected{% endif %}>
{{ category.name }}
</option>
{% endblock %}
{% block url-input %}
<input class="input rule-form__input" type="url" name="url" value="{{ rule.url }}" required />
{% endblock %}
{% block favicon-input %}
<input class="input rule-form__input" type="url" value="{{ rule.favicon|default:"" }}" name="favicon" />
{% endblock %}
{% block timezone-input %}
<option class="option rule-form__option" {% if rule.timezone == timezone %}selected{% endif %}>
{{ timezone }}
</option>
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Save rule</button>
{% endblock %}

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'collection/dist/css/rule.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content">
<main id="rule--page" class="main">
<form class="form rule-form" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
@ -17,45 +13,34 @@
</div>
<section class="section form__section rule-form__section">
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Name</label>
{% block name-input %}{% endblock %}
<label class="label" for="name">Name</label>
{{ form.name.errors }}
{{ form.name }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Category</label>
<select class="select rule-form__select" name="category">
{% for category in categories %}
{% block category-input %}{% endblock %}
{% endfor %}
</select>
<label class="label" for="name">Category</label>
{{ form.category.errors }}
{{ form.category }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Feed url</label>
{% block url-input %}{% endblock %}
<label class="label" for="name">Feed url</label>
{{ form.url.errors }}
{{ form.url }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Favicon url</label>
{% block favicon-input %}{% endblock %}
<label class="label" for="name">Favicon url</label>
{{ form.favicon.errors }}
{{ form.favicon }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Timezone</label>
<label class="label" for="name">Timezone</label>
<small class="small helptext">The timezone which the feed uses</small>
<select class="select rule-form__select" size="{{ timezones|length }}" name="timezone">
{% for timezone in timezones %}
{% block timezone-input %}{% endblock %}
{% endfor %}
</select>
{{ form.timezone.errors }}
{{ form.timezone }}
</fieldset>
</section>
@ -66,5 +51,5 @@
</fieldset>
</section>
</form>
</div>
</main>
{% endblock %}

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'collection/dist/css/rules.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content"></div>
<main id="rules--page" class="main"></main>
{% endblock %}
{% block scripts %}
@ -19,15 +15,15 @@
"pk": {{ rule.pk }},
"name": "{{ rule.name }}",
"url": "{{ rule.url }}",
"favicon": {% if rule.favicon %}"{{ rule.favicon }}"{% else %}""{% endif %},
"category": "{{ rule.category }}",
"favicon": "{{ rule.favicon|default:'' }}",
"category": "{{ rule.category|default:'' }}",
"timezone": "{{ rule.timezone }}",
"created": "{{ rule.created }}",
"succeeded": {% if rule.succeeded %}true{% else %}false{% endif %},
"error": {% if rule.error %}"{{ rule.error }}"{% else %}""{% endif %}
"error": "{{ rule.error|default:'' }}"
}
{% endfor %}
]
</script>
<script src="{% static 'collection/dist/js/rules.js' %}"></script>
<script src="{% static 'js/rules.js' %}"></script>
{% endblock %}

View file

@ -94,7 +94,7 @@ class NestedPostCategoryView(ListAPIView):
queryset = Post.objects.filter(
rule__in=category.rules.values_list("id", flat=True)
).order_by("rule__name", "-publication_date")
).order_by("rule", "-publication_date")
return queryset

View file

@ -1,5 +1,6 @@
from django import forms
from newsreader.accounts.models import User
from newsreader.news.collection.models import CollectionRule
from newsreader.news.core.models import Category
@ -11,17 +12,23 @@ class CategoryForm(forms.ModelForm):
widget=forms.widgets.CheckboxSelectMultiple,
)
user = forms.ModelChoiceField(
queryset=User.objects.none(),
widget=forms.widgets.HiddenInput(attrs={"readonly": True}),
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
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)
self.fields["user"].queryset = User.objects.filter(pk=self.user.pk)
self.initial["user"] = self.user
def save(self, commit=True) -> Category:
instance = super().save(commit=False)
instance.user = self.user
if commit:
instance.save()
@ -34,4 +41,4 @@ class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ("name", "rules")
fields = ("name", "rules", "user")

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'core/dist/css/categories.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content"></div>
<main id="categories--page" class="main"></main>
{% endblock %}
{% block scripts %}
@ -25,7 +21,7 @@
{
"pk": {{ rule.pk }},
"name": "{{ rule.name }}",
"favicon": {% if rule.favicon %}"{{ rule.favicon }}"{% else %}null{% endif %},
"favicon": "{{ rule.favicon|default:'' }}",
"created": "{{ rule.created }}"
}
{% endfor %}
@ -34,5 +30,5 @@
{% endfor %}
]
</script>
<script src="{% static 'core/dist/js/categories.js' %}"></script>
<script src="{% static 'js/categories.js' %}"></script>
{% endblock %}

View file

@ -4,15 +4,6 @@
<h1 class="h1 form__title">Create a category</h1>
{% endblock %}
{% block name-input %}
<input class="input category-form__input" type="text" name="name" required />
{% endblock %}
{% block rule-input %}
<input class="input category-form__input" type="checkbox" name="rules"
value="{{ rule.pk }}" />
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Create category</button>
{% endblock %}

View file

@ -4,17 +4,6 @@
<h1 class="h1 form__title">Update category</h1>
{% endblock %}
{% block name-input %}
<input class="input category-form__input" type="text" name="name"
value="{{ category.name }}" required />
{% endblock %}
{% block rule-input %}
<input class="input category-form__input" type="checkbox" name="rules"
{% if rule.pk in category.rule_ids %}checked{% endif %}
value="{{ rule.pk }}" />
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Save category</button>
{% endblock %}

View file

@ -2,24 +2,24 @@
{% load static %}
{% block head %}
<link href="{% static 'core/dist/css/category.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content">
<main id="category--page" class="main">
<form class="form category-form" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="form__header">
{% block form-header %}{% endblock %}
</div>
{{ form.non_field_errors }}
{{ form.user.errors }}
{{ form.user }}
<section class="section form__section category-form__section">
<fieldset class="form__fieldset category-form__fieldset">
<label class="label category-form__label" for="name">Name</label>
{% block name-input %}{% endblock %}
{{ form.name.errors }}
{{ form.name }}
</fieldset>
</section>
@ -29,17 +29,21 @@
<small class="small help-text">
Note that existing assigned rules will be reassigned to this category
</small>
{{ form.rules.errors }}
<ul class="list checkbox-list">
{% for rule in rules %}
<li class="list__item checkbox-list__item">
{% block rule-input %}{% endblock %}
<input class="input category-form__input" type="checkbox" name="rules"
{% if category and rule.pk in category.rule_ids %}checked{% endif %}
value="{{ rule.pk }}" />
<img class="favicon"
src="{% if rule.favicon %}{{ rule.favicon }}{% else %}/static/picture.svg{% endif %}" />
src="{% if rule.favicon %}{{ rule.favicon }}{% else %}/static/icons/picture.svg{% endif %}" />
<span>{{ rule.name }}</span>
</li>
{% endfor %}
</ul>
{{ form.rules.errors }}
</fieldset>
</section>
@ -50,5 +54,5 @@
</fieldset>
</section>
</form>
</div>
</main>
{% endblock %}

View file

@ -2,14 +2,10 @@
{% load static %}
{% block head %}
<link href="{% static 'core/dist/css/homepage.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content"></div>
<main id="homepage--page" class="main"></main>
{% endblock %}
{% block scripts %}
<script src="{% static 'core/dist/js/homepage.js' %}"></script>
<script src="{% static 'js/homepage.js' %}"></script>
{% endblock %}

View file

@ -488,11 +488,11 @@ class NestedCategoryPostView(TestCase):
self.assertEquals(posts[0]["title"], "Second BBC post")
self.assertEquals(posts[1]["title"], "First BBC post")
self.assertEquals(posts[2]["title"], "Second Reuters post")
self.assertEquals(posts[3]["title"], "First Reuters post")
self.assertEquals(posts[2]["title"], "Second Guardian post")
self.assertEquals(posts[3]["title"], "First Guardian post")
self.assertEquals(posts[4]["title"], "Second Guardian post")
self.assertEquals(posts[5]["title"], "First Guardian post")
self.assertEquals(posts[4]["title"], "Second Reuters post")
self.assertEquals(posts[5]["title"], "First Reuters post")
def test_only_posts_from_category_are_returned(self):
category = CategoryFactory.create(user=self.user)

View file

@ -27,7 +27,11 @@ class CategoryCreateViewTestCase(CategoryViewTestCase, TestCase):
def test_creation(self):
rules = CollectionRuleFactory.create_batch(size=4, user=self.user)
data = {"name": "new-category", "rules": [rule.pk for rule in rules]}
data = {
"name": "new-category",
"rules": [rule.pk for rule in rules],
"user": self.user.pk,
}
response = self.client.post(self.url, data)
self.assertEquals(response.status_code, 302)
@ -55,13 +59,29 @@ class CategoryCreateViewTestCase(CategoryViewTestCase, TestCase):
size=3, user=self.user, category=None
)
data = {"name": "new-category", "rules": [rule.pk for rule in other_rules]}
data = {
"name": "new-category",
"rules": [rule.pk for rule in other_rules],
"user": self.user.pk,
}
response = self.client.post(self.url, data)
self.assertContains(response, "not one of the available choices")
self.assertEquals(Category.objects.count(), 0)
def test_unique_together(self):
category = CategoryFactory(name="category", user=self.user)
data = {"name": "category", "user": self.user.pk, "rules": []}
response = self.client.post(self.url, data)
categories = Category.objects.all()
self.assertContains(response, "already exists")
self.assertCountEqual(categories, [category])
class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
def setUp(self):
@ -71,7 +91,7 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.url = reverse("category-update", args=[self.category.pk])
def test_name_change(self):
data = {"name": "durp"}
data = {"name": "durp", "user": self.user.pk}
self.client.post(self.url, data)
self.category.refresh_from_db()
@ -80,7 +100,11 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
def test_add_collection_rules(self):
rules = CollectionRuleFactory.create_batch(size=4, user=self.user)
data = {"name": self.category.name, "rules": [rule.pk for rule in rules]}
data = {
"name": self.category.name,
"rules": [rule.pk for rule in rules],
"user": self.user.pk,
}
self.client.post(self.url, data)
@ -100,7 +124,11 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.assertCountEqual(self.category.rule_ids, [rule.pk for rule in current_rules])
data = {"name": self.category.name, "rules": [rule.pk for rule in other_rules]}
data = {
"name": self.category.name,
"rules": [rule.pk for rule in other_rules],
"user": self.user.pk,
}
self.client.post(self.url, data)
@ -116,7 +144,7 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.assertCountEqual(self.category.rule_ids, [rule.pk for rule in current_rules])
data = {"name": "durp"}
data = {"name": "durp", "user": self.user.pk}
self.client.post(self.url, data)
self.category.refresh_from_db()
@ -139,7 +167,7 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
other_category = CategoryFactory(name="other category", user=other_user)
other_category.rules.set([*other_rules])
data = {"name": "durp"}
data = {"name": "durp", "user": other_user.pk}
other_url = reverse("category-update", args=[other_category.pk])
response = self.client.post(other_url, data)
@ -161,7 +189,11 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.assertCountEqual(self.category.rule_ids, [rule.pk for rule in current_rules])
data = {"name": self.category.name, "rules": [rule.pk for rule in other_rules]}
data = {
"name": self.category.name,
"rules": [rule.pk for rule in other_rules],
"user": self.user.pk,
}
response = self.client.post(self.url, data)
@ -172,3 +204,16 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
other_category.refresh_from_db()
self.assertCountEqual(other_category.rule_ids, [rule.pk for rule in other_rules])
def test_unique_together(self):
other_category = CategoryFactory(name="other category", user=self.user)
url = reverse("category-update", args=[other_category.pk])
data = {"name": "category", "user": self.user.pk, "rules": []}
response = self.client.post(url, data)
categories = Category.objects.all()
self.assertContains(response, "already exists")
self.assertCountEqual(categories, [self.category, other_category])

View file

@ -53,9 +53,7 @@ class CategoryDetailMixin:
return context_data
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
return {**super().get_form_kwargs(), "user": self.request.user}
class CategoryListView(CategoryViewMixin, ListView):

View file

@ -1,4 +1,6 @@
.card {
@extend .card;
&__header {
& div {
display: flex;

View file

@ -1 +1,2 @@
@import "card";
@import "rule-card";

View file

@ -1,5 +0,0 @@
.content {
display: flex;
flex-direction: column;
align-items: center;
}

View file

@ -1 +0,0 @@
@import "content";

View file

@ -1,20 +1,23 @@
.errorlist {
@extend .list;
padding: 10px;
margin: 5px 0;
padding: 0;
background-color: $error-red;
color: $white;
list-style: disc;
list-style-position: inside;
& li {
margin: 15px 0 0 0;
&__item {
margin: 10px 0;
padding: 10px;
&:first-child {
margin: 0;
}
background-color: $error-red;
border-radius: 5px;
}
& li {
@extend .errorlist__item;
}
}

View file

@ -1,8 +1,9 @@
.category-form {
@extend .form;
margin: 20px 0;
&__section:last-child {
& .category-form__fieldset {
display: flex;
flex-direction: row;

View file

@ -2,7 +2,6 @@
margin: 20px 0;
&__section:last-child {
& .rule-form__fieldset {
display: flex;
flex-direction: row;
@ -10,18 +9,17 @@
}
}
&__select[name=category] {
#id_category {
width: 50%;
padding: 0 10px;
}
&__select[name=timezone] {
#id_timezone {
max-height: 200px;
width: 50%;
margin: 0 15px;
padding: 0 10px;
}
}

View file

@ -1 +1,12 @@
@import "form";
@import "category-form";
@import "rule-form";
@import "import-form";
@import "login-form";
@import "activation-form";
@import "register-form";
@import "password-reset-form";
@import "password-reset-confirm-form";

View file

@ -1,13 +1,26 @@
@import "./body/index";
@import "./form/index";
@import "./main/index";
@import "./navbar/index";
@import "./loading-indicator/index";
@import "./modal/index";
@import "./card/index";
@import "./list/index";
@import "./content/index";
@import "./messages/index";
@import "./section/index";
@import "./errorlist/index";
@import "./fieldset/index";
@import "body/index";
@import "form/index";
@import "main/index";
@import "navbar/index";
@import "loading-indicator/index";
@import "modal/index";
@import "card/index";
@import "list/index";
@import "messages/index";
@import "section/index";
@import "errorlist/index";
@import "fieldset/index";
@import "sidebar/index";
@import "rules/index";
@import "category/index";
@import "post/index";
@import "post-block/index";
@import "post-message/index";
@import "posts/index";
@import "posts-header/index";
@import "posts-info/index";
@import "posts-section/index";

View file

@ -1,4 +1,7 @@
.main {
margin: 1% 10% 5% 10%;
background-color: $white;
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0;
}

View file

@ -1,16 +1,32 @@
.messages {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
margin: 5px 0 20px 0;
padding: 15px 0;
color: $white;
background-color: $error-red;
&__item {
padding: 0 30px;
width: 80%;
padding: 20px 15px;
margin: 5px 0;
border-radius: 5px;
background-color: $focus-blue;
&--error {
background-color: $error-red;
}
&--warning {
background-color: $light-orange;
}
&--success {
background-color: $success-green;
}
}
}

View file

@ -1,12 +1,42 @@
.modal {
display: flex;
flex-direction: column;
align-items: center;
position: fixed;
width: 100%;
height: 100%;
top: 0;
background-color: $dark;
&__item {
display: flex;
flex-direction: column;
align-self: center;
margin: 20px 0;
padding: 20px;
width: 60%;
border-radius: 5px;
background-color: $white;
}
&__header {
padding: 5px 20px;
}
&__content {
padding: 10px 30px;
}
&__footer {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px;
}
}

View file

@ -0,0 +1,9 @@
.post-modal {
@extend .modal;
margin: 0;
padding: 0;
border-radius: 0;
cursor: pointer;
}

View file

@ -1 +1,3 @@
@import "modal";
@import "post-modal";

View file

@ -1,6 +1,7 @@
.post-block {
display: flex;
flex-direction: column;
align-items: center;
width: 70%;
margin: 0 0 2% 0;

View file

@ -15,6 +15,8 @@
background-color: $white;
cursor: initial;
&__header {
display: flex;
flex-direction: column;
@ -58,7 +60,11 @@
font-size: 18px;
& p {
padding: 20px 0 0 0;
padding: 10px 0;
}
& h1, h2, h3 {
margin: 20px 0 5px 0;
}
& img {

View file

@ -1,6 +1,7 @@
.posts-section {
display: flex;
flex-direction: column;
width: 95%;
margin: 20px;
padding: 10px;

View file

@ -1,4 +1,6 @@
.rules {
padding: 0;
&__item {
display: flex;
justify-content: space-between;
@ -25,4 +27,20 @@
background-color: darken($azureish-white, +10%);
}
}
&__info {
display: flex;
align-items: center;
width: 80%;
}
&__title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .badge {
display: flex;
}
}

View file

@ -0,0 +1,26 @@
.sidebar {
display: flex;
flex-direction: column;
align-items: center;
align-self: start;
position: sticky;
top: 5%;
width: 20%;
&__nav {
width: 100%;
max-height: 80vh;
overflow: auto;
list-style: none;
border-radius: 5px;
font-family: $sidebar-font;
&__item {
padding: 2px 10px 5px 10px;
}
}
}

View file

@ -1,4 +1,6 @@
.read-button {
@extend .button;
margin: 20px 0 0 0;
color: $white;

View file

@ -1 +1,2 @@
@import "button";
@import "_read-button";

View file

@ -7,3 +7,4 @@
@import "input/index";
@import "label/index";
@import "help-text/index";
@import "badge/index";

View file

@ -0,0 +1,5 @@
@import "partials/index";
@import "components/index";
@import "elements/index";
@import "pages/index";

View file

@ -1,8 +0,0 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";

View file

@ -1,5 +0,0 @@
.card {
&__footer > *:last-child {
margin: 0 0 0 10px;
}
}

View file

@ -1 +0,0 @@
@import "card";

View file

@ -1,27 +0,0 @@
.category-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;
}
}

View file

@ -1 +0,0 @@
@import "category-modal";

View file

@ -1,2 +0,0 @@
@import "card/index";
@import "category-modal/index";

View file

@ -1,8 +1,7 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";
#categories--page {
& .card {
&__footer > *:last-child {
margin: 0 0 0 10px;
}
}
}

View file

@ -1 +0,0 @@
@import "category-form";

View file

@ -1 +0,0 @@
@import "category-form/index";

View file

@ -1,8 +1,2 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";
#category--page {
}

View file

@ -1,24 +0,0 @@
.categories {
display: flex;
flex-direction: column;
align-items: center;
width: 90%;
font-family: $sidebar-font;
border-radius: 2px;
& ul {
margin: 0;
padding: 0;
width: 100%;
list-style: none;
border-radius: 5px;
}
&__item {
padding: 2px 10px 5px 10px;
}
}

View file

@ -1 +0,0 @@
@import "categories";

View file

@ -1,7 +0,0 @@
.content {
display: flex;
flex-direction: column;
align-items: center;
margin: 2% 0 0 0;
}

View file

@ -1 +0,0 @@
@import "content";

View file

@ -1,18 +0,0 @@
@import "content/index";
@import "main/index";
@import "sidebar/index";
@import "categories/index";
@import "category/index";
@import "rules/index";
@import "rule/index";
@import "post-block/index";
@import "posts-section/index";
@import "posts/index";
@import "posts-header/index";
@import "posts-info/index";
@import "post/index";
@import "post-message/index";
@import "read-button/index";

View file

@ -1,12 +0,0 @@
.main {
display: flex;
flex-direction: row;
width: 100%;
margin: 0;
background-color: initial;
&--centered {
justify-content: center;
}
}

Some files were not shown because too many files have changed in this diff Show more