Form / view file refactor

This commit is contained in:
Sonny Bakker 2020-09-19 17:00:41 +02:00
parent 355f16b387
commit abb5328feb
26 changed files with 285 additions and 143 deletions

View file

@ -44,6 +44,8 @@ class PostModal extends React.Component {
const post = this.props.post;
const publicationDate = formatDatetime(post.publicationDate);
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
// TODO add mapping & get urls from backend
const ruleUrl =
this.props.rule.type === FEED
? `/collection/rules/${this.props.rule.id}/`

View file

@ -3,3 +3,4 @@ export const CATEGORY_TYPE = 'CATEGORY';
export const SUBREDDIT = 'subreddit';
export const FEED = 'feed';
export const TWITTER_TIMELINE = 'twitter_timeline';

View file

@ -0,0 +1,4 @@
from newsreader.news.collection.forms.feed import FeedForm, OPMLImportForm
from newsreader.news.collection.forms.reddit import SubRedditForm
from newsreader.news.collection.forms.rules import CollectionRuleBulkForm
from newsreader.news.collection.forms.twitter import TwitterTimelineForm

View file

@ -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):
self.user = kwargs.pop("user")
super().__init__(*args, **kwargs)
self.fields["category"].queryset = Category.objects.filter(user=self.user)
def save(self, commit=True):
instance = super().save(commit=False)
instance.user = self.user
if commit:
instance.save()
self.save_m2m()
return instance
class Meta:
model = CollectionRule
fields = "__all__"

View file

@ -0,0 +1,28 @@
from django import forms
from django.utils.translation import gettext_lazy as _
import pytz
from newsreader.core.forms import CheckboxInput
from newsreader.news.collection.forms.base import CollectionRuleForm
from newsreader.news.collection.models import CollectionRule
class FeedForm(CollectionRuleForm):
timezone = forms.ChoiceField(
widget=forms.Select(attrs={"size": len(pytz.all_timezones)}),
choices=((timezone, timezone) for timezone in pytz.all_timezones),
help_text=_("The timezone which the feed uses"),
initial=pytz.utc,
)
class Meta:
model = CollectionRule
fields = ("name", "url", "timezone", "favicon", "category")
class OPMLImportForm(forms.Form):
file = forms.FileField(allow_empty_file=False)
skip_existing = forms.BooleanField(
initial=False, required=False, widget=CheckboxInput
)

View file

@ -9,6 +9,7 @@ from newsreader.core.forms import CheckboxInput
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.models import CollectionRule
from newsreader.news.collection.reddit import REDDIT_API_URL
from newsreader.news.collection.forms.base import CollectionRuleForm
from newsreader.news.core.models import Category
@ -22,53 +23,9 @@ def get_reddit_help_text():
)
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),
help_text=_("The timezone which the feed uses"),
initial=pytz.utc,
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super().__init__(*args, **kwargs)
self.fields["category"].queryset = Category.objects.filter(user=self.user)
def save(self, commit=True):
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")
class CollectionRuleBulkForm(forms.Form):
rules = forms.ModelMultipleChoiceField(queryset=CollectionRule.objects.none())
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
self.fields["rules"].queryset = CollectionRule.objects.filter(user=user)
class SubRedditRuleForm(CollectionRuleForm):
class SubRedditForm(CollectionRuleForm):
url = forms.URLField(max_length=1024, help_text=get_reddit_help_text)
timezone = None
def clean_url(self):
url = self.cleaned_data["url"]
@ -92,10 +49,3 @@ class SubRedditRuleForm(CollectionRuleForm):
class Meta:
model = CollectionRule
fields = ("name", "url", "favicon", "category")
class OPMLImportForm(forms.Form):
file = forms.FileField(allow_empty_file=False)
skip_existing = forms.BooleanField(
initial=False, required=False, widget=CheckboxInput
)

View file

@ -0,0 +1,15 @@
from django import forms
from newsreader.news.collection.models import CollectionRule
class CollectionRuleBulkForm(forms.Form):
rules = forms.ModelMultipleChoiceField(queryset=CollectionRule.objects.none())
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
self.fields["rules"].queryset = CollectionRule.objects.filter(user=user)

