From dd2cd0a18d09bf1aaa7ab466aa23b2fa488cdcd9 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 19 Jan 2025 18:14:07 +0100 Subject: [PATCH 1/2] Add atom/rss news feed --- com/static/com/css/news-list.scss | 5 +++++ com/templates/com/news_list.jinja | 13 ++++++++++-- com/tests/test_views.py | 8 ++++++++ com/urls.py | 2 ++ com/views.py | 30 ++++++++++++++++++++++++++++ core/management/commands/populate.py | 9 +++++++-- locale/fr/LC_MESSAGES/django.po | 12 +++++++++-- 7 files changed, 73 insertions(+), 6 deletions(-) diff --git a/com/static/com/css/news-list.scss b/com/static/com/css/news-list.scss index bcbf8273..d073c4ac 100644 --- a/com/static/com/css/news-list.scss +++ b/com/static/com/css/news-list.scss @@ -36,6 +36,11 @@ &:not(:first-of-type) { margin: 2em 0 1em 0; } + + .feed { + float: right; + color: #f26522; + } } @media screen and (max-width: $small-devices) { diff --git a/com/templates/com/news_list.jinja b/com/templates/com/news_list.jinja index 168a95b4..0f1f4301 100644 --- a/com/templates/com/news_list.jinja +++ b/com/templates/com/news_list.jinja @@ -8,6 +8,9 @@ {% block additional_css %} + + {# Atom feed discovery, not really css but also goes there #} + {% endblock %} {% block additional_js %} @@ -19,7 +22,10 @@
{% set events_dates = NewsDate.objects.filter(end_date__gte=timezone.now(), start_date__lte=timezone.now()+timedelta(days=5), news__is_moderated=True).datetimes('start_date', 'day') %} -

{% trans %}Events today and the next few days{% endtrans %}

+

+ {% trans %}Events today and the next few days{% endtrans %} + +

{% if user.is_authenticated and (user.is_com_admin or user.memberships.board().ongoing().exists()) %} @@ -73,7 +79,10 @@
{% endif %} -

{% trans %}All coming events{% endtrans %}

+

+ {% trans %}All coming events{% endtrans %} + +

diff --git a/com/tests/test_views.py b/com/tests/test_views.py index 100a83ef..103deaac 100644 --- a/com/tests/test_views.py +++ b/com/tests/test_views.py @@ -319,3 +319,11 @@ class TestNewsCreation(TestCase): self.valid_payload, ) mocked.assert_called() + + +@pytest.mark.django_db +def test_feed(client): + """Smoke test that checks that the atom feed is working""" + resp = client.get(reverse("com:news_feed")) + assert resp.status_code == 200 + assert resp.headers["Content-Type"] == "application/rss+xml; charset=utf-8" diff --git a/com/urls.py b/com/urls.py index 592e653b..8afbfd12 100644 --- a/com/urls.py +++ b/com/urls.py @@ -25,6 +25,7 @@ from com.views import ( NewsCreateView, NewsDeleteView, NewsDetailView, + NewsFeed, NewsListView, NewsModerateView, NewsUpdateView, @@ -73,6 +74,7 @@ urlpatterns = [ name="weekmail_article_edit", ), path("news/", NewsListView.as_view(), name="news_list"), + path("news/feed/", NewsFeed(), name="news_feed"), path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"), path("news/create/", NewsCreateView.as_view(), name="news_new"), path("news//edit/", NewsUpdateView.as_view(), name="news_edit"), diff --git a/com/views.py b/com/views.py index a6faf214..e5ef6227 100644 --- a/com/views.py +++ b/com/views.py @@ -26,8 +26,10 @@ from datetime import timedelta from smtplib import SMTPRecipientsRefused from typing import Any +from dateutil.relativedelta import relativedelta from django.conf import settings from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin +from django.contrib.syndication.views import Feed from django.core.exceptions import PermissionDenied, ValidationError from django.db.models import Max from django.forms.models import modelform_factory @@ -268,6 +270,34 @@ class NewsDetailView(CanViewMixin, DetailView): return super().get_context_data(**kwargs) | {"date": self.object.dates.first()} +class NewsFeed(Feed): + title = _("News") + link = reverse_lazy("com:news_list") + description = _("All incoming events") + + def items(self): + return ( + NewsDate.objects.filter( + news__is_moderated=True, + end_date__gte=timezone.now() - (relativedelta(months=6)), + ) + .prefetch_related("news") + .order_by("-start_date") + ) + + def item_title(self, item: NewsDate): + return item.news.title + + def item_description(self, item: NewsDate): + return item.news.summary + + def item_link(self, item: NewsDate): + return item.news.get_absolute_url() + + def item_author_name(self, item: NewsDate): + return item.news.author.get_display_name() + + # Weekmail diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 4d63bfb9..c8e8d046 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -27,7 +27,7 @@ from typing import ClassVar, NamedTuple from django.conf import settings from django.contrib.auth.models import Permission -from django.contrib.sites.models import Site +from django.contrib.sites.shortcuts import get_current_site from django.core.management import call_command from django.core.management.base import BaseCommand from django.db import connection @@ -92,7 +92,12 @@ class Command(BaseCommand): raise Exception("Never call this command in prod. Never.") Sith.objects.create(weekmail_destinations="etudiants@git.an personnel@git.an") - Site.objects.create(domain=settings.SITH_URL, name=settings.SITH_NAME) + + site = get_current_site(None) + site.domain = settings.SITH_URL + site.name = settings.SITH_NAME + site.save() + groups = self._create_groups() self._create_ban_groups() diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 07eede14..dc6b5ee6 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-10 14:52+0100\n" +"POT-Creation-Date: 2025-01-19 18:12+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -1447,7 +1447,7 @@ msgid "News admin" msgstr "Administration des nouvelles" #: com/templates/com/news_admin_list.jinja com/templates/com/news_detail.jinja -#: com/templates/com/news_list.jinja +#: com/templates/com/news_list.jinja com/views.py msgid "News" msgstr "Nouvelles" @@ -1525,6 +1525,10 @@ msgstr "Éditer (sera soumise de nouveau à la modération)" msgid "Edit news" msgstr "Éditer la nouvelle" +#: com/templates/com/news_list.jinja +msgid "News feed" +msgstr "Flux d'actualités" + #: com/templates/com/news_list.jinja msgid "Events today and the next few days" msgstr "Événements aujourd'hui et dans les prochains jours" @@ -1767,6 +1771,10 @@ msgstr "Message d'alerte" msgid "Screens list" msgstr "Liste d'écrans" +#: com/views.py +msgid "All incoming events" +msgstr "Tous les événements à venir" + #: com/views.py msgid "Delete and save to regenerate" msgstr "Supprimer et sauver pour régénérer" From 5db98195601b36db50b0510b3e42d3fcde41ded0 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 21 Jan 2025 00:08:52 +0100 Subject: [PATCH 2/2] Address review comments --- com/tests/test_views.py | 13 +++++++++---- com/views.py | 2 +- core/management/commands/populate.py | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/com/tests/test_views.py b/com/tests/test_views.py index 103deaac..f80839ab 100644 --- a/com/tests/test_views.py +++ b/com/tests/test_views.py @@ -17,6 +17,7 @@ from unittest.mock import patch import pytest from django.conf import settings +from django.contrib.sites.models import Site from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.urls import reverse @@ -24,7 +25,7 @@ from django.utils import html from django.utils.timezone import localtime, now from django.utils.translation import gettext as _ from model_bakery import baker -from pytest_django.asserts import assertRedirects +from pytest_django.asserts import assertNumQueries, assertRedirects from club.models import Club, Membership from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle @@ -324,6 +325,10 @@ class TestNewsCreation(TestCase): @pytest.mark.django_db def test_feed(client): """Smoke test that checks that the atom feed is working""" - resp = client.get(reverse("com:news_feed")) - assert resp.status_code == 200 - assert resp.headers["Content-Type"] == "application/rss+xml; charset=utf-8" + Site.objects.clear_cache() + with assertNumQueries(2): + # get sith domain with Site api: 1 request + # get all news and related info: 1 request + resp = client.get(reverse("com:news_feed")) + assert resp.status_code == 200 + assert resp.headers["Content-Type"] == "application/rss+xml; charset=utf-8" diff --git a/com/views.py b/com/views.py index e5ef6227..0ab8fc1c 100644 --- a/com/views.py +++ b/com/views.py @@ -281,7 +281,7 @@ class NewsFeed(Feed): news__is_moderated=True, end_date__gte=timezone.now() - (relativedelta(months=6)), ) - .prefetch_related("news") + .select_related("news", "news__author") .order_by("-start_date") ) diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index c8e8d046..26bc6074 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -27,7 +27,7 @@ from typing import ClassVar, NamedTuple from django.conf import settings from django.contrib.auth.models import Permission -from django.contrib.sites.shortcuts import get_current_site +from django.contrib.sites.models import Site from django.core.management import call_command from django.core.management.base import BaseCommand from django.db import connection @@ -93,7 +93,7 @@ class Command(BaseCommand): Sith.objects.create(weekmail_destinations="etudiants@git.an personnel@git.an") - site = get_current_site(None) + site = Site.objects.get_current() site.domain = settings.SITH_URL site.name = settings.SITH_NAME site.save()