Test questions, 100% coverage

This commit is contained in:
Yohann D'ANELLO 2020-11-03 17:58:05 +01:00
parent b6cefc1519
commit c35fb4e996
4 changed files with 168 additions and 78 deletions

View File

@ -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',)

View File

@ -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.

View File

@ -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

View File

@ -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 <yohann.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <code>%(request_path)s</code> was not found on the server."
@ -1120,11 +1138,11 @@ msgstr ""
"Le chemin demandé <code>%(request_path)s</code> 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 <a href="
"\"%(send_email_url)s\">ce lien</a>."
#: 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."