View file

@ -0,0 +1,32 @@
from django import forms
from django.utils.translation import gettext_lazy as _
import pytz
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.forms.base import CollectionRuleForm
from newsreader.news.collection.models import CollectionRule
class TwitterTimelineForm(CollectionRuleForm):
screen_name = forms.CharField(
max_length=255,
label=_("Twitter profile name"),
help_text=_("Profile name without hashtags"),
)
def save(self, commit=True):
instance = super().save(commit=False)
instance.type = RuleTypeChoices.twitter_timeline
instance.timezone = str(pytz.utc)
if commit:
instance.save()
self.save_m2m()
return instance
class Meta:
model = CollectionRule
fields = ("name", "screen_name", "favicon", "category")

View file

@ -70,4 +70,4 @@ class CollectionRule(TimeStampedModel):
if self.type == RuleTypeChoices.subreddit:
return reverse("news:collection:subreddit-update", kwargs={"pk": self.pk})
return reverse("news:collection:rule-update", kwargs={"pk": self.pk})
return reverse("news:collection:feed-update", kwargs={"pk": self.pk})

View file

@ -4,6 +4,6 @@
{% block content %}
<main id="rule--page" class="main">
{% url "news:collection:rules" as cancel_url %}
{% include "components/form/form.html" with form=form title="Create rule" cancel_url=cancel_url confirm_text="Create rule" %}
{% include "components/form/form.html" with form=form title="Add a feed" cancel_url=cancel_url confirm_text="Add feed" %}
</main>
{% endblock %}

View file

@ -3,12 +3,12 @@
{% block content %}
<main id="rule--page" class="main">
{% if rule.error %}
{% if feed.error %}
{% trans "Failed to retrieve posts" as title %}
{% include "components/textbox/textbox.html" with title=title body=rule.error class="text-section--error" only %}
{% include "components/textbox/textbox.html" with title=title body=feed.error class="text-section--error" only %}
{% endif %}
{% url "news:collection:rules" as cancel_url %}
{% include "components/form/form.html" with form=form title="Update rule" cancel_url=cancel_url confirm_text="Save rule" only %}
{% include "components/form/form.html" with form=form title="Update feed" cancel_url=cancel_url confirm_text="Save feed" only %}
</main>
{% endblock %}

View file

@ -4,6 +4,6 @@
{% block content %}
<main id="import--page" class="main">
{% url "news:collection:rules" as cancel_url %}
{% include "components/form/form.html" with form=form title="Import an OPML file" cancel_url=cancel_url confirm_text="Import rules" %}
{% include "components/form/form.html" with form=form title="Import an OPML file" cancel_url=cancel_url confirm_text="Import feeds" %}
</main>
{% endblock %}

View file

@ -14,8 +14,9 @@
</fieldset>
<div class="form__actions">
<a class="link button button--confirm" href="{% url "news:collection:rule-create" %}">{% trans "Add a rule" %}</a>
<a class="link button button--confirm" href="{% url "news:collection:subreddit-create" %}">{% trans "Add a subreddit" %}</a>
<a class="link button button--confirm" href="{% url "news:collection:feed-create" %}">{% trans "Add a feed" %}</a>
<a class="link button button--reddit" href="{% url "news:collection:subreddit-create" %}">{% trans "Add a subreddit" %}</a>
<a class="link button button--twitter" href="{% url "news:collection:twitter-timeline-create" %}">{% trans "Add a Twitter profile" %}</a>
<a class="link button button--confirm" href="{% url "news:collection:import" %}">{% trans "Import rules" %}</a>
</div>
</section>

