Merge pull request #1008 from ae-utbm/feed

Add atom/rss news feed
This commit is contained in:
Bartuccio Antoine 2025-01-21 00:38:35 +01:00 committed by GitHub
commit c555d5c78c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 6 deletions

View File

@ -36,6 +36,11 @@
&:not(:first-of-type) { &:not(:first-of-type) {
margin: 2em 0 1em 0; margin: 2em 0 1em 0;
} }
.feed {
float: right;
color: #f26522;
}
} }
@media screen and (max-width: $small-devices) { @media screen and (max-width: $small-devices) {

View File

@ -8,6 +8,9 @@
{% block additional_css %} {% block additional_css %}
<link rel="stylesheet" href="{{ static('com/css/news-list.scss') }}"> <link rel="stylesheet" href="{{ static('com/css/news-list.scss') }}">
<link rel="stylesheet" href="{{ static('com/components/ics-calendar.scss') }}"> <link rel="stylesheet" href="{{ static('com/components/ics-calendar.scss') }}">
{# Atom feed discovery, not really css but also goes there #}
<link rel="alternate" type="application/rss+xml" title="{% trans %}News feed{% endtrans %}" href="{{ url("com:news_feed") }}">
{% endblock %} {% endblock %}
{% block additional_js %} {% block additional_js %}
@ -19,7 +22,10 @@
<div id="news"> <div id="news">
<div id="left_column" class="news_column"> <div id="left_column" class="news_column">
{% 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') %} {% 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') %}
<h3>{% trans %}Events today and the next few days{% endtrans %}</h3> <h3>
{% trans %}Events today and the next few days{% endtrans %}
<a target="#" href="{{ url("com:news_feed") }}"><i class="fa fa-rss feed"></i></a>
</h3>
{% if user.is_authenticated and (user.is_com_admin or user.memberships.board().ongoing().exists()) %} {% if user.is_authenticated and (user.is_com_admin or user.memberships.board().ongoing().exists()) %}
<a class="btn btn-blue margin-bottom" href="{{ url("com:news_new") }}"> <a class="btn btn-blue margin-bottom" href="{{ url("com:news_new") }}">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
@ -73,7 +79,10 @@
</div> </div>
{% endif %} {% endif %}
<h3>{% trans %}All coming events{% endtrans %}</h3> <h3>
{% trans %}All coming events{% endtrans %}
<a target="#" href="{{ url("com:news_feed") }}"><i class="fa fa-rss feed"></i></a>
</h3>
<ics-calendar locale="{{ get_language() }}"></ics-calendar> <ics-calendar locale="{{ get_language() }}"></ics-calendar>
</div> </div>

View File

@ -17,6 +17,7 @@ from unittest.mock import patch
import pytest import pytest
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
@ -24,7 +25,7 @@ from django.utils import html
from django.utils.timezone import localtime, now from django.utils.timezone import localtime, now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from model_bakery import baker 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 club.models import Club, Membership
from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle
@ -319,3 +320,15 @@ class TestNewsCreation(TestCase):
self.valid_payload, self.valid_payload,
) )
mocked.assert_called() mocked.assert_called()
@pytest.mark.django_db
def test_feed(client):
"""Smoke test that checks that the atom feed is working"""
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"

View File

@ -25,6 +25,7 @@ from com.views import (
NewsCreateView, NewsCreateView,
NewsDeleteView, NewsDeleteView,
NewsDetailView, NewsDetailView,
NewsFeed,
NewsListView, NewsListView,
NewsModerateView, NewsModerateView,
NewsUpdateView, NewsUpdateView,
@ -73,6 +74,7 @@ urlpatterns = [
name="weekmail_article_edit", name="weekmail_article_edit",
), ),
path("news/", NewsListView.as_view(), name="news_list"), 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/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
path("news/create/", NewsCreateView.as_view(), name="news_new"), path("news/create/", NewsCreateView.as_view(), name="news_new"),
path("news/<int:news_id>/edit/", NewsUpdateView.as_view(), name="news_edit"), path("news/<int:news_id>/edit/", NewsUpdateView.as_view(), name="news_edit"),

View File

@ -26,8 +26,10 @@ from datetime import timedelta
from smtplib import SMTPRecipientsRefused from smtplib import SMTPRecipientsRefused
from typing import Any from typing import Any
from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
from django.contrib.syndication.views import Feed
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Max from django.db.models import Max
from django.forms.models import modelform_factory 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()} 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)),
)
.select_related("news", "news__author")
.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 # Weekmail

View File

@ -92,7 +92,12 @@ class Command(BaseCommand):
raise Exception("Never call this command in prod. Never.") raise Exception("Never call this command in prod. Never.")
Sith.objects.create(weekmail_destinations="etudiants@git.an personnel@git.an") Sith.objects.create(weekmail_destinations="etudiants@git.an personnel@git.an")
Site.objects.create(domain=settings.SITH_URL, name=settings.SITH_NAME)
site = Site.objects.get_current()
site.domain = settings.SITH_URL
site.name = settings.SITH_NAME
site.save()
groups = self._create_groups() groups = self._create_groups()
self._create_ban_groups() self._create_ban_groups()

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "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" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -1447,7 +1447,7 @@ msgid "News admin"
msgstr "Administration des nouvelles" msgstr "Administration des nouvelles"
#: com/templates/com/news_admin_list.jinja com/templates/com/news_detail.jinja #: 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" msgid "News"
msgstr "Nouvelles" msgstr "Nouvelles"
@ -1525,6 +1525,10 @@ msgstr "Éditer (sera soumise de nouveau à la modération)"
msgid "Edit news" msgid "Edit news"
msgstr "Éditer la nouvelle" msgstr "Éditer la nouvelle"
#: com/templates/com/news_list.jinja
msgid "News feed"
msgstr "Flux d'actualités"
#: com/templates/com/news_list.jinja #: com/templates/com/news_list.jinja
msgid "Events today and the next few days" msgid "Events today and the next few days"
msgstr "Événements aujourd'hui et dans les prochains jours" msgstr "Événements aujourd'hui et dans les prochains jours"
@ -1767,6 +1771,10 @@ msgstr "Message d'alerte"
msgid "Screens list" msgid "Screens list"
msgstr "Liste d'écrans" msgstr "Liste d'écrans"
#: com/views.py
msgid "All incoming events"
msgstr "Tous les événements à venir"
#: com/views.py #: com/views.py
msgid "Delete and save to regenerate" msgid "Delete and save to regenerate"
msgstr "Supprimer et sauver pour régénérer" msgstr "Supprimer et sauver pour régénérer"