From 833f9147ce42d10684d5e66455224358e31f1a67 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 11:33:12 +0100 Subject: [PATCH 1/3] Prevent errors when a not authenticated user tries to see a user detail page --- apps/registration/tests.py | 4 ++++ apps/registration/views.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/apps/registration/tests.py b/apps/registration/tests.py index f732f16..00cc564 100644 --- a/apps/registration/tests.py +++ b/apps/registration/tests.py @@ -31,6 +31,10 @@ class TestIndexPage(TestCase): response = self.client.get(reverse("registration:reset_admin")) self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:reset_admin"), 302, 200) + User.objects.create() + response = self.client.get(reverse("registration:user_detail", args=(1,))) + self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:user_detail", args=(1,))) + class TestRegistration(TestCase): def setUp(self) -> None: diff --git a/apps/registration/views.py b/apps/registration/views.py index 7fe6707..cf266a8 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -158,6 +158,8 @@ class UserDetailView(LoginRequiredMixin, DetailView): def dispatch(self, request, *args, **kwargs): user = request.user + if not user.is_authenticated: + return self.handle_no_permission() # Only an admin or the concerned user can see the information if not user.registration.is_admin and user.pk != kwargs["pk"]: raise PermissionDenied From 10115a0419d4bde6c602c60dc23a7053b0ecb9ea Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 12:31:00 +0100 Subject: [PATCH 2/3] Administrators can update the team of the email validation status of a team. --- apps/registration/forms.py | 6 +++--- apps/registration/tests.py | 4 ++++ apps/registration/views.py | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/registration/forms.py b/apps/registration/forms.py index b533e73..ecb2b9f 100644 --- a/apps/registration/forms.py +++ b/apps/registration/forms.py @@ -64,7 +64,7 @@ class StudentRegistrationForm(forms.ModelForm): """ class Meta: model = StudentRegistration - fields = ('student_class', 'school', 'give_contact_to_animath',) + fields = ('team', 'student_class', 'school', 'give_contact_to_animath', 'email_confirmed',) class PhotoAuthorizationForm(forms.ModelForm): @@ -92,7 +92,7 @@ class CoachRegistrationForm(forms.ModelForm): """ class Meta: model = CoachRegistration - fields = ('professional_activity', 'give_contact_to_animath',) + fields = ('team', 'professional_activity', 'give_contact_to_animath', 'email_confirmed',) class AdminRegistrationForm(forms.ModelForm): @@ -101,4 +101,4 @@ class AdminRegistrationForm(forms.ModelForm): """ class Meta: model = AdminRegistration - fields = ('role', 'give_contact_to_animath',) + fields = ('role', 'give_contact_to_animath', 'email_confirmed',) diff --git a/apps/registration/tests.py b/apps/registration/tests.py index 00cc564..ffcfc3e 100644 --- a/apps/registration/tests.py +++ b/apps/registration/tests.py @@ -226,6 +226,8 @@ class TestRegistration(TestCase): last_name="Name", email="new_" + user.email, give_contact_to_animath=True, + email_confirmed=True, + team_id="", )) self.assertEqual(response.status_code, 200) @@ -234,6 +236,8 @@ class TestRegistration(TestCase): last_name="Name", email="new_" + user.email, give_contact_to_animath=True, + email_confirmed=True, + team_id="", ) response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=data) self.assertRedirects(response, reverse("registration:user_detail", args=(user.pk,)), 302, 200) diff --git a/apps/registration/views.py b/apps/registration/views.py index cf266a8..5c8368e 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -43,6 +43,11 @@ class SignupView(CreateView): context["student_registration_form"] = StudentRegistrationForm(self.request.POST or None) context["coach_registration_form"] = CoachRegistrationForm(self.request.POST or None) + del context["student_registration_form"].fields["team"] + del context["student_registration_form"].fields["email_confirmed"] + del context["coach_registration_form"].fields["team"] + del context["coach_registration_form"].fields["email_confirmed"] + return context @transaction.atomic @@ -52,6 +57,8 @@ class SignupView(CreateView): registration_form = StudentRegistrationForm(self.request.POST) else: registration_form = CoachRegistrationForm(self.request.POST) + del registration_form.fields["team"] + del registration_form.fields["email_confirmed"] if not registration_form.is_valid(): return self.form_invalid(form) @@ -191,6 +198,10 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): context["title"] = _("Update user {user}").format(user=str(self.object.registration)) context["registration_form"] = user.registration.form_class(data=self.request.POST or None, instance=self.object.registration) + if not user.registration.is_admin: + if "team" in context["registration_form"].fields: + del context["registration_form"].fields["team"] + del context["registration_form"].fields["email_confirmed"] return context @transaction.atomic @@ -198,6 +209,11 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): user = form.instance registration_form = user.registration.form_class(data=self.request.POST or None, instance=self.object.registration) + if not user.registration.is_admin: + if "team" in registration_form.fields: + del registration_form.fields["team"] + del registration_form.fields["email_confirmed"] + if not registration_form.is_valid(): return self.form_invalid(form) From dbcf15c4f3159004879dc47e1966f0dcfba1629b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 14:06:08 +0100 Subject: [PATCH 3/3] Display user list --- .../templates/registration/user_list.html | 7 + apps/registration/tests.py | 7 + apps/registration/urls.py | 3 +- apps/registration/views.py | 15 +- corres2math/templates/base.html | 3 + locale/fr/LC_MESSAGES/django.po | 156 +++++++++--------- 6 files changed, 113 insertions(+), 78 deletions(-) create mode 100644 apps/registration/templates/registration/user_list.html diff --git a/apps/registration/templates/registration/user_list.html b/apps/registration/templates/registration/user_list.html new file mode 100644 index 0000000..1c0d551 --- /dev/null +++ b/apps/registration/templates/registration/user_list.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% load django_tables2 %} + +{% block content %} + {% render_table table %} +{% endblock %} diff --git a/apps/registration/tests.py b/apps/registration/tests.py index ffcfc3e..f33dc97 100644 --- a/apps/registration/tests.py +++ b/apps/registration/tests.py @@ -203,6 +203,13 @@ class TestRegistration(TestCase): response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,))) self.assertEqual(response.status_code, 200) + def test_user_list(self): + """ + Display the list of all users. + """ + response = self.client.get(reverse("registration:user_list")) + self.assertEqual(response.status_code, 200) + def test_update_user(self): """ Update the user information, for each type of user. diff --git a/apps/registration/urls.py b/apps/registration/urls.py index bce8aa8..804036c 100644 --- a/apps/registration/urls.py +++ b/apps/registration/urls.py @@ -1,7 +1,7 @@ from django.urls import path from .views import MyAccountDetailView, ResetAdminView, SignupView, UserDetailView, UserImpersonateView, \ - UserResendValidationEmailView, UserUpdateView, UserUploadPhotoAuthorizationView, UserValidateView, \ + UserListView, UserResendValidationEmailView, UserUpdateView, UserUploadPhotoAuthorizationView, UserValidateView, \ UserValidationEmailSentView app_name = "registration" @@ -18,5 +18,6 @@ urlpatterns = [ path("user//upload-photo-authorization/", UserUploadPhotoAuthorizationView.as_view(), name="upload_user_photo_authorization"), path("user//impersonate/", UserImpersonateView.as_view(), name="user_impersonate"), + path("user/list/", UserListView.as_view(), name="user_list"), path("reset-admin/", ResetAdminView.as_view(), name="reset_admin"), ] diff --git a/apps/registration/views.py b/apps/registration/views.py index 5c8368e..6247e09 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -1,6 +1,9 @@ import os +from django_tables2 import SingleTableView + from corres2math.tokens import email_validation_token +from corres2math.views import AdminMixin from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User @@ -16,7 +19,8 @@ from magic import Magic from participation.models import Phase from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm -from .models import StudentRegistration +from .models import StudentRegistration, Registration +from .tables import RegistrationTable class SignupView(CreateView): @@ -178,6 +182,15 @@ class UserDetailView(LoginRequiredMixin, DetailView): return context +class UserListView(AdminMixin, SingleTableView): + """ + Display the list of all registered users. + """ + model = Registration + table_class = RegistrationTable + template_name = "registration/user_list.html" + + class UserUpdateView(LoginRequiredMixin, UpdateView): """ Update the detail about a user and its registration. diff --git a/corres2math/templates/base.html b/corres2math/templates/base.html index 9a6611e..a06cf17 100644 --- a/corres2math/templates/base.html +++ b/corres2math/templates/base.html @@ -71,6 +71,9 @@ {% endif %} {% if user.is_authenticated and user.registration.is_admin %} + diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index eb82b5d..05dfbc2 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Corres2math\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-12-04 01:42+0100\n" +"POT-Creation-Date: 2020-12-11 14:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Yohann D'ANELLO \n" "Language-Team: LANGUAGE \n" @@ -99,13 +99,13 @@ msgstr "changelogs" msgid "Changelog of type \"{action}\" for model {model} at {timestamp}" msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" -#: apps/participation/admin.py:16 apps/participation/models.py:121 +#: apps/participation/admin.py:16 apps/participation/models.py:132 #: apps/participation/tables.py:35 apps/participation/tables.py:62 msgid "problem number" msgstr "numéro de problème" -#: apps/participation/admin.py:21 apps/participation/models.py:127 -#: apps/participation/models.py:181 +#: apps/participation/admin.py:21 apps/participation/models.py:138 +#: apps/participation/models.py:192 msgid "valid" msgstr "valide" @@ -184,96 +184,96 @@ msgstr "" "Donner l'autorisation de publier la vidéo sur le site principal pour " "promouvoir les Correspondances." -#: apps/participation/models.py:96 +#: apps/participation/models.py:107 #, python-brace-format msgid "Team {name} ({trigram})" msgstr "Équipe {name} ({trigram})" -#: apps/participation/models.py:99 apps/participation/models.py:114 +#: apps/participation/models.py:110 apps/participation/models.py:125 #: apps/registration/models.py:106 apps/registration/models.py:155 msgid "team" msgstr "équipe" -#: apps/participation/models.py:100 +#: apps/participation/models.py:111 msgid "teams" msgstr "équipes" -#: apps/participation/models.py:118 +#: apps/participation/models.py:129 #, python-brace-format msgid "Problem #{problem:d}" msgstr "Problème n°{problem:d}" -#: apps/participation/models.py:128 apps/participation/models.py:182 +#: apps/participation/models.py:139 apps/participation/models.py:193 msgid "The video got the validation of the administrators." msgstr "La vidéo a été validée par les administrateurs." -#: apps/participation/models.py:137 +#: apps/participation/models.py:148 msgid "solution video" msgstr "vidéo de solution" -#: apps/participation/models.py:146 +#: apps/participation/models.py:157 msgid "received participation" msgstr "participation reçue" -#: apps/participation/models.py:155 +#: apps/participation/models.py:166 msgid "synthesis video" msgstr "vidéo de synthèse" -#: apps/participation/models.py:162 +#: apps/participation/models.py:173 #, python-brace-format msgid "Participation of the team {name} ({trigram})" msgstr "Participation de l'équipe {name} ({trigram})" -#: apps/participation/models.py:165 apps/participation/models.py:239 +#: apps/participation/models.py:176 apps/participation/models.py:250 msgid "participation" msgstr "participation" -#: apps/participation/models.py:166 +#: apps/participation/models.py:177 msgid "participations" msgstr "participations" -#: apps/participation/models.py:174 +#: apps/participation/models.py:185 msgid "link" msgstr "lien" -#: apps/participation/models.py:175 +#: apps/participation/models.py:186 msgid "The full video link." msgstr "Le lien complet de la vidéo." -#: apps/participation/models.py:224 +#: apps/participation/models.py:235 #, python-brace-format msgid "Video of team {name} ({trigram})" msgstr "Vidéo de l'équipe {name} ({trigram})" -#: apps/participation/models.py:228 +#: apps/participation/models.py:239 msgid "video" msgstr "vidéo" -#: apps/participation/models.py:229 +#: apps/participation/models.py:240 msgid "videos" msgstr "vidéos" -#: apps/participation/models.py:244 +#: apps/participation/models.py:255 msgid "question" msgstr "question" -#: apps/participation/models.py:258 +#: apps/participation/models.py:269 msgid "phase number" msgstr "phase" -#: apps/participation/models.py:263 +#: apps/participation/models.py:274 msgid "phase description" msgstr "description" -#: apps/participation/models.py:267 +#: apps/participation/models.py:278 msgid "start date of the given phase" msgstr "début de la phase" -#: apps/participation/models.py:272 +#: apps/participation/models.py:283 msgid "end date of the given phase" msgstr "fin de la phase" -#: apps/participation/models.py:290 +#: apps/participation/models.py:299 msgid "" "Phase {phase_number:d} starts on {start:%Y-%m-%d %H:%M} and ends on {end:%Y-" "%m-%d %H:%M}" @@ -281,11 +281,11 @@ msgstr "" "Phase {phase_number:d} démarrant le {start:%d/%m/%Y %H:%M} et finissant le " "{end:%d/%m/%Y %H:%M}" -#: apps/participation/models.py:294 +#: apps/participation/models.py:303 msgid "phase" msgstr "phase" -#: apps/participation/models.py:295 +#: apps/participation/models.py:304 msgid "phases" msgstr "phases" @@ -324,12 +324,12 @@ msgstr "" "contacter :)" #: apps/participation/templates/participation/create_team.html:11 -#: corres2math/templates/base.html:237 +#: corres2math/templates/base.html:242 msgid "Create" msgstr "Créer" #: apps/participation/templates/participation/join_team.html:11 -#: corres2math/templates/base.html:232 +#: corres2math/templates/base.html:237 msgid "Join" msgstr "Rejoindre" @@ -503,7 +503,7 @@ msgstr "Définir l'équipe qui recevra votre vidéo" #: apps/participation/templates/participation/participation_detail.html:181 #: apps/participation/templates/participation/participation_detail.html:233 -#: apps/participation/views.py:494 +#: apps/participation/views.py:499 msgid "Upload video" msgstr "Envoyer la vidéo" @@ -538,7 +538,7 @@ msgid "Update question" msgstr "Modifier la question" #: apps/participation/templates/participation/participation_detail.html:217 -#: apps/participation/views.py:470 +#: apps/participation/views.py:475 msgid "Delete question" msgstr "Supprimer la question" @@ -548,8 +548,8 @@ msgid "Display synthesis" msgstr "Afficher la synthèse" #: apps/participation/templates/participation/phase_list.html:10 -#: apps/participation/views.py:513 corres2math/templates/base.html:68 -#: corres2math/templates/base.html:70 corres2math/templates/base.html:221 +#: apps/participation/views.py:518 corres2math/templates/base.html:68 +#: corres2math/templates/base.html:70 corres2math/templates/base.html:226 msgid "Calendar" msgstr "Calendrier" @@ -661,7 +661,7 @@ msgid "Update team" msgstr "Modifier l'équipe" #: apps/participation/templates/participation/team_detail.html:127 -#: apps/participation/views.py:323 +#: apps/participation/views.py:328 msgid "Leave team" msgstr "Quitter l'équipe" @@ -670,12 +670,12 @@ msgid "Are you sure that you want to leave this team?" msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?" #: apps/participation/templates/participation/team_list.html:6 -#: corres2math/templates/base.html:225 +#: corres2math/templates/base.html:230 msgid "All teams" msgstr "Toutes les équipes" -#: apps/participation/views.py:36 corres2math/templates/base.html:81 -#: corres2math/templates/base.html:236 +#: apps/participation/views.py:36 corres2math/templates/base.html:84 +#: corres2math/templates/base.html:241 msgid "Create team" msgstr "Créer une équipe" @@ -687,17 +687,17 @@ msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." msgid "You are already in a team." msgstr "Vous êtes déjà dans une équipe." -#: apps/participation/views.py:82 corres2math/templates/base.html:86 -#: corres2math/templates/base.html:231 +#: apps/participation/views.py:82 corres2math/templates/base.html:89 +#: corres2math/templates/base.html:236 msgid "Join team" msgstr "Rejoindre une équipe" -#: apps/participation/views.py:142 apps/participation/views.py:329 -#: apps/participation/views.py:362 +#: apps/participation/views.py:142 apps/participation/views.py:334 +#: apps/participation/views.py:367 msgid "You are not in a team." msgstr "Vous n'êtes pas dans une équipe." -#: apps/participation/views.py:143 apps/participation/views.py:363 +#: apps/participation/views.py:143 apps/participation/views.py:368 msgid "You don't participate, so you don't have any team." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." @@ -733,43 +733,43 @@ msgstr "Vous n'êtes pas administrateur." msgid "This team has no pending validation." msgstr "L'équipe n'a pas de validation en attente." -#: apps/participation/views.py:244 +#: apps/participation/views.py:249 msgid "You must specify if you validate the registration or not." msgstr "Vous devez spécifier si vous validez l'inscription ou non." -#: apps/participation/views.py:272 +#: apps/participation/views.py:277 #, python-brace-format msgid "Update team {trigram}" msgstr "Mise à jour de l'équipe {trigram}" -#: apps/participation/views.py:309 apps/registration/views.py:243 +#: apps/participation/views.py:314 apps/registration/views.py:284 #, python-brace-format msgid "Photo authorization of {student}.{ext}" msgstr "Autorisation de droit à l'image de {student}.{ext}" -#: apps/participation/views.py:313 +#: apps/participation/views.py:318 #, python-brace-format msgid "Photo authorizations of team {trigram}.zip" msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip" -#: apps/participation/views.py:331 +#: apps/participation/views.py:336 msgid "The team is already validated or the validation is pending." msgstr "La validation de l'équipe est déjà faite ou en cours." -#: apps/participation/views.py:375 +#: apps/participation/views.py:380 msgid "The team is not validated yet." msgstr "L'équipe n'est pas encore validée." -#: apps/participation/views.py:385 +#: apps/participation/views.py:390 #, python-brace-format msgid "Participation of team {trigram}" msgstr "Participation de l'équipe {trigram}" -#: apps/participation/views.py:422 +#: apps/participation/views.py:427 msgid "Create question" msgstr "Créer une question" -#: apps/participation/views.py:522 +#: apps/participation/views.py:527 msgid "Calendar update" msgstr "Mise à jour du calendrier" @@ -966,8 +966,8 @@ msgid "Your password has been set. You may go ahead and log in now." msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter." #: apps/registration/templates/registration/password_reset_complete.html:10 -#: corres2math/templates/base.html:134 corres2math/templates/base.html:241 -#: corres2math/templates/base.html:242 +#: corres2math/templates/base.html:139 corres2math/templates/base.html:246 +#: corres2math/templates/base.html:247 #: corres2math/templates/registration/login.html:7 #: corres2math/templates/registration/login.html:8 #: corres2math/templates/registration/login.html:25 @@ -1024,7 +1024,7 @@ msgstr "Réinitialiser mon mot de passe" #: apps/registration/templates/registration/signup.html:5 #: apps/registration/templates/registration/signup.html:8 #: apps/registration/templates/registration/signup.html:20 -#: apps/registration/views.py:29 +#: apps/registration/views.py:33 msgid "Sign up" msgstr "Inscription" @@ -1101,40 +1101,40 @@ msgid "Update user" msgstr "Modifier l'utilisateur" #: apps/registration/templates/registration/user_detail.html:77 -#: apps/registration/views.py:216 +#: apps/registration/views.py:247 msgid "Upload photo authorization" msgstr "Téléverser l'autorisation de droit à l'image" -#: apps/registration/views.py:37 +#: apps/registration/views.py:41 msgid "You can't register now." msgstr "Vous ne pouvez pas vous inscrire maintenant." -#: apps/registration/views.py:74 +#: apps/registration/views.py:85 msgid "Email validation" msgstr "Validation de l'adresse mail" -#: apps/registration/views.py:76 +#: apps/registration/views.py:87 msgid "Validate email" msgstr "Valider l'adresse mail" -#: apps/registration/views.py:115 +#: apps/registration/views.py:126 msgid "Email validation unsuccessful" msgstr "Échec de la validation de l'adresse mail" -#: apps/registration/views.py:126 +#: apps/registration/views.py:137 msgid "Email validation email sent" msgstr "Mail de confirmation de l'adresse mail envoyé" -#: apps/registration/views.py:134 +#: apps/registration/views.py:145 msgid "Resend email validation link" msgstr "Renvoyé le lien de validation de l'adresse mail" -#: apps/registration/views.py:168 +#: apps/registration/views.py:181 #, python-brace-format msgid "Detail of user {user}" msgstr "Détails de l'utilisateur {user}" -#: apps/registration/views.py:189 +#: apps/registration/views.py:211 #, python-brace-format msgid "Update user {user}" msgstr "Mise à jour de l'utilisateur {user}" @@ -1205,46 +1205,50 @@ msgid "Home" msgstr "Accueil" #: corres2math/templates/base.html:75 +msgid "Users" +msgstr "Utilisateurs" + +#: corres2math/templates/base.html:78 msgid "Teams" msgstr "Équipes" -#: corres2math/templates/base.html:92 +#: corres2math/templates/base.html:95 msgid "My team" msgstr "Mon équipe" -#: corres2math/templates/base.html:97 +#: corres2math/templates/base.html:100 msgid "My participation" msgstr "Ma participation" -#: corres2math/templates/base.html:104 +#: corres2math/templates/base.html:107 msgid "Chat" msgstr "Chat" -#: corres2math/templates/base.html:108 +#: corres2math/templates/base.html:111 msgid "Administration" msgstr "Administration" -#: corres2math/templates/base.html:116 +#: corres2math/templates/base.html:119 msgid "Search..." msgstr "Chercher ..." -#: corres2math/templates/base.html:125 +#: corres2math/templates/base.html:128 msgid "Return to admin view" msgstr "Retourner à l'interface administrateur" -#: corres2math/templates/base.html:130 +#: corres2math/templates/base.html:134 msgid "Register" msgstr "S'inscrire" -#: corres2math/templates/base.html:146 +#: corres2math/templates/base.html:151 msgid "My account" msgstr "Mon compte" -#: corres2math/templates/base.html:149 +#: corres2math/templates/base.html:154 msgid "Log out" msgstr "Déconnexion" -#: corres2math/templates/base.html:166 +#: corres2math/templates/base.html:171 #, python-format msgid "" "Your email address is not validated. Please click on the link you received " @@ -1255,11 +1259,11 @@ msgstr "" "avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur ce lien." -#: corres2math/templates/base.html:190 +#: corres2math/templates/base.html:195 msgid "Contact us" msgstr "Nous contacter" -#: corres2math/templates/base.html:228 +#: corres2math/templates/base.html:233 msgid "Search results" msgstr "Résultats de la recherche"