diff --git a/apps/participation/forms.py b/apps/participation/forms.py index 47220a6..605601c 100644 --- a/apps/participation/forms.py +++ b/apps/participation/forms.py @@ -2,7 +2,7 @@ import re from bootstrap_datepicker_plus import DateTimePickerInput from django import forms -from django.core.exceptions import ValidationError +from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db.models import Q from django.utils.translation import gettext_lazy as _ @@ -128,7 +128,7 @@ class SendParticipationForm(forms.ModelForm): super().__init__(*args, **kwargs) try: self.fields["sent_participation"].initial = self.instance.sent_participation - except: # No sent participation + except ObjectDoesNotExist: # No sent participation pass self.fields["sent_participation"].queryset = Participation.objects.filter( ~Q(pk=self.instance.pk) & Q(problem=self.instance.problem, valid=True) @@ -155,6 +155,11 @@ class QuestionForm(forms.ModelForm): super().__init__(*args, **kwargs) self.fields["question"].widget.attrs.update({"placeholder": _("How did you get the idea to ...?")}) + def clean(self): + if Phase.current_phase().phase_number != 2: + self.add_error(None, _("You can only create or update a question during the second phase.")) + return super().clean() + class Meta: model = Question fields = ('question',) diff --git a/apps/participation/tests.py b/apps/participation/tests.py index 59a20be..4ce5822 100644 --- a/apps/participation/tests.py +++ b/apps/participation/tests.py @@ -4,7 +4,6 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.core.management import call_command -from django.db.models import F from django.test import TestCase from django.urls import reverse from django.utils import timezone @@ -504,6 +503,70 @@ class TestStudentParticipation(TestCase): data=dict(link="https://youtube.com/watch?v=73nsrixx7eI")) self.assertEqual(response.status_code, 200) + def test_questions(self): + """ + Ensure that creating/updating/deleting a question is working. + """ + self.user.registration.team = self.team + self.user.registration.save() + + self.team.participation.valid = True + self.team.participation.save() + + response = self.client.get(reverse("participation:add_question", args=(self.team.participation.pk,))) + self.assertEqual(response.status_code, 200) + + # We are not in second phase + response = self.client.post(reverse("participation:add_question", args=(self.team.participation.pk,)), + data=dict(question="I got censored!")) + self.assertEqual(response.status_code, 200) + + # Set the second phase + for i in range(1, 5): + Phase.objects.filter(phase_number=i).update(start=timezone.now() + timedelta(days=i - 2), + end=timezone.now() + timedelta(days=i - 1)) + self.assertEqual(Phase.current_phase().phase_number, 2) + + # Create a question + response = self.client.post(reverse("participation:add_question", args=(self.team.participation.pk,)), + data=dict(question="I asked a question!")) + self.assertRedirects(response, reverse("participation:participation_detail", + args=(self.team.participation.pk,)), 302, 200) + qs = Question.objects.filter(participation=self.team.participation, question="I asked a question!") + self.assertTrue(qs.exists()) + question = qs.get() + + # Update a question + response = self.client.get(reverse("participation:update_question", args=(question.pk,))) + self.assertEqual(response.status_code, 200) + response = self.client.post(reverse("participation:update_question", args=(question.pk,)), data=dict( + question="The question changed!", + )) + self.assertRedirects(response, reverse("participation:participation_detail", + args=(self.team.participation.pk,)), 302, 200) + question.refresh_from_db() + self.assertEqual(question.question, "The question changed!") + + # Delete the question + response = self.client.get(reverse("participation:delete_question", args=(question.pk,))) + self.assertEqual(response.status_code, 200) + response = self.client.post(reverse("participation:delete_question", args=(question.pk,))) + self.assertRedirects(response, reverse("participation:participation_detail", + args=(self.team.participation.pk,)), 302, 200) + self.assertFalse(Question.objects.filter(pk=question.pk).exists()) + + # Non-authenticated users are redirected to login page + self.client.logout() + response = self.client.get(reverse("participation:add_question", args=(self.team.participation.pk,))) + self.assertRedirects(response, reverse("login") + "?next=" + + reverse("participation:add_question", args=(self.team.participation.pk,)), 302, 200) + response = self.client.get(reverse("participation:update_question", args=(self.question.pk,))) + self.assertRedirects(response, reverse("login") + "?next=" + + reverse("participation:delete_question", args=(self.question.pk,)), 302, 200) + response = self.client.get(reverse("participation:add_question", args=(self.question.pk,))) + self.assertRedirects(response, reverse("login") + "?next=" + + reverse("participation:add_question", args=(self.question.pk,)), 302, 200) + def test_current_phase(self): """ Ensure that the current phase is the good one. diff --git a/apps/participation/views.py b/apps/participation/views.py index 04a8455..de74762 100644 --- a/apps/participation/views.py +++ b/apps/participation/views.py @@ -418,6 +418,7 @@ class CreateQuestionView(LoginRequiredMixin, CreateView): self.participation = Participation.objects.get(pk=kwargs["pk"]) if request.user.registration.is_admin or \ request.user.registration.participates and \ + self.participation.valid and \ request.user.registration.team.pk == self.participation.team_id: return super().dispatch(request, *args, **kwargs) raise PermissionDenied @@ -443,6 +444,7 @@ class UpdateQuestionView(LoginRequiredMixin, UpdateView): return self.handle_no_permission() if request.user.registration.is_admin or \ request.user.registration.participates and \ + self.object.participation.valid and \ request.user.registration.team.pk == self.object.participation.team_id: return super().dispatch(request, *args, **kwargs) raise PermissionDenied @@ -464,6 +466,7 @@ class DeleteQuestionView(LoginRequiredMixin, DeleteView): return self.handle_no_permission() if request.user.registration.is_admin or \ request.user.registration.participates and \ + self.object.participation.valid and \ request.user.registration.team.pk == self.object.participation.team_id: return super().dispatch(request, *args, **kwargs) raise PermissionDenied diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 3411a2a..a759138 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-11-02 10:56+0100\n" +"POT-Creation-Date: 2020-11-03 17:32+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Yohann D'ANELLO \n" "Language-Team: LANGUAGE \n" @@ -30,7 +30,7 @@ msgid "This task failed successfully." msgstr "Cette tâche a échoué avec succès." #: apps/eastereggs/templates/eastereggs/xp_modal.html:16 -#: templates/base_modal.html:19 +#: corres2math/templates/base_modal.html:19 msgid "Close" msgstr "Fermer" @@ -99,6 +99,16 @@ 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:122 +#: 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:128 +#: apps/participation/models.py:182 +msgid "valid" +msgstr "valide" + #: apps/participation/forms.py:20 apps/participation/models.py:33 msgid "The trigram must be composed of three uppercase letters." msgstr "Le trigramme doit être composé de trois lettres majuscules." @@ -123,19 +133,25 @@ msgstr "Vous ne pouvez pas envoyer de vidéo après la date limite." msgid "Send to team" msgstr "Envoyer à l'équipe" -#: apps/participation/forms.py:152 +#: apps/participation/forms.py:156 msgid "How did you get the idea to ...?" msgstr "Comment avez-vous eu l'idée de ... ?" -#: apps/participation/forms.py:175 +#: apps/participation/forms.py:160 +msgid "You can only create or update a question during the second phase." +msgstr "" +"Vous pouvez créer ou modifier une question seulement pendant la seconde " +"phase." + +#: apps/participation/forms.py:186 msgid "Start date must be before the end date." msgstr "La date de début doit être avant la date de fin." -#: apps/participation/forms.py:177 +#: apps/participation/forms.py:188 msgid "This phase must start after the previous phases." msgstr "Cette phase doit commencer après les phases précédentes." -#: apps/participation/forms.py:179 +#: apps/participation/forms.py:190 msgid "This phase must end after the next phases." msgstr "Cette phase doit finir avant les phases suivantes." @@ -187,15 +203,6 @@ msgstr "équipes" msgid "Problem #{problem:d}" msgstr "Problème n°{problem:d}" -#: apps/participation/models.py:122 apps/participation/tables.py:35 -#: apps/participation/tables.py:62 -msgid "problem number" -msgstr "numéro de problème" - -#: apps/participation/models.py:128 apps/participation/models.py:182 -msgid "valid" -msgstr "valide" - #: apps/participation/models.py:129 apps/participation/models.py:183 msgid "The video got the validation of the administrators." msgstr "La vidéo a été validée par les administrateurs." @@ -283,12 +290,12 @@ msgid "phases" msgstr "phases" #: apps/participation/templates/participation/create_team.html:11 -#: templates/base.html:231 +#: corres2math/templates/base.html:231 msgid "Create" msgstr "Créer" #: apps/participation/templates/participation/join_team.html:11 -#: templates/base.html:227 +#: corres2math/templates/base.html:227 msgid "Join" msgstr "Rejoindre" @@ -462,7 +469,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:463 +#: apps/participation/views.py:482 msgid "Upload video" msgstr "Envoyer la vidéo" @@ -497,7 +504,7 @@ msgid "Update question" msgstr "Modifier la question" #: apps/participation/templates/participation/participation_detail.html:217 -#: apps/participation/views.py:440 +#: apps/participation/views.py:459 msgid "Delete question" msgstr "Supprimer la question" @@ -507,8 +514,8 @@ msgid "Display synthesis" msgstr "Afficher la synthèse" #: apps/participation/templates/participation/phase_list.html:10 -#: apps/participation/views.py:482 templates/base.html:68 -#: templates/base.html:70 templates/base.html:217 +#: apps/participation/views.py:501 corres2math/templates/base.html:68 +#: corres2math/templates/base.html:70 corres2math/templates/base.html:217 msgid "Calendar" msgstr "Calendrier" @@ -620,7 +627,7 @@ msgid "Update team" msgstr "Modifier l'équipe" #: apps/participation/templates/participation/team_detail.html:127 -#: apps/participation/views.py:296 +#: apps/participation/views.py:314 msgid "Leave team" msgstr "Quitter l'équipe" @@ -628,8 +635,8 @@ msgstr "Quitter l'équipe" msgid "Are you sure that you want to leave this team?" msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?" -#: apps/participation/views.py:36 templates/base.html:77 -#: templates/base.html:230 +#: apps/participation/views.py:36 corres2math/templates/base.html:77 +#: corres2math/templates/base.html:230 msgid "Create team" msgstr "Créer une équipe" @@ -641,80 +648,89 @@ 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 templates/base.html:82 -#: templates/base.html:226 +#: apps/participation/views.py:82 corres2math/templates/base.html:82 +#: corres2math/templates/base.html:226 msgid "Join team" msgstr "Rejoindre une équipe" -#: apps/participation/views.py:133 apps/participation/views.py:302 -#: apps/participation/views.py:335 +#: apps/participation/views.py:133 apps/participation/views.py:320 +#: apps/participation/views.py:353 msgid "You are not in a team." msgstr "Vous n'êtes pas dans une équipe." -#: apps/participation/views.py:134 apps/participation/views.py:336 +#: apps/participation/views.py:134 apps/participation/views.py:354 msgid "You don't participate, so you don't have any team." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." -#: apps/participation/views.py:155 +#: apps/participation/views.py:156 #, python-brace-format msgid "Detail of team {trigram}" msgstr "Détails de l'équipe {trigram}" -#: apps/participation/views.py:180 +#: apps/participation/views.py:188 msgid "You don't participate, so you can't request the validation of the team." msgstr "" "Vous ne participez pas, vous ne pouvez pas demander la validation de " "l'équipe." -#: apps/participation/views.py:183 +#: apps/participation/views.py:191 msgid "The validation of the team is already done or pending." msgstr "La validation de l'équipe est déjà faite ou en cours." -#: apps/participation/views.py:196 +#: apps/participation/views.py:194 +msgid "" +"The team can't be validated: missing email address confirmations, photo " +"authorizations, people or the chosen problem is not set." +msgstr "" +"L'équipe ne peut pas être validée : il manque soit les confirmations " +"d'adresse e-mail, soit une autorisation parentale, soit des personnes soit " +"le problème n'a pas été choisi." + +#: apps/participation/views.py:213 msgid "You are not an administrator." msgstr "Vous n'êtes pas administrateur." -#: apps/participation/views.py:199 +#: apps/participation/views.py:216 msgid "This team has no pending validation." msgstr "L'équipe n'a pas de validation en attente." -#: apps/participation/views.py:218 +#: apps/participation/views.py:235 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:245 +#: apps/participation/views.py:263 #, python-brace-format msgid "Update team {trigram}" msgstr "Mise à jour de l'équipe {trigram}" -#: apps/participation/views.py:282 apps/registration/views.py:243 +#: apps/participation/views.py:300 apps/registration/views.py:243 #, python-brace-format msgid "Photo authorization of {student}.{ext}" msgstr "Autorisation de droit à l'image de {student}.{ext}" -#: apps/participation/views.py:286 +#: apps/participation/views.py:304 #, 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:304 +#: apps/participation/views.py:322 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:348 +#: apps/participation/views.py:366 msgid "The team is not validated yet." msgstr "L'équipe n'est pas encore validée." -#: apps/participation/views.py:357 +#: apps/participation/views.py:376 #, python-brace-format msgid "Participation of team {trigram}" msgstr "Participation de l'équipe {trigram}" -#: apps/participation/views.py:394 +#: apps/participation/views.py:413 msgid "Create question" msgstr "Créer une question" -#: apps/participation/views.py:491 +#: apps/participation/views.py:510 msgid "Calendar update" msgstr "Mise à jour du calendrier" @@ -907,9 +923,11 @@ 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 -#: templates/base.html:130 templates/base.html:221 templates/base.html:222 -#: templates/registration/login.html:7 templates/registration/login.html:8 -#: templates/registration/login.html:25 +#: corres2math/templates/base.html:130 corres2math/templates/base.html:221 +#: corres2math/templates/base.html:222 +#: corres2math/templates/registration/login.html:7 +#: corres2math/templates/registration/login.html:8 +#: corres2math/templates/registration/login.html:25 msgid "Log in" msgstr "Connexion" @@ -1082,11 +1100,11 @@ msgstr "Anglais" msgid "French" msgstr "Français" -#: templates/400.html:6 +#: corres2math/templates/400.html:6 msgid "Bad request" msgstr "Requête invalide" -#: templates/400.html:7 +#: corres2math/templates/400.html:7 msgid "" "Sorry, your request was bad. Don't know what could be wrong. An email has " "been sent to webmasters with the details of the error. You can now watch " @@ -1096,23 +1114,23 @@ msgstr "" "email a été envoyé aux administrateurs avec les détails de l'erreur. Vous " "pouvez désormais retourner voir des vidéos." -#: templates/403.html:6 +#: corres2math/templates/403.html:6 msgid "Permission denied" msgstr "Permission refusée" -#: templates/403.html:7 +#: corres2math/templates/403.html:7 msgid "You don't have the right to perform this request." msgstr "Vous n'avez pas le droit d'effectuer cette requête." -#: templates/403.html:10 templates/404.html:10 +#: corres2math/templates/403.html:10 corres2math/templates/404.html:10 msgid "Exception message:" msgstr "Message d'erreur :" -#: templates/404.html:6 +#: corres2math/templates/404.html:6 msgid "Page not found" msgstr "Page non trouvée" -#: templates/404.html:7 +#: corres2math/templates/404.html:7 #, python-format msgid "" "The requested path %(request_path)s was not found on the server." @@ -1120,11 +1138,11 @@ msgstr "" "Le chemin demandé %(request_path)s n'a pas été trouvé sur le " "serveur." -#: templates/500.html:6 +#: corres2math/templates/500.html:6 msgid "Server error" msgstr "Erreur du serveur" -#: templates/500.html:7 +#: corres2math/templates/500.html:7 msgid "" "Sorry, an error occurred when processing your request. An email has been " "sent to webmasters with the detail of the error, and this will be fixed " @@ -1135,47 +1153,47 @@ msgstr "" "avec les détails de l'erreur. Vous pouvez désormais retourner voir des " "vidéos." -#: templates/base.html:64 +#: corres2math/templates/base.html:64 msgid "Home" msgstr "Accueil" -#: templates/base.html:88 +#: corres2math/templates/base.html:88 msgid "My team" msgstr "Mon équipe" -#: templates/base.html:93 +#: corres2math/templates/base.html:93 msgid "My participation" msgstr "Ma participation" -#: templates/base.html:100 +#: corres2math/templates/base.html:100 msgid "Chat" msgstr "Chat" -#: templates/base.html:104 +#: corres2math/templates/base.html:104 msgid "Administration" msgstr "Administration" -#: templates/base.html:112 +#: corres2math/templates/base.html:112 msgid "Search..." msgstr "Chercher ..." -#: templates/base.html:121 +#: corres2math/templates/base.html:121 msgid "Return to admin view" msgstr "Retourner à l'interface administrateur" -#: templates/base.html:126 +#: corres2math/templates/base.html:126 msgid "Register" msgstr "S'inscrire" -#: templates/base.html:142 +#: corres2math/templates/base.html:142 msgid "My account" msgstr "Mon compte" -#: templates/base.html:145 +#: corres2math/templates/base.html:145 msgid "Log out" msgstr "Déconnexion" -#: templates/base.html:162 +#: corres2math/templates/base.html:162 #, python-format msgid "" "Your email address is not validated. Please click on the link you received " @@ -1186,23 +1204,23 @@ msgstr "" "avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur ce lien." -#: templates/base.html:186 +#: corres2math/templates/base.html:186 msgid "Contact us" msgstr "Nous contacter" -#: templates/base.html:219 +#: corres2math/templates/base.html:219 msgid "Search results" msgstr "Résultats de la recherche" -#: templates/registration/logged_out.html:8 +#: corres2math/templates/registration/logged_out.html:8 msgid "Thanks for spending some quality time with the Web site today." msgstr "Merci d'avoir utilisé la plateforme des Correspondances." -#: templates/registration/logged_out.html:9 +#: corres2math/templates/registration/logged_out.html:9 msgid "Log in again" msgstr "Se reconnecter" -#: templates/registration/login.html:13 +#: corres2math/templates/registration/login.html:13 #, python-format msgid "" "You are authenticated as %(user)s, but are not authorized to access this " @@ -1211,18 +1229,19 @@ msgstr "" "Vous êtes connectés en tant que %(user)s, mais n'êtes pas autorisés à " "accéder à cette page. Voulez-vous vous reconnecter avec un autre compte ?" -#: templates/registration/login.html:23 +#: corres2math/templates/registration/login.html:23 msgid "Forgotten your password or username?" msgstr "Mot de passe oublié ?" -#: templates/search/search.html:6 templates/search/search.html:11 +#: corres2math/templates/search/search.html:6 +#: corres2math/templates/search/search.html:10 msgid "Search" msgstr "Chercher" -#: templates/search/search.html:16 +#: corres2math/templates/search/search.html:15 msgid "Results" msgstr "Résultats" -#: templates/search/search.html:26 +#: corres2math/templates/search/search.html:25 msgid "No results found." msgstr "Aucun résultat."