1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-05-18 12:12:46 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
Emmy D'Anello
4f129280c3
Add buttons to publish notes
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-03-24 18:14:43 +01:00
Emmy D'Anello
d2c1a826a8
Update permissions for juries presidents
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-03-24 17:42:09 +01:00
Emmy D'Anello
0b9079b431
Add button to update notes
Add jury president field for pools

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-03-24 15:36:51 +01:00
10 changed files with 420 additions and 168 deletions

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TFJM\n" "Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-24 11:26+0100\n" "POT-Creation-Date: 2024-03-24 18:13+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n" "Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -214,17 +214,17 @@ msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}" msgstr "Poule {letter}{number}"
#: draw/models.py:408 draw/models.py:435 participation/admin.py:136 #: draw/models.py:408 draw/models.py:435 participation/admin.py:136
#: participation/admin.py:155 participation/models.py:583 #: participation/admin.py:155 participation/models.py:597
#: participation/models.py:592 participation/tables.py:84 #: participation/models.py:606 participation/tables.py:84
msgid "pool" msgid "pool"
msgstr "poule" msgstr "poule"
#: draw/models.py:409 participation/models.py:584 #: draw/models.py:409 participation/models.py:598
msgid "pools" msgid "pools"
msgstr "poules" msgstr "poules"
#: draw/models.py:421 participation/models.py:503 participation/models.py:753 #: draw/models.py:421 participation/models.py:503 participation/models.py:767
#: participation/models.py:783 participation/models.py:821 #: participation/models.py:797 participation/models.py:839
msgid "participation" msgid "participation"
msgstr "participation" msgstr "participation"
@ -248,8 +248,8 @@ msgid ""
msgstr "" msgstr ""
"L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1." "L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1."
#: draw/models.py:458 draw/models.py:481 participation/models.py:606 #: draw/models.py:458 draw/models.py:481 participation/models.py:620
#: participation/models.py:790 #: participation/models.py:804
#, python-brace-format #, python-brace-format
msgid "Problem #{problem}" msgid "Problem #{problem}"
msgstr "Problème n°{problem}" msgstr "Problème n°{problem}"
@ -377,8 +377,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
msgid "Close" msgid "Close"
msgstr "Fermer" msgstr "Fermer"
#: draw/views.py:31 participation/views.py:155 participation/views.py:441 #: draw/views.py:31 participation/views.py:156 participation/views.py:442
#: participation/views.py:472 #: participation/views.py:473
msgid "You are not in a team." msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe." msgstr "Vous n'êtes pas dans une équipe."
@ -456,21 +456,21 @@ msgid "selected for final"
msgstr "sélectionnée pour la finale" msgstr "sélectionnée pour la finale"
#: participation/admin.py:124 participation/admin.py:183 #: participation/admin.py:124 participation/admin.py:183
#: participation/models.py:613 participation/tables.py:112 #: participation/models.py:627 participation/tables.py:112
msgid "defender" msgid "defender"
msgstr "défenseur⋅se" msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:620 #: participation/admin.py:128 participation/models.py:634
#: participation/models.py:833 #: participation/models.py:851
msgid "opponent" msgid "opponent"
msgstr "opposant⋅e" msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:627 #: participation/admin.py:132 participation/models.py:641
#: participation/models.py:834 #: participation/models.py:852
msgid "reporter" msgid "reporter"
msgstr "rapporteur⋅e" msgstr "rapporteur⋅e"
#: participation/admin.py:187 participation/models.py:788 #: participation/admin.py:187 participation/models.py:802
msgid "problem" msgid "problem"
msgstr "numéro de problème" msgstr "numéro de problème"
@ -490,11 +490,11 @@ msgstr "Ce trigramme est déjà utilisé."
msgid "No team was found with this access code." msgid "No team was found with this access code."
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
#: participation/forms.py:60 participation/views.py:443 #: participation/forms.py:60 participation/views.py:444
msgid "The team is already validated or the validation is pending." msgid "The team is already validated or the validation is pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." msgstr "La validation de l'équipe est déjà faite ou en cours."
#: participation/forms.py:89 participation/forms.py:359 #: participation/forms.py:89 participation/forms.py:364
#: registration/forms.py:122 registration/forms.py:144 #: registration/forms.py:122 registration/forms.py:144
#: registration/forms.py:166 registration/forms.py:188 #: registration/forms.py:166 registration/forms.py:188
#: registration/forms.py:237 registration/forms.py:270 #: registration/forms.py:237 registration/forms.py:270
@ -520,7 +520,7 @@ msgstr "Message à adresser à l'équipe :"
msgid "The uploaded file size must be under 5 Mo." msgid "The uploaded file size must be under 5 Mo."
msgstr "Le fichier envoyé doit peser moins de 5 Mo." msgstr "Le fichier envoyé doit peser moins de 5 Mo."
#: participation/forms.py:161 participation/forms.py:361 #: participation/forms.py:161 participation/forms.py:366
msgid "The uploaded file must be a PDF file." msgid "The uploaded file must be a PDF file."
msgstr "Le fichier envoyé doit être au format PDF." msgstr "Le fichier envoyé doit être au format PDF."
@ -528,21 +528,21 @@ msgstr "Le fichier envoyé doit être au format PDF."
msgid "The PDF file must not have more than 30 pages." msgid "The PDF file must not have more than 30 pages."
msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages." msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages."
#: participation/forms.py:232 #: participation/forms.py:237
#: participation/templates/participation/pool_detail.html:123 #: participation/templates/participation/pool_detail.html:123
#: participation/templates/participation/tournament_detail.html:119 #: participation/templates/participation/tournament_detail.html:139
msgid "Add" msgid "Add"
msgstr "Ajouter" msgstr "Ajouter"
#: participation/forms.py:247 #: participation/forms.py:252
msgid "This user already exists, but is a participant." msgid "This user already exists, but is a participant."
msgstr "Cet⋅te utilisateur⋅rice existe déjà, mais en tant que participant⋅e." msgstr "Cet⋅te utilisateur⋅rice existe déjà, mais en tant que participant⋅e."
#: participation/forms.py:258 #: participation/forms.py:263
msgid "CSV file:" msgid "CSV file:"
msgstr "Tableur au format CSV :" msgstr "Tableur au format CSV :"
#: participation/forms.py:282 #: participation/forms.py:287
msgid "" msgid ""
"This file contains non-UTF-8 and non-ISO-8859-1 content. Please send your " "This file contains non-UTF-8 and non-ISO-8859-1 content. Please send your "
"sheet as a CSV file." "sheet as a CSV file."
@ -550,30 +550,30 @@ msgstr ""
"Ce fichier contient des éléments non-UTF-8 et non-ISO-8859-1. Merci " "Ce fichier contient des éléments non-UTF-8 et non-ISO-8859-1. Merci "
"d'envoyer votre tableur au format CSV." "d'envoyer votre tableur au format CSV."
#: participation/forms.py:297 #: participation/forms.py:302
msgid "Can't determine the pool size. Are you sure your file is correct?" msgid "Can't determine the pool size. Are you sure your file is correct?"
msgstr "" msgstr ""
"Impossible de déterminer la taille de la poule. Êtes-vous sûr⋅e que le " "Impossible de déterminer la taille de la poule. Êtes-vous sûr⋅e que le "
"fichier est correct ?" "fichier est correct ?"
#: participation/forms.py:317 #: participation/forms.py:322
msgid "The following note is higher of the maximum expected value:" msgid "The following note is higher of the maximum expected value:"
msgstr "La note suivante est supérieure au maximum attendu :" msgstr "La note suivante est supérieure au maximum attendu :"
#: participation/forms.py:325 #: participation/forms.py:330
msgid "The following user was not found:" msgid "The following user was not found:"
msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :" msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :"
#: participation/forms.py:342 #: participation/forms.py:347
msgid "The defender, the opponent and the reporter must be different." msgid "The defender, the opponent and the reporter must be different."
msgstr "" msgstr ""
"Les équipes défenseuse, opposante et rapportrice doivent être différent⋅es." "Les équipes défenseuse, opposante et rapportrice doivent être différent⋅es."
#: participation/forms.py:346 #: participation/forms.py:351
msgid "This defender did not work on this problem." msgid "This defender did not work on this problem."
msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème." msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème."
#: participation/forms.py:365 #: participation/forms.py:370
msgid "The PDF file must not have more than 2 pages." msgid "The PDF file must not have more than 2 pages."
msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages." msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages."
@ -864,19 +864,23 @@ msgstr "Tour {round}"
msgid "juries" msgid "juries"
msgstr "jurys" msgstr "jurys"
#: participation/models.py:550 #: participation/models.py:552
msgid "president of the jury"
msgstr "président⋅e du jury"
#: participation/models.py:559
msgid "BigBlueButton URL" msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton" msgstr "Lien BigBlueButton"
#: participation/models.py:551 #: participation/models.py:560
msgid "The link of the BBB visio for this pool." msgid "The link of the BBB visio for this pool."
msgstr "Le lien du salon BBB pour cette poule." msgstr "Le lien du salon BBB pour cette poule."
#: participation/models.py:556 #: participation/models.py:565
msgid "results available" msgid "results available"
msgstr "résultats disponibles" msgstr "résultats disponibles"
#: participation/models.py:557 #: participation/models.py:566
msgid "" msgid ""
"Check this case when results become accessible to teams. They stay " "Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given." "accessible to you. Only averages are given."
@ -885,28 +889,32 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont " "Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées." "communiquées."
#: participation/models.py:577 #: participation/models.py:587
msgid "The president of the jury must be part of the jury."
msgstr "Læ président⋅e du jury doit faire partie du jury."
#: participation/models.py:591
#, python-brace-format #, python-brace-format
msgid "Pool of day {round} for tournament {tournament} with teams {teams}" msgid "Pool of day {round} for tournament {tournament} with teams {teams}"
msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}" msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}"
#: participation/models.py:597 #: participation/models.py:611
msgid "position" msgid "position"
msgstr "position" msgstr "position"
#: participation/models.py:604 #: participation/models.py:618
msgid "defended solution" msgid "defended solution"
msgstr "solution défendue" msgstr "solution défendue"
#: participation/models.py:637 #: participation/models.py:651
msgid "observer" msgid "observer"
msgstr "observateur⋅rice" msgstr "observateur⋅rice"
#: participation/models.py:642 #: participation/models.py:656
msgid "penalties" msgid "penalties"
msgstr "pénalités" msgstr "pénalités"
#: participation/models.py:644 #: participation/models.py:658
msgid "" msgid ""
"Number of penalties for the defender. The defender will loose a 0.5 " "Number of penalties for the defender. The defender will loose a 0.5 "
"coefficient per penalty." "coefficient per penalty."
@ -914,124 +922,124 @@ msgstr ""
"Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 " "Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 "
"sur sa présentation orale par pénalité." "sur sa présentation orale par pénalité."
#: participation/models.py:720 participation/models.py:723 #: participation/models.py:734 participation/models.py:737
#: participation/models.py:726 participation/models.py:729 #: participation/models.py:740 participation/models.py:743
#, python-brace-format #, python-brace-format
msgid "Team {trigram} is not registered in the pool." msgid "Team {trigram} is not registered in the pool."
msgstr "L'équipe {trigram} n'est pas inscrite dans la poule." msgstr "L'équipe {trigram} n'est pas inscrite dans la poule."
#: participation/models.py:734 #: participation/models.py:748
#, python-brace-format #, python-brace-format
msgid "Passage of {defender} for problem {problem}" msgid "Passage of {defender} for problem {problem}"
msgstr "Passage de {defender} pour le problème {problem}" msgstr "Passage de {defender} pour le problème {problem}"
#: participation/models.py:738 participation/models.py:747 #: participation/models.py:752 participation/models.py:761
#: participation/models.py:828 participation/models.py:870 #: participation/models.py:846 participation/models.py:888
msgid "passage" msgid "passage"
msgstr "passage" msgstr "passage"
#: participation/models.py:739 #: participation/models.py:753
msgid "passages" msgid "passages"
msgstr "passages" msgstr "passages"
#: participation/models.py:758 #: participation/models.py:772
msgid "difference" msgid "difference"
msgstr "différence" msgstr "différence"
#: participation/models.py:759 #: participation/models.py:773
msgid "Score to add/remove on the final score" msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final" msgstr "Score à ajouter/retrancher au score final"
#: participation/models.py:766 #: participation/models.py:780
msgid "tweak" msgid "tweak"
msgstr "harmonisation" msgstr "harmonisation"
#: participation/models.py:767 #: participation/models.py:781
msgid "tweaks" msgid "tweaks"
msgstr "harmonisations" msgstr "harmonisations"
#: participation/models.py:795 #: participation/models.py:809
msgid "solution for the final tournament" msgid "solution for the final tournament"
msgstr "solution pour la finale" msgstr "solution pour la finale"
#: participation/models.py:800 participation/models.py:839 #: participation/models.py:814 participation/models.py:857
msgid "file" msgid "file"
msgstr "fichier" msgstr "fichier"
#: participation/models.py:806 #: participation/models.py:824
#, python-brace-format #, python-brace-format
msgid "Solution of team {team} for problem {problem}" msgid "Solution of team {team} for problem {problem}"
msgstr "Solution de l'équipe {team} pour le problème {problem}" msgstr "Solution de l'équipe {team} pour le problème {problem}"
#: participation/models.py:808 #: participation/models.py:826
msgid "for final" msgid "for final"
msgstr "pour la finale" msgstr "pour la finale"
#: participation/models.py:811 #: participation/models.py:829
msgid "solution" msgid "solution"
msgstr "solution" msgstr "solution"
#: participation/models.py:812 #: participation/models.py:830
msgid "solutions" msgid "solutions"
msgstr "solutions" msgstr "solutions"
#: participation/models.py:845 #: participation/models.py:863
#, python-brace-format #, python-brace-format
msgid "Synthesis of {team} as {type} for problem {problem} of {defender}" msgid "Synthesis of {team} as {type} for problem {problem} of {defender}"
msgstr "" msgstr ""
"Note de synthèse de l'équipe {team} en tant que {type} pour le problème " "Note de synthèse de l'équipe {team} en tant que {type} pour le problème "
"{problem} de {defender}" "{problem} de {defender}"
#: participation/models.py:853 #: participation/models.py:871
msgid "synthesis" msgid "synthesis"
msgstr "note de synthèse" msgstr "note de synthèse"
#: participation/models.py:854 #: participation/models.py:872
msgid "syntheses" msgid "syntheses"
msgstr "notes de synthèse" msgstr "notes de synthèse"
#: participation/models.py:863 #: participation/models.py:881
msgid "jury" msgid "jury"
msgstr "jury" msgstr "jury"
#: participation/models.py:875 #: participation/models.py:893
msgid "defender writing note" msgid "defender writing note"
msgstr "note d'écrit de la défense" msgstr "note d'écrit de la défense"
#: participation/models.py:881 #: participation/models.py:899
msgid "defender oral note" msgid "defender oral note"
msgstr "note d'oral de la défense" msgstr "note d'oral de la défense"
#: participation/models.py:887 #: participation/models.py:905
msgid "opponent writing note" msgid "opponent writing note"
msgstr "note d'écrit de l'opposition" msgstr "note d'écrit de l'opposition"
#: participation/models.py:893 #: participation/models.py:911
msgid "opponent oral note" msgid "opponent oral note"
msgstr "note d'oral de l'opposition" msgstr "note d'oral de l'opposition"
#: participation/models.py:899 #: participation/models.py:917
msgid "reporter writing note" msgid "reporter writing note"
msgstr "note d'écrit du rapportage" msgstr "note d'écrit du rapportage"
#: participation/models.py:905 #: participation/models.py:923
msgid "reporter oral note" msgid "reporter oral note"
msgstr "note d'oral du rapportage" msgstr "note d'oral du rapportage"
#: participation/models.py:911 #: participation/models.py:929
msgid "observer note" msgid "observer note"
msgstr "note de l'observation" msgstr "note de l'observation"
#: participation/models.py:944 #: participation/models.py:965
#, python-brace-format #, python-brace-format
msgid "Notes of {jury} for {passage}" msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}" msgstr "Notes de {jury} pour le {passage}"
#: participation/models.py:951 #: participation/models.py:968
msgid "note" msgid "note"
msgstr "note" msgstr "note"
#: participation/models.py:952 #: participation/models.py:969
msgid "notes" msgid "notes"
msgstr "notes" msgstr "notes"
@ -1357,7 +1365,41 @@ msgstr "Modifier la poule"
msgid "Upload notes" msgid "Upload notes"
msgstr "Envoyer les notes" msgstr "Envoyer les notes"
#: participation/templates/participation/pool_jury.html:44 #: participation/templates/participation/pool_jury.html:9
msgid ""
"On this page, you can manage the juries of the pool. You can add a new jury "
"by entering the email address of the jury. If the jury is not registered, "
"the account will be created automatically. If the jury already exists, its "
"account will be autocompleted and directly linked to the pool."
msgstr ""
"Sur cette page, vous pouvez gérer les juré⋅es de la poule. Vous pouvez "
"ajouter un⋅e juré⋅e en entrant son adresse e-mail. Si læ juré⋅e n'est pas "
"inscrit⋅e, le compte sera créé automatiquement. Si læ juré⋅e existe déjà, "
"son compte sera autocomplété et directement lié à la poule."
#: participation/templates/participation/pool_jury.html:17
msgid ""
"On this page, you can also define the president of the jury, who will have "
"the right to see all solutions and if necessary define the notes of other "
"jury members."
msgstr ""
"Sur cette page, vous pouvez aussi définir læ président⋅e du jury, qui aura "
"le droit de voir toutes les solutions et si nécessaire définir les notes des "
"autres membres du jury."
#: participation/templates/participation/pool_jury.html:41
msgid "PoJ"
msgstr "PDJ"
#: participation/templates/participation/pool_jury.html:46
msgid "Preside"
msgstr "Présider"
#: participation/templates/participation/pool_jury.html:51
msgid "Remove"
msgstr "Retirer"
#: participation/templates/participation/pool_jury.html:74
msgid "Back to pool detail" msgid "Back to pool detail"
msgstr "Retour aux détails de la poule" msgstr "Retour aux détails de la poule"
@ -1508,7 +1550,7 @@ msgid "Invalidate"
msgstr "Invalider" msgstr "Invalider"
#: participation/templates/participation/team_detail.html:209 #: participation/templates/participation/team_detail.html:209
#: participation/views.py:326 #: participation/views.py:327
msgid "Upload motivation letter" msgid "Upload motivation letter"
msgstr "Envoyer la lettre de motivation" msgstr "Envoyer la lettre de motivation"
@ -1517,7 +1559,7 @@ msgid "Update team"
msgstr "Modifier l'équipe" msgstr "Modifier l'équipe"
#: participation/templates/participation/team_detail.html:219 #: participation/templates/participation/team_detail.html:219
#: participation/views.py:435 #: participation/views.py:436
msgid "Leave team" msgid "Leave team"
msgstr "Quitter l'équipe" msgstr "Quitter l'équipe"
@ -1611,7 +1653,15 @@ msgstr "Poules"
msgid "Add new pool" msgid "Add new pool"
msgstr "Ajouter une nouvelle poule" msgstr "Ajouter une nouvelle poule"
#: participation/templates/participation/tournament_detail.html:118 #: participation/templates/participation/tournament_detail.html:121
msgid "Publish notes for first round"
msgstr "Publier les notes pour le premier tour"
#: participation/templates/participation/tournament_detail.html:127
msgid "Publish notes for second round"
msgstr "Publier les notes pour le second tour"
#: participation/templates/participation/tournament_detail.html:138
msgid "Add pool" msgid "Add pool"
msgstr "Ajouter une poule" msgstr "Ajouter une poule"
@ -1644,44 +1694,44 @@ msgstr "Modèles :"
msgid "Warning: non-free format" msgid "Warning: non-free format"
msgstr "Attention : format non libre" msgstr "Attention : format non libre"
#: participation/views.py:55 tfjm/templates/base.html:79 #: participation/views.py:56 tfjm/templates/base.html:79
#: tfjm/templates/navbar.html:35 #: tfjm/templates/navbar.html:35
msgid "Create team" msgid "Create team"
msgstr "Créer une équipe" msgstr "Créer une équipe"
#: participation/views.py:64 participation/views.py:105 #: participation/views.py:65 participation/views.py:106
msgid "You don't participate, so you can't create a team." msgid "You don't participate, so you can't create a team."
msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
#: participation/views.py:66 participation/views.py:107 #: participation/views.py:67 participation/views.py:108
msgid "You are already in a team." msgid "You are already in a team."
msgstr "Vous êtes déjà dans une équipe." msgstr "Vous êtes déjà dans une équipe."
#: participation/views.py:96 tfjm/templates/base.html:74 #: participation/views.py:97 tfjm/templates/base.html:74
#: tfjm/templates/navbar.html:40 #: tfjm/templates/navbar.html:40
msgid "Join team" msgid "Join team"
msgstr "Rejoindre une équipe" msgstr "Rejoindre une équipe"
#: participation/views.py:156 participation/views.py:473 #: participation/views.py:157 participation/views.py:474
msgid "You don't participate, so you don't have any team." msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
#: participation/views.py:182 #: participation/views.py:183
#, python-brace-format #, python-brace-format
msgid "Detail of team {trigram}" msgid "Detail of team {trigram}"
msgstr "Détails de l'équipe {trigram}" msgstr "Détails de l'équipe {trigram}"
#: participation/views.py:211 #: participation/views.py:212
msgid "You don't participate, so you can't request the validation of the team." msgid "You don't participate, so you can't request the validation of the team."
msgstr "" msgstr ""
"Vous ne participez pas, vous ne pouvez pas demander la validation de " "Vous ne participez pas, vous ne pouvez pas demander la validation de "
"l'équipe." "l'équipe."
#: participation/views.py:214 #: participation/views.py:215
msgid "The validation of the team is already done or pending." msgid "The validation of the team is already done or pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." msgstr "La validation de l'équipe est déjà faite ou en cours."
#: participation/views.py:217 #: participation/views.py:218
msgid "" msgid ""
"The team can't be validated: missing email address confirmations, " "The team can't be validated: missing email address confirmations, "
"authorizations, people, motivation letter or the tournament is not set." "authorizations, people, motivation letter or the tournament is not set."
@ -1690,114 +1740,123 @@ msgstr ""
"d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre " "d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre "
"de motivation, soit le tournoi n'a pas été choisi." "de motivation, soit le tournoi n'a pas été choisi."
#: participation/views.py:239 #: participation/views.py:240
msgid "You are not an organizer of the tournament." msgid "You are not an organizer of the tournament."
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi." msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
#: participation/views.py:242 #: participation/views.py:243
msgid "This team has no pending validation." msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente." msgstr "L'équipe n'a pas de validation en attente."
#: participation/views.py:269 #: participation/views.py:270
msgid "You must specify if you validate the registration or not." msgid "You must specify if you validate the registration or not."
msgstr "Vous devez spécifier si vous validez l'inscription ou non." msgstr "Vous devez spécifier si vous validez l'inscription ou non."
#: participation/views.py:304 #: participation/views.py:305
#, python-brace-format #, python-brace-format
msgid "Update team {trigram}" msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {trigram}" msgstr "Mise à jour de l'équipe {trigram}"
#: participation/views.py:365 participation/views.py:421 #: participation/views.py:366 participation/views.py:422
#, python-brace-format #, python-brace-format
msgid "Motivation letter of {team}.{ext}" msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}" msgstr "Lettre de motivation de {team}.{ext}"
#: participation/views.py:396 #: participation/views.py:397
#, python-brace-format #, python-brace-format
msgid "Photo authorization of {participant}.{ext}" msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}" msgstr "Autorisation de droit à l'image de {participant}.{ext}"
#: participation/views.py:402 #: participation/views.py:403
#, python-brace-format #, python-brace-format
msgid "Parental authorization of {participant}.{ext}" msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}" msgstr "Autorisation parentale de {participant}.{ext}"
#: participation/views.py:409 #: participation/views.py:410
#, python-brace-format #, python-brace-format
msgid "Health sheet of {participant}.{ext}" msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}" msgstr "Fiche sanitaire de {participant}.{ext}"
#: participation/views.py:415 #: participation/views.py:416
#, python-brace-format #, python-brace-format
msgid "Vaccine sheet of {participant}.{ext}" msgid "Vaccine sheet of {participant}.{ext}"
msgstr "Carnet de vaccination de {participant}.{ext}" msgstr "Carnet de vaccination de {participant}.{ext}"
#: participation/views.py:425 #: participation/views.py:426
#, python-brace-format #, python-brace-format
msgid "Photo authorizations of team {trigram}.zip" msgid "Photo authorizations of team {trigram}.zip"
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip" msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
#: participation/views.py:487 #: participation/views.py:488
msgid "The team is not validated yet." msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée." msgstr "L'équipe n'est pas encore validée."
#: participation/views.py:501 #: participation/views.py:502
#, python-brace-format #, python-brace-format
msgid "Participation of team {trigram}" msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}" msgstr "Participation de l'équipe {trigram}"
#: participation/views.py:582 #: participation/views.py:584
#, python-brace-format #, python-brace-format
msgid "Payments of {tournament}" msgid "Payments of {tournament}"
msgstr "Paiements de {tournament}" msgstr "Paiements de {tournament}"
#: participation/views.py:663 #: participation/views.py:657
msgid "Notes published!"
msgstr "Notes publiées !"
#: participation/views.py:693
msgid "You can't upload a solution after the deadline." msgid "You can't upload a solution after the deadline."
msgstr "Vous ne pouvez pas envoyer de solution après la date limite." msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
#: participation/views.py:771 #: participation/views.py:802
#, python-brace-format #, python-brace-format
msgid "Solutions for pool {pool} of tournament {tournament}.zip" msgid "Solutions for pool {pool} of tournament {tournament}.zip"
msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip" msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:772 #: participation/views.py:803
#, python-brace-format #, python-brace-format
msgid "Syntheses for pool {pool} of tournament {tournament}.zip" msgid "Syntheses for pool {pool} of tournament {tournament}.zip"
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip" msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:798 #: participation/views.py:833
#, python-brace-format #, python-brace-format
msgid "Jury of pool {pool} for {tournament} with teams {teams}" msgid "Jury of pool {pool} for {tournament} with teams {teams}"
msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}" msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}"
#: participation/views.py:814 #: participation/views.py:849
#, python-brace-format #, python-brace-format
msgid "The jury {name} is already in the pool!" msgid "The jury {name} is already in the pool!"
msgstr "{name} est déjà dans la poule !" msgstr "{name} est déjà dans la poule !"
#: participation/views.py:834 #: participation/views.py:869
msgid "New TFJM² jury account" msgid "New TFJM² jury account"
msgstr "Nouveau compte de juré⋅e pour le TFJM²" msgstr "Nouveau compte de juré⋅e pour le TFJM²"
#: participation/views.py:851 #: participation/views.py:886
#, python-brace-format #, python-brace-format
msgid "The jury {name} has been successfully added!" msgid "The jury {name} has been successfully added!"
msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !" msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !"
#: participation/views.py:883 #: participation/views.py:921
#, python-brace-format #, python-brace-format
msgid "The jury {name} has been successfully removed!" msgid "The jury {name} has been successfully removed!"
msgstr "{name} a été retiré⋅e avec succès du jury !" msgstr "{name} a été retiré⋅e avec succès du jury !"
#: participation/views.py:911 #: participation/views.py:947
#, python-brace-format
msgid "The jury {name} has been successfully promoted president!"
msgstr "{name} a été nommé⋅e président⋅e du jury !"
#: participation/views.py:975
msgid "The following user is not registered as a jury:" msgid "The following user is not registered as a jury:"
msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :" msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :"
#: participation/views.py:925 #: participation/views.py:989
msgid "Notes were successfully uploaded." msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées." msgstr "Les notes ont bien été envoyées."
#: participation/views.py:1589 #: participation/views.py:1672
msgid "You can't upload a synthesis after the deadline." msgid "You can't upload a synthesis after the deadline."
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite." msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."

