Compare commits

...
4 Commits
Author SHA1 Message Date
imperosol d7e1cdafde add translations 2026-10-25 12:17:55 +01:00
imperosol 93b6238c75 Test ClubNewsView 2026-10-25 12:17:54 +01:00
imperosol 91865fe7a1 add club news view to ClubTabsMixin 2026-10-25 12:17:54 +01:00
imperosol 1e189f7b2b display club past news 2026-10-25 12:17:54 +01:00
5 changed files with 305 additions and 25 deletions

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
{% extends "core/base.jinja" %}
{% block content %}
{% if club.has_rights_in_club(user) %}
<a class="btn btn-blue" href="{{ url("com:news_new") }}">
<i class="fa fa-plus"></i> {% trans %}Create news{% endtrans %}
</a>
{% endif %}
{% for semester_code, semester_events in events %}
<h3>{{ semester_code }}</h3>
{% for event in semester_events %}
<details class="accordion">
<summary>{{ event.title }}</summary>
<div class="accordion-content">
{# Events that occur weekly from the start to the end of the semester
would take a lot of space if shown by default.
Thus, if the event has more than one date, those are
hidden by a details element.
If there is only one date, it is directly displayed. #}
{% set dates = event.dates.all()|list %}
{% if dates|length == 1 %}
<em>
<time datetime="{{ dates[0].start_date.isoformat(timespec="seconds") }}">
{{ dates[0].start_date|localtime|date(DATETIME_FORMAT) }},
{{ dates[0].start_date|localtime|time(DATETIME_FORMAT) }}
</time> -
<time datetime="{{ dates[0].end_date.isoformat(timespec="seconds") }}">
{{ dates[0].end_date|localtime|date(DATETIME_FORMAT) }},
{{ dates[0].end_date|localtime|time(DATETIME_FORMAT) }}
</time>
</em>
{% else %}
<details>
<summary>
{% trans trimmed nb_occurrences=dates|length %}
Dates ({{ nb_occurrences }} occurrences)
{% endtrans %}
</summary>
<ul>
{% for d in dates %}
<li>
<time datetime="{{ d.start_date.isoformat(timespec="seconds") }}">
{{ d.start_date|localtime|date(DATETIME_FORMAT) }},
{{ d.start_date|localtime|time(DATETIME_FORMAT) }}
</time> -
<time datetime="{{ d.end_date.isoformat(timespec="seconds") }}">
{{ d.end_date|localtime|date(DATETIME_FORMAT) }},
{{ d.end_date|localtime|time(DATETIME_FORMAT) }}
</time>
</li>
{% endfor %}
</ul>
</details>
{% endif %}
<h4>{% trans %}Permalink{% endtrans %}</h4>
<p>{{ request.build_absolute_uri(event.get_absolute_url())|urlize }}</p>
<h4>{% trans %}Summary{% endtrans %}</h4>
{% if event.summary %}
{{ event.summary|markdown }}
{% else %}
<p><em>{% trans %}No summary{% endtrans %}</em></p>
{% endif %}
<h4>{% trans %}Description{% endtrans %}</h4>
{% if event.content %}
{{ event.content|markdown }}
{% else %}
<p><em>{% trans %}No detailed description{% endtrans %}</em></p>
{% endif %}
</div>
</details>
{% endfor %}
<br />
{% endfor %}
{% endblock %}
+60
View File
@@ -0,0 +1,60 @@
from datetime import datetime
from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.utils.timezone import get_current_timezone
from model_bakery import baker
from club.models import Club
from club.views import ClubNewsView
from com.models import News, NewsDate
from core.baker_recipes import subscriber_user
from core.models import User
class TestClubNews(TestCase):
@classmethod
def setUpTestData(cls):
cls.club = baker.make(Club)
cls.news = baker.make(
News, club=cls.club, is_published=True, author=baker.make(User), _quantity=5
)
d = datetime(year=2025, month=10, day=1, tzinfo=get_current_timezone())
dates = [
baker.prepare(NewsDate, news=cls.news[0], start_date=d),
baker.prepare(NewsDate, news=cls.news[0], start_date=d.replace(day=8)),
baker.prepare(NewsDate, news=cls.news[0], start_date=d.replace(day=15)),
baker.prepare(NewsDate, news=cls.news[1], start_date=d.replace(month=5)),
baker.prepare(NewsDate, news=cls.news[2], start_date=d.replace(month=4)),
baker.prepare(NewsDate, news=cls.news[3], start_date=d.replace(year=2024)),
baker.prepare(NewsDate, news=cls.news[4], start_date=d.replace(year=2020)),
]
NewsDate.objects.bulk_create(dates)
cls.url = reverse("club:news_list", kwargs={"club_id": cls.club.id})
def test_load_page(self):
"""Just test that the page loads."""
self.client.force_login(subscriber_user.make())
res = self.client.get(self.url)
assert res.status_code == 200
def test_page_data(self):
"""Test that we display the good events."""
# For this test, the object we want to test
# is built with `itertools.groupby`.
# However, it is consumed when rendering the template,
# making it unusable for later testing purposes,
# so we must use lower level django APIs in this test.
request = RequestFactory().get(self.url)
request.user = subscriber_user.make()
response = ClubNewsView.as_view()(request, club_id=self.club.id)
events = [
(semester_code, list(dates))
for semester_code, dates in response.context_data["events"]
]
assert events == [
("A25", [self.news[0]]),
("P25", [self.news[1], self.news[2]]),
("A24", [self.news[3]]),
("A20", [self.news[4]]),
]
+2
View File
@@ -31,6 +31,7 @@ from club.views import (
ClubListView, ClubListView,
ClubMailingView, ClubMailingView,
ClubMembersView, ClubMembersView,
ClubNewsView,
ClubOldMembersView, ClubOldMembersView,
ClubPageEditView, ClubPageEditView,
ClubPageHistView, ClubPageHistView,
@@ -136,4 +137,5 @@ urlpatterns = [
PosterDeleteView.as_view(), PosterDeleteView.as_view(),
name="poster_delete", name="poster_delete",
), ),
path("<int:club_id>/news/", ClubNewsView.as_view(), name="news_list"),
] ]
+35 -1
View File
@@ -37,6 +37,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
from django.core.paginator import InvalidPage, Paginator from django.core.paginator import InvalidPage, Paginator
from django.db.models import F, Prefetch, Q, Sum from django.db.models import F, Prefetch, Q, Sum
from django.db.models.expressions import OuterRef, Subquery
from django.db.models.functions import Length from django.db.models.functions import Length
from django.http import Http404, StreamingHttpResponse from django.http import Http404, StreamingHttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@@ -71,7 +72,7 @@ from club.models import (
MailingSubscription, MailingSubscription,
Membership, Membership,
) )
from com.models import Poster from com.models import NewsDate, Poster
from com.views import ( from com.views import (
PosterCreateBaseView, PosterCreateBaseView,
PosterDeleteBaseView, PosterDeleteBaseView,
@@ -80,6 +81,7 @@ from com.views import (
) )
from core.auth.mixins import CanEditMixin, PermissionOrClubBoardRequiredMixin from core.auth.mixins import CanEditMixin, PermissionOrClubBoardRequiredMixin
from core.models import Page, PageRev from core.models import Page, PageRev
from core.utils import get_semester_code
from core.views import BasePageEditView, DetailFormView, UseFragmentsMixin from core.views import BasePageEditView, DetailFormView, UseFragmentsMixin
from core.views.mixins import ( from core.views.mixins import (
AllowFragment, AllowFragment,
@@ -134,6 +136,11 @@ class ClubTabsMixin(TabedViewMixin):
"slug": "history", "slug": "history",
"name": _("History"), "name": _("History"),
}, },
{
"url": reverse("club:news_list", kwargs={"club_id": self.object.id}),
"slug": "events",
"name": _("Events"),
},
] ]
if self.request.user.can_edit(self.object): if self.request.user.can_edit(self.object):
tab_list.extend( tab_list.extend(
@@ -935,3 +942,30 @@ class PosterDeleteView(ClubTabsMixin, PosterDeleteBaseView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id}) return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id})
class ClubNewsView(PermissionRequiredMixin, ClubTabsMixin, DetailView):
pk_url_kwarg = "club_id"
model = Club
permission_required = "club.view_club"
template_name = "club/club_news_list.jinja"
current_tab = "events"
def get_context_data(self, **kwargs: Any):
events = list(
self.object.news.viewable_by(self.request.user)
.annotate(
first_date=Subquery(
NewsDate.objects.filter(news_id=OuterRef("pk"))
.order_by("start_date")
.values("start_date")[:1]
),
)
.prefetch_related("dates")
.order_by("-first_date")
)
return super().get_context_data(**kwargs) | {
"events": itertools.groupby(
events, key=lambda event: get_semester_code(event.first_date.date())
),
}
+129 -24
View File
@@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 19:45+0200\n" "POT-Creation-Date: 2026-10-25 12:04+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"
@@ -394,7 +394,8 @@ msgstr ""
"de la présidence." "de la présidence."
#: club/models.py core/models.py counter/models.py eboutic/models.py #: club/models.py core/models.py counter/models.py eboutic/models.py
#: election/models.py pedagogy/models.py sas/models.py trombi/models.py #: election/models.py pedagogy/models.py sas/models.py timetable/models.py
#: trombi/models.py
msgid "user" msgid "user"
msgstr "utilisateur" msgstr "utilisateur"
@@ -580,6 +581,7 @@ msgid "Role"
msgstr "Rôle" msgstr "Rôle"
#: club/templates/club/club_members.jinja #: club/templates/club/club_members.jinja
#: club/templates/club/club_news_list.jinja
#: club/templates/club/club_old_members.jinja #: club/templates/club/club_old_members.jinja
#: core/templates/core/group_list.jinja core/templates/core/user_clubs.jinja #: core/templates/core/group_list.jinja core/templates/core/user_clubs.jinja
msgid "Description" msgid "Description"
@@ -594,6 +596,34 @@ msgstr "Depuis"
msgid "There are no members in this club." msgid "There are no members in this club."
msgstr "Il n'y a pas de membres dans ce club." msgstr "Il n'y a pas de membres dans ce club."
#: club/templates/club/club_news_list.jinja
#: com/templates/com/news_admin_list.jinja com/templates/com/news_edit.jinja
#: com/templates/com/news_list.jinja core/templates/core/user_tools.jinja
msgid "Create news"
msgstr "Créer une nouvelle"
#: club/templates/club/club_news_list.jinja
#, python-format
msgid "Dates (%(nb_occurrences)s occurrences)"
msgstr "Dates (%(nb_occurrences)s occurrences)"
#: club/templates/club/club_news_list.jinja
msgid "Permalink"
msgstr "Permalien"
#: club/templates/club/club_news_list.jinja
#: com/templates/com/news_admin_list.jinja
msgid "Summary"
msgstr "Résumé"
#: club/templates/club/club_news_list.jinja
msgid "No summary"
msgstr "Pas de Résumé"
#: club/templates/club/club_news_list.jinja
msgid "No detailed description"
msgstr "Pas de description détaillée"
#: club/templates/club/club_old_members.jinja #: club/templates/club/club_old_members.jinja
msgid "Club old members" msgid "Club old members"
msgstr "Anciens membres du club" msgstr "Anciens membres du club"
@@ -1030,6 +1060,11 @@ msgstr "Anciens membres"
msgid "History" msgid "History"
msgstr "Historique" msgstr "Historique"
#: club/views.py com/templates/com/news_admin_list.jinja
#: core/templates/core/base/navbar.jinja
msgid "Events"
msgstr "Événements"
#: club/views.py core/templates/core/base/header.jinja core/views/user.py #: club/views.py core/templates/core/base/header.jinja core/views/user.py
#: sas/templates/sas/picture.jinja trombi/views.py #: sas/templates/sas/picture.jinja trombi/views.py
msgid "Tools" msgid "Tools"
@@ -1317,11 +1352,6 @@ msgstr "Administration des nouvelles"
msgid "News" msgid "News"
msgstr "Nouvelles" msgstr "Nouvelles"
#: com/templates/com/news_admin_list.jinja com/templates/com/news_edit.jinja
#: com/templates/com/news_list.jinja core/templates/core/user_tools.jinja
msgid "Create news"
msgstr "Créer une nouvelle"
#: com/templates/com/news_admin_list.jinja #: com/templates/com/news_admin_list.jinja
msgid "Weeklies" msgid "Weeklies"
msgstr "Événements hebdomadaires" msgstr "Événements hebdomadaires"
@@ -1336,10 +1366,6 @@ msgstr "Événements hebdomadaires affichées"
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
#: com/templates/com/news_admin_list.jinja
msgid "Summary"
msgstr "Résumé"
#: com/templates/com/news_admin_list.jinja com/templates/com/weekmail.jinja #: com/templates/com/news_admin_list.jinja com/templates/com/weekmail.jinja
#: forum/templates/forum/forum.jinja sas/models.py #: forum/templates/forum/forum.jinja sas/models.py
msgid "Author" msgid "Author"
@@ -1367,11 +1393,6 @@ msgstr "Dépublier"
msgid "Weeklies to moderate" msgid "Weeklies to moderate"
msgstr "Nouvelles hebdomadaires à modérer" msgstr "Nouvelles hebdomadaires à modérer"
#: com/templates/com/news_admin_list.jinja
#: core/templates/core/base/navbar.jinja
msgid "Events"
msgstr "Événements"
#: com/templates/com/news_admin_list.jinja #: com/templates/com/news_admin_list.jinja
msgid "Displayed events" msgid "Displayed events"
msgstr "Événements affichés" msgstr "Événements affichés"
@@ -1869,7 +1890,7 @@ msgstr "département"
msgid "dpt option" msgid "dpt option"
msgstr "Filière" msgstr "Filière"
#: core/models.py pedagogy/models.py #: core/models.py pedagogy/models.py timetable/models.py
msgid "semester" msgid "semester"
msgstr "semestre" msgstr "semestre"
@@ -1994,6 +2015,18 @@ msgstr ""
"Autorise les cotisants (ou les personnes whitelistées, si votre profil est " "Autorise les cotisants (ou les personnes whitelistées, si votre profil est "
"caché) à accéder aux statistiques de votre compte AE" "caché) à accéder aux statistiques de votre compte AE"
#: core/models.py
msgid "show your timetable to others"
msgstr "montrez votre emploi du temps aux autres"
#: core/models.py
msgid ""
"Allow subscribers to see your timetable and to compare it to theirs (if you "
"have filled it)"
msgstr ""
"Permet aux cotisants de voir votre emploi du temps et de le comparer au leur "
"(si vous l'avez enregistré)"
#: core/models.py #: core/models.py
msgid "get a notification for every click" msgid "get a notification for every click"
msgstr "avoir une notification pour chaque click" msgstr "avoir une notification pour chaque click"
@@ -5201,7 +5234,7 @@ msgstr "heures THE"
msgid "hours TE" msgid "hours TE"
msgstr "heures TE" msgstr "heures TE"
#: pedagogy/models.py #: pedagogy/models.py timetable/models.py
msgid "ue" msgid "ue"
msgstr "UE" msgstr "UE"
@@ -5999,6 +6032,46 @@ msgstr "Membre existant"
msgid "the groups that can create subscriptions" msgid "the groups that can create subscriptions"
msgstr "les groupes pouvant créer des cotisations" msgstr "les groupes pouvant créer des cotisations"
#: timetable/models.py
msgid "timetable"
msgstr "emploi du temps"
#: timetable/models.py
msgid "timetables"
msgstr "emplois du temps"
#: timetable/models.py
msgid "weekday"
msgstr "jour de la semaine"
#: timetable/models.py
msgid "start at"
msgstr "début"
#: timetable/models.py
msgid "end at"
msgstr "fin"
#: timetable/models.py
msgid "course type"
msgstr "type de cours"
#: timetable/models.py
msgid "room"
msgstr "salle"
#: timetable/models.py
msgid "week group"
msgstr "groupe de semaine"
#: timetable/models.py
msgid "timetable slot"
msgstr "créneau d'emploi du temps"
#: timetable/models.py
msgid "timetable slots"
msgstr "créneaux d'emploi du temps"
#: timetable/templates/timetable/generator.jinja #: timetable/templates/timetable/generator.jinja
msgid "Timetable generator" msgid "Timetable generator"
msgstr "Générateur d'emploi du temps" msgstr "Générateur d'emploi du temps"
@@ -6029,8 +6102,44 @@ msgid "Generate"
msgstr "Générer" msgstr "Générer"
#: timetable/templates/timetable/generator.jinja #: timetable/templates/timetable/generator.jinja
msgid "Save to PNG" msgid "Save on your profile"
msgstr "Sauver en PNG" msgstr "Enregistrer sur votre profil"
#: timetable/templates/timetable/generator.jinja
msgid "Make this timetable public"
msgstr "Rendre cet emploi du temps publique"
#: timetable/templates/timetable/generator.jinja
#, python-format
msgid ""
"You can change this at any time in <a href=\"%(url)s\">your preferences</a>"
msgstr ""
"Vous pouvez changer ceci n'importe quand dans <a href=\"%(url)s\">vos préférences</a>"
#: timetable/templates/timetable/generator.jinja
msgid "Save timetable"
msgstr "Enregistrer l'emploi du temps"
#: timetable/templates/timetable/generator.jinja
#: trombi/templates/trombi/detail.jinja
msgid "Export"
msgstr "Export"
#: timetable/templates/timetable/generator.jinja
msgid "Export to PNG"
msgstr "Exporter en PNG"
#: timetable/views.py
msgid "Timetables"
msgstr "Emplois du temps"
#: timetable/views.py
msgid "Generator"
msgstr "Générateur"
#: timetable/views.py
msgid "My timetables"
msgstr "Mes emplois du temps"
#: trombi/models.py #: trombi/models.py
msgid "subscription deadline" msgid "subscription deadline"
@@ -6145,10 +6254,6 @@ msgstr "Fin des inscriptions : "
msgid "Comment deadline: " msgid "Comment deadline: "
msgstr "Fin des commentaires : " msgstr "Fin des commentaires : "
#: trombi/templates/trombi/detail.jinja
msgid "Export"
msgstr "Exporter"
#: trombi/templates/trombi/detail.jinja #: trombi/templates/trombi/detail.jinja
msgid "Add user" msgid "Add user"
msgstr "Ajouter une personne" msgstr "Ajouter une personne"