View file

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<main id="twitter--page" class="main">
{% url "news:collection:rules" as cancel_url %}
{% include "components/form/form.html" with form=form title="Add a Twitter profile" cancel_url=cancel_url confirm_text="Add profile" %}
</main>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% load static i18n %}
{% block content %}
<main id="twitter--page" class="main">
{% if timeline.error %}
{% trans "Failed to retrieve posts" as title %}
{% include "components/textbox/textbox.html" with title=title body=timeline.error class="text-section--error" only %}
{% endif %}
{% url "news:collection:rules" as cancel_url %}
{% include "components/form/form.html" with form=form title="Update profile" cancel_url=cancel_url confirm_text="Save profile" %}
</main>
{% endblock %}

View file

@ -49,7 +49,7 @@ class CollectionRuleViewTestCase:
timezone=other_rule.timezone,
)
other_url = reverse("news:collection:rule-update", args=[other_rule.pk])
other_url = reverse("news:collection:feed-update", args=[other_rule.pk])
response = self.client.post(other_url, self.form_data)
self.assertEquals(response.status_code, 404)

View file

@ -10,11 +10,11 @@ from newsreader.news.collection.tests.views.base import CollectionRuleViewTestCa
from newsreader.news.core.tests.factories import CategoryFactory
class CollectionRuleCreateViewTestCase(CollectionRuleViewTestCase, TestCase):
class FeedCreateViewTestCase(CollectionRuleViewTestCase, TestCase):
def setUp(self):
super().setUp()
self.url = reverse("news:collection:rule-create")
self.url = reverse("news:collection:feed-create")
self.form_data.update(
name="new rule",
@ -38,14 +38,14 @@ class CollectionRuleCreateViewTestCase(CollectionRuleViewTestCase, TestCase):
self.assertEquals(rule.user.pk, self.user.pk)
class CollectionRuleUpdateViewTestCase(CollectionRuleViewTestCase, TestCase):
class FeedUpdateViewTestCase(CollectionRuleViewTestCase, TestCase):
def setUp(self):
super().setUp()
self.rule = FeedFactory(
name="collection rule", user=self.user, category=self.category
)
self.url = reverse("news:collection:rule-update", kwargs={"pk": self.rule.pk})
self.url = reverse("news:collection:feed-update", kwargs={"pk": self.rule.pk})
self.form_data.update(
name=self.rule.name,
@ -94,7 +94,7 @@ class CollectionRuleUpdateViewTestCase(CollectionRuleViewTestCase, TestCase):
category=self.category,
type=RuleTypeChoices.subreddit,
)
url = reverse("news:collection:rule-update", kwargs={"pk": rule.pk})
url = reverse("news:collection:feed-update", kwargs={"pk": rule.pk})
response = self.client.get(url)

View file

@ -84,7 +84,7 @@ class OPMLImportTestCase(TestCase):
rules = CollectionRule.objects.all()
self.assertEquals(len(rules), 0)
self.assertFormError(response, "form", "file", _("No (new) rules found"))
self.assertFormError(response, "form", "file", _("No (new) feeds found"))
def test_invalid_feeds(self):
file_path = self._get_file_path("invalid-url-feeds.opml")
@ -99,7 +99,7 @@ class OPMLImportTestCase(TestCase):
rules = CollectionRule.objects.all()
self.assertEquals(len(rules), 0)
self.assertFormError(response, "form", "file", _("No (new) rules found"))
self.assertFormError(response, "form", "file", _("No (new) feeds found"))
def test_invalid_file(self):
file_path = self._get_file_path("test.png")

View file

@ -11,12 +11,14 @@ from newsreader.news.collection.views import (
CollectionRuleBulkDeleteView,
CollectionRuleBulkDisableView,
CollectionRuleBulkEnableView,
CollectionRuleCreateView,
CollectionRuleListView,
CollectionRuleUpdateView,
FeedCreateView,
FeedUpdateView,
OPMLImportView,
SubRedditCreateView,
SubRedditUpdateView,
TwitterTimelineCreateView,
TwitterTimelineUpdateView,
)
@ -28,17 +30,13 @@ endpoints = [
]
urlpatterns = [
# Feeds
path(
"feeds/<int:pk>/", login_required(FeedUpdateView.as_view()), name="feed-update"
),
path("feeds/create/", login_required(FeedCreateView.as_view()), name="feed-create"),
# Generic rules
path("rules/", login_required(CollectionRuleListView.as_view()), name="rules"),
path(
"rules/<int:pk>/",
login_required(CollectionRuleUpdateView.as_view()),
name="rule-update",
),
path(
"rules/create/",
login_required(CollectionRuleCreateView.as_view()),
name="rule-create",
),
path(
"rules/delete/",
login_required(CollectionRuleBulkDeleteView.as_view()),
@ -54,15 +52,27 @@ urlpatterns = [
login_required(CollectionRuleBulkDisableView.as_view()),
name="rules-disable",
),
path("rules/import/", login_required(OPMLImportView.as_view()), name="import"),
# Reddit
path(
"rules/subreddits/create/",
"subreddits/create/",
login_required(SubRedditCreateView.as_view()),
name="subreddit-create",
),
path(
"rules/subreddits/<int:pk>/",
"subreddits/<int:pk>/",
login_required(SubRedditUpdateView.as_view()),
name="subreddit-update",
),
path("rules/import/", login_required(OPMLImportView.as_view()), name="import"),
# Twitter
path(
"twitter/timelines/create/",
login_required(TwitterTimelineCreateView.as_view()),
name="twitter-timeline-create",
),
path(
"twitter/timelines/<int:pk>/",
login_required(TwitterTimelineUpdateView.as_view()),
name="twitter-timeline-update",
),
]

View file

@ -1,3 +1,8 @@
from newsreader.news.collection.views.feed import (
FeedCreateView,
FeedUpdateView,
OPMLImportView,
)
from newsreader.news.collection.views.reddit import (
SubRedditCreateView,
SubRedditUpdateView,
@ -6,8 +11,9 @@ from newsreader.news.collection.views.rules import (
CollectionRuleBulkDeleteView,
CollectionRuleBulkDisableView,
CollectionRuleBulkEnableView,
CollectionRuleCreateView,
CollectionRuleListView,
CollectionRuleUpdateView,
OPMLImportView,
)
from newsreader.news.collection.views.twitter import (
TwitterTimelineCreateView,
TwitterTimelineUpdateView,
)

View file

@ -2,7 +2,6 @@ from django.urls import reverse_lazy
import pytz
from newsreader.news.collection.forms import CollectionRuleForm
from newsreader.news.collection.models import CollectionRule
from newsreader.news.core.models import Category
@ -17,7 +16,6 @@ class CollectionRuleViewMixin:
class CollectionRuleDetailMixin:
success_url = reverse_lazy("news:collection:rules")
form_class = CollectionRuleForm
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)

View file

@ -0,0 +1,62 @@
from django.contrib import messages
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView, FormView, UpdateView
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.forms import (
CollectionRuleBulkForm,
FeedForm,
OPMLImportForm,
)
from newsreader.news.collection.models import CollectionRule
from newsreader.news.collection.views.base import (
CollectionRuleDetailMixin,
CollectionRuleViewMixin,
)
from newsreader.utils.opml import parse_opml
class FeedUpdateView(CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView):
template_name = "news/collection/views/feed-update.html"
form_class = FeedForm
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(type=RuleTypeChoices.feed)
class FeedCreateView(CollectionRuleViewMixin, CollectionRuleDetailMixin, CreateView):
template_name = "news/collection/views/feed-create.html"
form_class = FeedForm
context_object_name = "feed"
class OPMLImportView(FormView):
form_class = OPMLImportForm
template_name = "news/collection/views/import.html"
def form_valid(self, form):
user = self.request.user
file = form.cleaned_data["file"]
skip_existing = form.cleaned_data["skip_existing"]
instances = parse_opml(file, user, skip_existing=skip_existing)
try:
feeds = CollectionRule.objects.bulk_create(instances)
except IOError:
form.add_error("file", _("Invalid OPML file"))
return self.form_invalid(form)
if not feeds:
form.add_error("file", _("No (new) feeds found"))
return self.form_invalid(form)
message = _(f"{len(feeds)} new feeds created")
messages.success(self.request, message)
return super().form_valid(form)
def get_success_url(self):
return reverse("news:collection:rules")

View file

@ -1,7 +1,7 @@
from django.views.generic.edit import CreateView, UpdateView
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.forms import SubRedditRuleForm
from newsreader.news.collection.forms import SubRedditForm
from newsreader.news.collection.views.base import (
CollectionRuleDetailMixin,
CollectionRuleViewMixin,
@ -11,14 +11,14 @@ from newsreader.news.collection.views.base import (
class SubRedditCreateView(
CollectionRuleViewMixin, CollectionRuleDetailMixin, CreateView
):
form_class = SubRedditRuleForm
form_class = SubRedditForm
template_name = "news/collection/views/subreddit-create.html"
class SubRedditUpdateView(
CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView
):
form_class = SubRedditRuleForm
form_class = SubRedditForm
template_name = "news/collection/views/subreddit-update.html"
context_object_name = "subreddit"

View file

@ -2,17 +2,14 @@ from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView, FormView, UpdateView
from django.views.generic.edit import FormView
from django.views.generic.list import ListView
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.forms import CollectionRuleBulkForm, OPMLImportForm
from newsreader.news.collection.models import CollectionRule
from newsreader.news.collection.forms import CollectionRuleBulkForm
from newsreader.news.collection.views.base import (
CollectionRuleDetailMixin,
CollectionRuleViewMixin,
)
from newsreader.utils.opml import parse_opml
class CollectionRuleListView(CollectionRuleViewMixin, ListView):
@ -21,23 +18,6 @@ class CollectionRuleListView(CollectionRuleViewMixin, ListView):
context_object_name = "rules"
class CollectionRuleUpdateView(
CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView
):
template_name = "news/collection/views/rule-update.html"
context_object_name = "rule"
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(type=RuleTypeChoices.feed)
class CollectionRuleCreateView(
CollectionRuleViewMixin, CollectionRuleDetailMixin, CreateView
):
template_name = "news/collection/views/rule-create.html"
class CollectionRuleBulkView(FormView):
form_class = CollectionRuleBulkForm
@ -90,33 +70,3 @@ class CollectionRuleBulkDeleteView(CollectionRuleBulkView):
rule.delete()
return response
class OPMLImportView(FormView):
form_class = OPMLImportForm
template_name = "news/collection/views/import.html"
def form_valid(self, form):
user = self.request.user
file = form.cleaned_data["file"]
skip_existing = form.cleaned_data["skip_existing"]
instances = parse_opml(file, user, skip_existing=skip_existing)
try:
rules = CollectionRule.objects.bulk_create(instances)
except IOError:
form.add_error("file", _("Invalid OPML file"))
return self.form_invalid(form)
if not rules:
form.add_error("file", _("No (new) rules found"))
return self.form_invalid(form)
message = _(f"{len(rules)} new rules created")
messages.success(self.request, message)
return super().form_valid(form)
def get_success_url(self):
return reverse("news:collection:rules")

View file

@ -0,0 +1,29 @@
from django.views.generic.edit import CreateView, UpdateView
from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.forms import TwitterTimelineForm
from newsreader.news.collection.views.base import (
CollectionRuleDetailMixin,
CollectionRuleViewMixin,
)
# TODO add tests
class TwitterTimelineCreateView(
CollectionRuleViewMixin, CollectionRuleDetailMixin, CreateView
):
form_class = TwitterTimelineForm
template_name = "news/collection/views/twitter/timeline-create.html"
# TODO add tests
class TwitterTimelineUpdateView(
CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView
):
form_class = TwitterTimelineForm
template_name = "news/collection/views/twitter/timeline-update.html"
context_object_name = "timeline"
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(type=RuleTypeChoices.twitter_timeline)

View file

@ -38,4 +38,5 @@ def parse_opml(file, user, skip_existing=False):
logging.info(f"Skipped due to invalid URL: {e}")
continue
# TODO create feed type rules
yield CollectionRule(url=feed_url, name=name, user=user)