From 65b14d01f6966525862d8e7cdb6324b1117bbf0a Mon Sep 17 00:00:00 2001 From: imperosol Date: Mon, 21 Sep 2026 13:18:48 +0200 Subject: [PATCH] make existing users approve CGU after login --- core/templates/core/cgu_approve.jinja | 39 ++++++ core/urls.py | 2 + core/views/forms.py | 56 +++++--- core/views/user.py | 36 ++++- locale/fr/LC_MESSAGES/django.po | 184 +++++++++++++++----------- 5 files changed, 220 insertions(+), 97 deletions(-) create mode 100644 core/templates/core/cgu_approve.jinja diff --git a/core/templates/core/cgu_approve.jinja b/core/templates/core/cgu_approve.jinja new file mode 100644 index 00000000..a152ef27 --- /dev/null +++ b/core/templates/core/cgu_approve.jinja @@ -0,0 +1,39 @@ +{% extends "core/base.jinja" %} + +{%- block additional_css -%} + +{%- endblock -%} + +{% block title %} + {% trans %}Login{% endtrans %} +{% endblock %} + +{% block info_boxes %} +{% endblock %} + +{% block nav %} +{% endblock %} + +{% block content %} +

{% trans %}Terms of Service{% endtrans %}

+ +
+ {% csrf_token %} + +
+ {% trans trimmed %} + To continue using our services, + please read and approve the AE website's terms of service + {% endtrans %} +
+
+ {{ form.cgu_approved.errors }} + {{ form.cgu_approved }} + {{ form.cgu_approved.label_tag() }} +
+ + + +
+{% endblock %} + diff --git a/core/urls.py b/core/urls.py index d53697a9..0dccdc2e 100644 --- a/core/urls.py +++ b/core/urls.py @@ -31,6 +31,7 @@ from core.converters import ( TwoDigitMonthConverter, ) from core.views import ( + CGUApprovalView, FileDeleteView, FileEditPropView, FileEditView, @@ -125,6 +126,7 @@ urlpatterns = [ name="password_reset_complete", ), path("register/", UserCreationView.as_view(), name="register"), + path("cgu/", CGUApprovalView.as_view(), name="approve_cgu"), # Group handling path("group/", GroupListView.as_view(), name="group_list"), path("group/new/", GroupCreateView.as_view(), name="group_new"), diff --git a/core/views/forms.py b/core/views/forms.py index c10a7da6..40afa426 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -30,9 +30,7 @@ from django import forms from django.conf import settings from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.contrib.auth.models import Permission -from django.contrib.staticfiles.management.commands.collectstatic import ( - staticfiles_storage, -) +from django.contrib.staticfiles.storage import staticfiles_storage from django.core.exceptions import ValidationError from django.db import transaction from django.forms import ( @@ -43,6 +41,7 @@ from django.forms import ( Widget, ) from django.urls import reverse +from django.utils.functional import lazy from django.utils.safestring import mark_safe from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ @@ -110,6 +109,34 @@ class FutureDateTimeField(forms.DateTimeField): return {"min": widget.format_value(now())} +class CGUApprovalField(forms.BooleanField): + cgu_file_id = settings.SITH_CGU_FILE_ID + default_error_messages = {"required": _("You must approve the terms of service.")} + __label = None + + def __init__(self, *, label_suffix: str | None = "", **kwargs): + # Because the core app of the sith is so huge, + # and because we require a url from the latter, + # putting the reverse into the __init__ will result in it + # being evaluated at server startup time (even with reverse_lazy). + # This will result in a circular import. + # Thus, we must keep the label in its own property and force it to be lazy. + kwargs["label"] = lazy(self.get_label, str) + kwargs["required"] = True + super().__init__(label_suffix=label_suffix, **kwargs) + + def get_label(self): + if not self.__label: + self.__label = mark_safe( + _( + "I have read and I approve the " + 'Terms of Service' + ) + % {"url": reverse("core:page", kwargs={"page_name": self.cgu_file_id})} + ) + return self.__label + + # Forms @@ -149,23 +176,14 @@ class RegisteringForm(UserCreationForm): class Meta: model = User fields = ("first_name", "last_name", "email", "cgu_approved") - field_classes = {"email": AntiSpamEmailField} + field_classes = {"email": AntiSpamEmailField, "cgu_approved": CGUApprovalField} - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["cgu_approved"].required = True - self.fields["cgu_approved"].label_suffix = "" - self.fields["cgu_approved"].label = mark_safe( - _( - "I have read and I approve the " - 'End User License Agreement' - ) - % { - "url": reverse( - "core:download", kwargs={"file_id": settings.SITH_CGU_FILE_ID} - ) - } - ) + +class CGUApprovalForm(forms.ModelForm): + class Meta: + model = User + fields = ["cgu_approved"] + field_classes = {"cgu_approved": CGUApprovalField} class UserProfileForm(forms.ModelForm): diff --git a/core/views/user.py b/core/views/user.py index 31462551..4e16bf64 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -27,12 +27,12 @@ from datetime import timedelta # This file contains all the views that concern the user model from operator import itemgetter from smtplib import SMTPException -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from django.contrib import messages from django.contrib.auth import login, views from django.contrib.auth.decorators import login_required -from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm +from django.contrib.auth.forms import SetPasswordForm from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied @@ -60,6 +60,7 @@ from honeypot.decorators import check_honeypot from core.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin from core.models import Gift, Preferences, User from core.views.forms import ( + CGUApprovalForm, GiftForm, LoginForm, RegisteringForm, @@ -71,6 +72,7 @@ from core.views.forms import ( from core.views.mixins import FragmentMixin, TabedViewMixin, UseFragmentsMixin from counter.models import Refilling, Selling from eboutic.models import Invoice +from sith import settings from trombi.views import UserTrombiForm if TYPE_CHECKING: @@ -82,9 +84,16 @@ class SithLoginView(views.LoginView): template_name = "core/login.jinja" authentication_form = LoginForm - form_class = PasswordChangeForm redirect_authenticated_user = True + def get_success_url(self) -> str: + redirect_to = self.get_redirect_url() + default_url = self.get_default_redirect_url() + if not self.request.user.cgu_approved: + query = {"next": redirect_to} if redirect_to else {} + return reverse("core:approve_cgu", query=query) + return redirect_to or default_url + class SithPasswordChangeView(views.PasswordChangeView): """Allows a user to change its password.""" @@ -188,6 +197,27 @@ class UserCreationView(FormView): return super().form_valid(form) +class CGUApprovalView(views.RedirectURLMixin, UpdateView): + form_class = CGUApprovalForm + next_page = settings.LOGIN_REDIRECT_URL + template_name = "core/cgu_approve.jinja" + + def dispatch(self, request, *args, **kwargs): + if self.request.user.is_anonymous: + return redirect("core:login") + if self.request.user.cgu_approved: + return redirect(self.get_success_url()) + return super().dispatch(request, *args, **kwargs) + + def get_object(self, *args, **kwargs): + return self.request.user + + def get_context_data(self, **kwargs) -> dict[str, Any]: + return super().get_context_data(**kwargs) | { + self.redirect_field_name: self.get_redirect_url() + } + + class UserMeRedirect(LoginRequiredMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): if remaining := kwargs.get("remaining_path"): diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 1ed6df60..31520888 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: 2026-09-15 23:49+0200\n" +"POT-Creation-Date: 2026-09-21 13:16+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -736,8 +736,9 @@ msgstr "Méthode de paiement" #: counter/templates/counter/fragments/create_student_card.jinja #: counter/templates/counter/last_ops.jinja #: election/templates/election/election_detail.jinja -#: forum/templates/forum/macros.jinja pedagogy/templates/pedagogy/guide.jinja -#: pedagogy/templates/pedagogy/ue_detail.jinja sas/templates/sas/album.jinja +#: forum/templates/forum/macros.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/guide.jinja sas/templates/sas/album.jinja #: sas/templates/sas/moderation.jinja sas/templates/sas/picture.jinja #: trombi/templates/trombi/detail.jinja #: trombi/templates/trombi/edit_profile.jinja @@ -931,8 +932,9 @@ msgstr "Outils" #: counter/templates/counter/cash_summary_list.jinja #: counter/templates/counter/counter_list.jinja #: election/templates/election/election_detail.jinja -#: forum/templates/forum/macros.jinja pedagogy/templates/pedagogy/guide.jinja -#: pedagogy/templates/pedagogy/ue_detail.jinja sas/templates/sas/album.jinja +#: forum/templates/forum/macros.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/guide.jinja sas/templates/sas/album.jinja #: trombi/templates/trombi/detail.jinja #: trombi/templates/trombi/edit_profile.jinja msgid "Edit" @@ -2113,7 +2115,8 @@ msgstr "R&D" msgid "Site created by the IT Department of the AE" msgstr "Site réalisé par le Pôle Informatique de l'AE" -#: core/templates/core/base/header.jinja core/templates/core/login.jinja +#: core/templates/core/base/header.jinja core/templates/core/cgu_approve.jinja +#: core/templates/core/login.jinja #: core/templates/core/password_reset_complete.jinja msgid "Login" msgstr "Connexion" @@ -2211,6 +2214,18 @@ msgstr "FAQ" msgid "Wiki" msgstr "Wiki" +#: core/templates/core/cgu_approve.jinja +msgid "Terms of Service" +msgstr "Conditions générales d'utilisation" + +#: core/templates/core/cgu_approve.jinja +msgid "" +"To continue using our services, please read and approve the AE website's " +"terms of service" +msgstr "" +"Pour continuer à utiliser nos services, veuillez lire et approuver les " +"conditions générales d'utilisation du site AE." + #: core/templates/core/create.jinja #, python-format msgid "Create %(name)s" @@ -2237,6 +2252,7 @@ msgstr "Confirmation" #: core/templates/core/file_delete_confirm.jinja #: counter/templates/counter/counter_click.jinja #: counter/templates/counter/fragments/delete_student_card.jinja +#: pedagogy/templates/pedagogy/fragments/comment_report.jinja #: sas/templates/sas/ask_picture_removal.jinja msgid "Cancel" msgstr "Annuler" @@ -2263,6 +2279,7 @@ msgstr "Propriétés" #: core/templates/core/file_delete_confirm.jinja #: counter/templates/counter/fragments/delete_student_card.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja #, python-format msgid "Are you sure you want to delete \"%(obj)s\"?" msgstr "Êtes-vous sûr de vouloir supprimer \"%(obj)s\" ?" @@ -3138,18 +3155,22 @@ msgid "Ensure this timestamp is set in the future" msgstr "Assurez-vous que cet horodatage est dans le futur" #: core/views/forms.py -msgid "Username, email, or account number" -msgstr "Nom d'utilisateur, email, ou numéro de compte AE" +msgid "You must approve the terms of service." +msgstr "Vous devez approuver les conditions générales d'utilisation" #: core/views/forms.py #, python-format msgid "" -"I have read and I approve the End User " -"License Agreement" +"I have read and I approve the Terms of " +"Service" msgstr "" "J'ai lu et j'approuve les Conditions " "Générales d'utilisation" +#: core/views/forms.py +msgid "Username, email, or account number" +msgstr "Nom d'utilisateur, email, ou numéro de compte AE" + #: core/views/forms.py msgid "" "Profile: you need to be visible on the picture, in order to be recognized " @@ -5122,6 +5143,80 @@ msgstr "signaler" msgid "reporter" msgstr "signalant" +#: pedagogy/templates/pedagogy/fragments/comment_report.jinja +msgid "Report" +msgstr "Signaler" + +#: pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja +msgid "Leave comment" +msgstr "Laisser un commentaire" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: trombi/templates/trombi/export.jinja +msgid "Comments" +msgstr "Commentaires" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Global grade" +msgstr "Note globale" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Utility" +msgstr "Utilité" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Interest" +msgstr "Intérêt" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Teaching" +msgstr "Enseignement" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Work load" +msgstr "Charge de travail" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +msgid "This comment has been reported" +msgstr "Ce commentaire a été signalé" + +#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja +msgid "Report this comment" +msgstr "Signaler ce commentaire" + +#: pedagogy/templates/pedagogy/fragments/ue_details/form.jinja +msgid "" +"You already posted a comment on this UE. If you want to comment again, " +"please modify or delete your previous comment." +msgstr "" +"Vous avez déjà commenté cette UE. Si vous voulez de nouveau commenter, " +"veuillez modifier ou supprimer votre commentaire précédent." + +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Objectives" +msgstr "Objectifs" + +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Program" +msgstr "Programme" + +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Earned skills" +msgstr "Compétences acquises" + +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "Key concepts" +msgstr "Concepts clefs" + +#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja +msgid "UE manager: " +msgstr "Gestionnaire d'UE : " + #: pedagogy/templates/pedagogy/guide.jinja msgid "A guide of courses available at UTBM." msgstr "Un guide de tous les cours disponibles à l'UTBM." @@ -5130,6 +5225,10 @@ msgstr "Un guide de tous les cours disponibles à l'UTBM." msgid "Search UE" msgstr "Recherche d'UE" +#: pedagogy/templates/pedagogy/guide.jinja +msgid "Hide closed UEs" +msgstr "Cacher les UEs fermées" + #: pedagogy/templates/pedagogy/guide.jinja #, python-format msgid "%(display_name)s" @@ -5202,71 +5301,6 @@ msgstr "TE : " msgid "THE: " msgstr "THE : " -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Global grade" -msgstr "Note globale" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Utility" -msgstr "Utilité" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Interest" -msgstr "Intérêt" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Teaching" -msgstr "Enseignement" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Work load" -msgstr "Charge de travail" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Objectives" -msgstr "Objectifs" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Program" -msgstr "Programme" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Earned skills" -msgstr "Compétences acquises" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Key concepts" -msgstr "Concepts clefs" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "UE manager: " -msgstr "Gestionnaire d'UE : " - -#: pedagogy/templates/pedagogy/ue_detail.jinja pedagogy/tests/tests.py -msgid "" -"You already posted a comment on this UE. If you want to comment again, " -"please modify or delete your previous comment." -msgstr "" -"Vous avez déjà commenté cette UE. Si vous voulez de nouveau commenter, " -"veuillez modifier ou supprimer votre commentaire précédent." - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Leave comment" -msgstr "Laisser un commentaire" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -#: trombi/templates/trombi/export.jinja -msgid "Comments" -msgstr "Commentaires" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "This comment has been reported" -msgstr "Ce commentaire a été signalé" - -#: pedagogy/templates/pedagogy/ue_detail.jinja -msgid "Report this comment" -msgstr "Signaler ce commentaire" - #: pedagogy/templates/pedagogy/ue_edit.jinja msgid "Edit UE" msgstr "Éditer l'UE"