View File

@ -43,7 +43,7 @@ class SynthesisInline(admin.TabularInline):
class PoolInline(admin.TabularInline): class PoolInline(admin.TabularInline):
model = Pool model = Pool
extra = 0 extra = 0
autocomplete_fields = ('tournament', 'participations', 'juries',) autocomplete_fields = ('tournament', 'participations', 'jury_president', 'juries',)
show_change_link = True show_change_link = True
@ -100,10 +100,10 @@ class ParticipationAdmin(admin.ModelAdmin):
@admin.register(Pool) @admin.register(Pool)
class PoolAdmin(admin.ModelAdmin): class PoolAdmin(admin.ModelAdmin):
list_display = ('__str__', 'tournament', 'round', 'letter', 'teams',) list_display = ('__str__', 'tournament', 'round', 'letter', 'teams', 'jury_president',)
list_filter = ('tournament', 'round', 'letter',) list_filter = ('tournament', 'round', 'letter',)
search_fields = ('participations__team__name', 'participations__team__trigram',) search_fields = ('participations__team__name', 'participations__team__trigram',)
autocomplete_fields = ('tournament', 'participations', 'juries',) autocomplete_fields = ('tournament', 'participations', 'jury_president', 'juries',)
inlines = (PassageInline, TweakInline,) inlines = (PassageInline, TweakInline,)
@admin.display(description=_("teams")) @admin.display(description=_("teams"))

View File

@ -178,8 +178,13 @@ class SolutionForm(forms.ModelForm):
class PoolForm(forms.ModelForm): class PoolForm(forms.ModelForm):
class Meta: class Meta:
model = Pool model = Pool
fields = ('tournament', 'round', 'letter', 'bbb_url', 'results_available', 'juries',) fields = ('tournament', 'round', 'letter', 'bbb_url', 'results_available', 'jury_president', 'juries',)
widgets = { widgets = {
"jury_president": forms.Select(attrs={
'class': 'selectpicker',
'data-live-search': 'true',
'data-live-search-normalize': 'true',
}),
"juries": forms.SelectMultiple(attrs={ "juries": forms.SelectMultiple(attrs={
'class': 'selectpicker', 'class': 'selectpicker',
'data-live-search': 'true', 'data-live-search': 'true',
@ -218,19 +223,19 @@ class AddJuryForm(forms.ModelForm):
Div( Div(
Div( Div(
Field('email', autofocus="autofocus", list="juries-email"), Field('email', autofocus="autofocus", list="juries-email"),
css_class='col-md-5', css_class='col-md-5 px-1',
), ),
Div( Div(
Field('first_name', list="juries-first-name"), Field('first_name', list="juries-first-name"),
css_class='col-md-3', css_class='col-md-3 px-1',
), ),
Div( Div(
Field('last_name', list="juries-last-name"), Field('last_name', list="juries-last-name"),
css_class='col-md-3', css_class='col-md-3 px-1',
), ),
Div( Div(
Submit('submit', _("Add")), Submit('submit', _("Add")),
css_class='col-md-1 py-md-4', css_class='col-md-1 py-md-4 px-1',
), ),
css_class='row', css_class='row',
) )

View File

@ -0,0 +1,27 @@
# Generated by Django 5.0.2 on 2024-03-24 14:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("participation", "0008_alter_participation_options"),
("registration", "0012_payment_token_alter_payment_type"),
]
operations = [
migrations.AddField(
model_name="pool",
name="jury_president",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="pools_presided",
to="registration.volunteerregistration",
verbose_name="president of the jury",
),
),
]

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.db import models from django.db import models
from django.db.models import Index from django.db.models import F, Index, Q
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
@ -543,6 +543,15 @@ class Pool(models.Model):
verbose_name=_("juries"), verbose_name=_("juries"),
) )
jury_president = models.ForeignKey(
VolunteerRegistration,
on_delete=models.SET_NULL,
null=True,
default=None,
related_name="pools_presided",
verbose_name=_("president of the jury"),
)
bbb_url = models.CharField( bbb_url = models.CharField(
max_length=255, max_length=255,
blank=True, blank=True,
@ -573,6 +582,11 @@ class Pool(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("participation:pool_detail", args=(self.pk,)) return reverse_lazy("participation:pool_detail", args=(self.pk,))
def validate_constraints(self, exclude=None):
if self.jury_president not in self.juries.all():
raise ValidationError({'jury_president': _("The president of the jury must be part of the jury.")})
return super().validate_constraints()
def __str__(self): def __str__(self):
return _("Pool of day {round} for tournament {tournament} with teams {teams}")\ return _("Pool of day {round} for tournament {tournament} with teams {teams}")\
.format(round=self.round, .format(round=self.round,
@ -802,6 +816,10 @@ class Solution(models.Model):
unique=True, unique=True,
) )
@property
def tournament(self):
return Tournament.final_tournament() if self.final_solution else self.participation.tournament
def __str__(self): def __str__(self):
return _("Solution of team {team} for problem {problem}")\ return _("Solution of team {team} for problem {problem}")\
.format(team=self.participation.team.name, problem=self.problem)\ .format(team=self.participation.team.name, problem=self.problem)\
@ -940,6 +958,9 @@ class Note(models.Model):
def modal_name(self): def modal_name(self):
return f"updateNotes{self.pk}" return f"updateNotes{self.pk}"
def has_any_note(self):
return any(self.get_all())
def __str__(self): def __str__(self):
return _("Notes of {jury} for {passage}").format(jury=self.jury, passage=self.passage) return _("Notes of {jury} for {passage}").format(jury=self.jury, passage=self.passage)

View File

@ -4,23 +4,53 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="alert alert-info">
<p>
{% blocktrans trimmed %}
On this page, you can manage the juries of the pool. You can add a new jury by entering the email address
of the jury. If the jury is not registered, the account will be created automatically. If the jury already
exists, its account will be autocompleted and directly linked to the pool.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
On this page, you can also define the president of the jury, who will have the right to see all solutions
and if necessary define the notes of other jury members.
{% endblocktrans %}
</p>
</div>
<hr> <hr>
{% for jury in pool.juries.all %} {% for jury in pool.juries.all %}
<div class="row my-3"> <div class="row my-3 px-0">
<div class="col-md-5"> <div class="col-md-5 px-1">
<input type="email" class="form-control" value="{{ jury.user.email }}" disabled> <input type="email" class="form-control" value="{{ jury.user.email }}" disabled>
</div> </div>
<div class="col-md-3"> <div class="col-md-3 px-1">
<input type="text" class="form-control" value="{{ jury.user.first_name }}" disabled> <input type="text" class="form-control" value="{{ jury.user.first_name }}" disabled>
</div> </div>
<div class="col-md-3"> <div class="col-md-3 px-1">
<input type="text" class="form-control" value="{{ jury.user.last_name }}" disabled> <input type="text" class="form-control" value="{{ jury.user.last_name }}" disabled>
</div> </div>
<div class="col-md-1"> <div class="col-md-1 px-1">
<a href="{% url 'participation:pool_remove_jury' pk=pool.pk jury_id=jury.id %}" class="btn btn-danger"> <div class="btn-group-vertical btn-group-sm">
Retirer {% if jury == pool.jury_president %}
</a> <button class="btn btn-success">
<i class="fas fa-crown"></i> {% trans "PoJ" %}
</button>
{% else %}
<a href="{% url 'participation:pool_preside' pk=pool.pk jury_id=jury.id %}"
class="btn btn-warning">
<i class="fas fa-crown"></i> {% trans "Preside" %}
</a>
{% endif %}
<a href="{% url 'participation:pool_remove_jury' pk=pool.pk jury_id=jury.id %}"
class="btn btn-danger">
<i class="fas fa-trash"></i> {% trans "Remove" %}
</a>
</div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View File

@ -111,6 +111,26 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% if not available_notes_1 or not available_notes_2 %}
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
<div class="card-footer text-center">
<div class="btn-group">
{% if not available_notes_1 %}
<a href="{% url 'participation:tournament_publish_notes' pk=tournament.pk round=1 %}" class="btn btn-info">
<i class="fas fa-upload"></i>
{% trans "Publish notes for first round" %}
</a>
{% endif %}
{% if not available_notes_2 %}
<a href="{% url 'participation:tournament_publish_notes' pk=tournament.pk round=2 %}" class="btn btn-info">
<i class="fas fa-upload"></i>
{% trans "Publish notes for second round" %}
</a>
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
</div> </div>
{% endif %} {% endif %}

View File

@ -7,10 +7,11 @@ from django.views.generic import TemplateView
from .views import CreateTeamView, FinalNotationSheetTemplateView, JoinTeamView, MyParticipationDetailView, \ from .views import CreateTeamView, FinalNotationSheetTemplateView, JoinTeamView, MyParticipationDetailView, \
MyTeamDetailView, NoteUpdateView, ParticipationDetailView, PassageCreateView, PassageDetailView, \ MyTeamDetailView, NoteUpdateView, ParticipationDetailView, PassageCreateView, PassageDetailView, \
PassageUpdateView, PoolCreateView, PoolDetailView, PoolDownloadView, PoolJuryView, PoolNotesTemplateView, \ PassageUpdateView, PoolCreateView, PoolDetailView, PoolDownloadView, PoolJuryView, PoolNotesTemplateView, \
PoolRemoveJuryView, PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, ScaleNotationSheetTemplateView, \ PoolPresideJuryView, PoolRemoveJuryView, PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, \
SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \ ScaleNotationSheetTemplateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, TeamDetailView, \
TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, \ TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \
TournamentExportCSVView, TournamentListView, TournamentPaymentsView, TournamentUpdateView TournamentDetailView, TournamentExportCSVView, TournamentListView, TournamentPaymentsView, \
TournamentPublishNotesView, TournamentUpdateView
app_name = "participation" app_name = "participation"
@ -35,6 +36,8 @@ urlpatterns = [
path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"), path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"),
path("tournament/<int:pk>/payments/", TournamentPaymentsView.as_view(), name="tournament_payments"), path("tournament/<int:pk>/payments/", TournamentPaymentsView.as_view(), name="tournament_payments"),
path("tournament/<int:pk>/csv/", TournamentExportCSVView.as_view(), name="tournament_csv"), path("tournament/<int:pk>/csv/", TournamentExportCSVView.as_view(), name="tournament_csv"),
path("tournament/<int:pk>/publish-notes/<int:round>/", TournamentPublishNotesView.as_view(),
name="tournament_publish_notes"),
path("pools/create/", PoolCreateView.as_view(), name="pool_create"), path("pools/create/", PoolCreateView.as_view(), name="pool_create"),
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"), path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"), path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
@ -45,6 +48,7 @@ urlpatterns = [
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"), path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
path("pools/<int:pk>/jury/", PoolJuryView.as_view(), name="pool_jury"), path("pools/<int:pk>/jury/", PoolJuryView.as_view(), name="pool_jury"),
path("pools/<int:pk>/jury/remove/<int:jury_id>/", PoolRemoveJuryView.as_view(), name="pool_remove_jury"), path("pools/<int:pk>/jury/remove/<int:jury_id>/", PoolRemoveJuryView.as_view(), name="pool_remove_jury"),
path("pools/<int:pk>/jury/preside/<int:jury_id>/", PoolPresideJuryView.as_view(), name="pool_preside"),
path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"), path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"),
path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"), path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"),
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"), path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),

View File

@ -26,6 +26,7 @@ from django.utils import timezone
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import FormMixin, ProcessFormView from django.views.generic.edit import FormMixin, ProcessFormView
from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView
from magic import Magic from magic import Magic
@ -543,13 +544,6 @@ class TournamentDetailView(MultiTableMixin, DetailView):
""" """
model = Tournament model = Tournament
def dispatch(self, request, *args, **kwargs):
self.tables = [
ParticipationTable(self.get_object().participations.all()),
PoolTable(self.get_object().pools.order_by('id').all()),
]
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
tables = context['tables'] tables = context['tables']
@ -565,9 +559,17 @@ class TournamentDetailView(MultiTableMixin, DetailView):
if note: if note:
notes[participation] = note notes[participation] = note
context["notes"] = sorted(notes.items(), key=lambda x: x[1], reverse=True) context["notes"] = sorted(notes.items(), key=lambda x: x[1], reverse=True)
context["available_notes_1"] = all(pool.results_available for pool in self.object.pools.filter(round=1).all())
context["available_notes_2"] = all(pool.results_available for pool in self.object.pools.filter(round=2).all())
return context return context
def get_tables(self):
return [
ParticipationTable(self.object.participations.all()),
PoolTable(self.object.pools.order_by('id').all()),
]
class TournamentPaymentsView(VolunteerMixin, SingleTableMixin, DetailView): class TournamentPaymentsView(VolunteerMixin, SingleTableMixin, DetailView):
""" """
@ -631,6 +633,34 @@ class TournamentExportCSVView(VolunteerMixin, DetailView):
return resp return resp
class TournamentPublishNotesView(VolunteerMixin, SingleObjectMixin, RedirectView):
"""
Publish notes of a tournament for a given round.
"""
model = Tournament
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
tournament = self.get_object()
reg = request.user.registration
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
if int(kwargs["round"]) not in (1, 2):
raise Http404
tournament = Tournament.objects.get(pk=kwargs["pk"])
tournament.pools.filter(round=kwargs["round"]).update(results_available=True)
messages.success(request, _("Notes published!"))
return super().get(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs):
return reverse_lazy("participation:tournament_detail", args=(kwargs['pk'],))
class SolutionUploadView(LoginRequiredMixin, FormView): class SolutionUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_solution.html" template_name = "participation/upload_solution.html"
form_class = SolutionForm form_class = SolutionForm
@ -721,8 +751,7 @@ class PoolUpdateView(VolunteerMixin, UpdateView):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self.handle_no_permission() return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \ if request.user.registration.is_admin or request.user.registration.is_volunteer \
and (self.get_object().tournament in request.user.registration.organized_tournaments.all() and self.get_object().tournament in request.user.registration.organized_tournaments.all():
or request.user.registration in self.get_object().juries.all()):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
@ -750,9 +779,11 @@ class PoolDownloadView(VolunteerMixin, DetailView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self.handle_no_permission() return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \ reg = request.user.registration
and (self.get_object().tournament in request.user.registration.organized_tournaments.all() if reg.is_admin or reg.is_volunteer \
or request.user.registration in self.get_object().juries.all()): and (self.get_object().tournament in reg.organized_tournaments.all()
or reg in self.get_object().juries.all()
or reg.pools_presided.filter(tournament=self.get_object().tournament).exists()):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
@ -785,12 +816,16 @@ class PoolJuryView(VolunteerMixin, FormView, DetailView):
form_class = AddJuryForm form_class = AddJuryForm
template_name = 'participation/pool_jury.html' template_name = 'participation/pool_jury.html'
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated: self.object = self.get_object()
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \ if request.user.is_authenticated and \
and self.get_object().tournament in request.user.registration.organized_tournaments.all(): (request.user.registration.is_admin or request.user.registration.is_volunteer
and (self.object.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration == self.object.jury_president)):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -866,11 +901,14 @@ class PoolRemoveJuryView(VolunteerMixin, DetailView):
model = Pool model = Pool
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated: self.object = self.get_object()
return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \ if request.user.is_authenticated and \
and self.get_object().tournament in request.user.registration.organized_tournaments.all(): (request.user.registration.is_admin or request.user.registration.is_volunteer
and (self.object.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration == self.object.jury_president)):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -885,6 +923,32 @@ class PoolRemoveJuryView(VolunteerMixin, DetailView):
return redirect(reverse_lazy('participation:pool_jury', args=(pool.pk,))) return redirect(reverse_lazy('participation:pool_jury', args=(pool.pk,)))
class PoolPresideJuryView(VolunteerMixin, DetailView):
model = Pool
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if request.user.is_authenticated and \
(request.user.registration.is_admin or request.user.registration.is_volunteer
and (self.object.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration == self.object.jury_president)):
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
def get(self, request, *args, **kwargs):
pool = self.get_object()
if not pool.juries.filter(pk=kwargs['jury_id']).exists():
raise Http404
jury = pool.juries.get(pk=kwargs['jury_id'])
pool.jury_president = jury
pool.save()
messages.success(request, _("The jury {name} has been successfully promoted president!")
.format(name=f"{jury.user.first_name} {jury.user.last_name}"))
return redirect(reverse_lazy('participation:pool_jury', args=(pool.pk,)))
class PoolUploadNotesView(VolunteerMixin, FormView, DetailView): class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
model = Pool model = Pool
form_class = UploadNotesForm form_class = UploadNotesForm
@ -896,7 +960,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
if request.user.is_authenticated and \ if request.user.is_authenticated and \
(request.user.registration.is_admin or request.user.registration.is_volunteer (request.user.registration.is_admin or request.user.registration.is_volunteer
and (self.object.tournament in request.user.registration.organized_tournaments.all() and (self.object.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration in self.object.juries.all())): or request.user.registration == self.object.jury_president)):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
@ -935,6 +999,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
""" """
model = Pool model = Pool
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if request.user.is_authenticated and \
(request.user.registration.is_admin or request.user.registration.is_volunteer
and (self.object.tournament in request.user.registration.organized_tournaments.all()
or request.user.registration == self.object.jury_president)):
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
def render_to_response(self, context, **response_kwargs): # noqa: C901 def render_to_response(self, context, **response_kwargs): # noqa: C901
pool_size = self.object.passages.count() pool_size = self.object.passages.count()
passage_width = 7 if pool_size == 4 else 6 passage_width = 7 if pool_size == 4 else 6
@ -1513,26 +1588,34 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self.handle_no_permission() return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \ reg = request.user.registration
and (self.get_object().pool.tournament in request.user.registration.organized_tournaments.all() passage = self.get_object()
or request.user.registration in self.get_object().pool.juries.all()) \ if reg.is_admin or reg.is_volunteer \
or request.user.registration.participates and request.user.registration.team \ and (self.get_object().pool.tournament in reg.organized_tournaments.all()
and request.user.registration.team.participation in [self.get_object().defender, or reg in passage.pool.juries.all()
self.get_object().opponent, or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \
self.get_object().reporter]: or reg.participates and reg.team \
and reg.team.participation in [passage.defender, passage.opponent, passage.reporter, passage.observer]:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if self.request.user.registration in self.object.pool.juries.all(): reg = self.request.user.registration
if reg in self.object.pool.juries.all():
context["my_note"] = Note.objects.get_or_create(passage=self.object, jury=self.request.user.registration)[0] context["my_note"] = Note.objects.get_or_create(passage=self.object, jury=self.request.user.registration)[0]
context["notes"] = NoteTable(self.object.notes.all()) if reg.is_volunteer:
elif self.request.user.registration.is_admin: notes = self.object.notes.all()
context["notes"] = NoteTable(self.object.notes.all()) if not reg.is_admin \
if 'notes' in context and not self.object.observer: or (reg != self.object.pool.jury_president
and reg not in self.object.pool.tournament.organizers.all()):
notes = [note for note in notes if note.has_any_note()]
context["notes"] = NoteTable(notes)
# Only display the observer column for 4-teams pools # Only display the observer column for 4-teams pools
context['notes']._sequence.pop() context['notes']._sequence.remove('observer_oral')
if 'notes' in context and not self.request.user.registration.is_admin:
context['notes']._sequence.remove('update')
return context return context
@ -1611,8 +1694,9 @@ class NoteUpdateView(VolunteerMixin, UpdateView):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self.handle_no_permission() return self.handle_no_permission()
if request.user.registration.is_admin or request.user.registration.is_volunteer \ reg = request.user.registration
and self.get_object().jury == request.user.registration: note = self.get_object()
if reg.is_admin or reg.is_volunteer and (note.jury == reg or note.passage.pool.jury_president == reg):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()

View File

@ -797,9 +797,10 @@ class SolutionView(LoginRequiredMixin, View):
else: else:
passage_participant_qs = Passage.objects.none() passage_participant_qs = Passage.objects.none()
if not (user.registration.is_admin if not (user.registration.is_admin
or user.registration.is_volunteer and user.registration or (user.registration.is_volunteer
in (solution.participation.tournament and user.registration in solution.tournament.organizers.all())
if not solution.final_solution else Tournament.final_tournament()).organizers.all() or (user.registration.is_volunteer
and user.registration.presided_pools.filter(tournament=solution.tournament).exists())
or user.registration.is_volunteer or user.registration.is_volunteer
and Passage.objects.filter(Q(pool__juries=user.registration) and Passage.objects.filter(Q(pool__juries=user.registration)
| Q(pool__tournament__in=user.registration.organized_tournaments.all()), | Q(pool__tournament__in=user.registration.organized_tournaments.all()),
@ -834,7 +835,8 @@ class SynthesisView(LoginRequiredMixin, View):
user = request.user user = request.user
if not (user.registration.is_admin or user.registration.is_volunteer if not (user.registration.is_admin or user.registration.is_volunteer
and (user.registration in synthesis.passage.pool.juries.all() and (user.registration in synthesis.passage.pool.juries.all()
or user.registration in synthesis.passage.pool.tournament.organizers.all()) or user.registration in synthesis.passage.pool.tournament.organizers.all()
or user.registration.presided_pools.filter(tournament=synthesis.passage.pool.tournament).exists())
or user.registration.participates and user.registration.team == synthesis.participation.team): or user.registration.participates and user.registration.team == synthesis.participation.team):
raise PermissionDenied raise PermissionDenied
# Guess mime type of the file # Guess mime type of the file