Update GSheets for ETEAM

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-07-05 16:48:17 +02:00
parent 44302a9ff4
commit d13ae89267
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
5 changed files with 577 additions and 303 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-07-05 11:45+0200\n" "POT-Creation-Date: 2024-07-05 16:44+0200\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"
@ -78,8 +78,8 @@ msgstr "Type de permission nécessaire pour écrire un message dans un canal."
#: chat/models.py:62 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 #: chat/models.py:62 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88
#: draw/models.py:27 participation/admin.py:79 participation/admin.py:144 #: draw/models.py:27 participation/admin.py:79 participation/admin.py:144
#: participation/admin.py:176 participation/models.py:727 #: participation/admin.py:176 participation/models.py:781
#: participation/models.py:751 participation/models.py:1060 #: participation/models.py:805 participation/models.py:1114
#: registration/models.py:763 #: registration/models.py:763
#: registration/templates/registration/payment_form.html:53 #: registration/templates/registration/payment_form.html:53
msgid "tournament" msgid "tournament"
@ -95,7 +95,7 @@ msgstr ""
#: chat/models.py:73 draw/models.py:446 draw/models.py:473 #: chat/models.py:73 draw/models.py:446 draw/models.py:473
#: participation/admin.py:140 participation/admin.py:160 #: participation/admin.py:140 participation/admin.py:160
#: participation/models.py:1563 participation/models.py:1572 #: participation/models.py:1642 participation/models.py:1651
#: participation/tables.py:84 #: participation/tables.py:84
msgid "pool" msgid "pool"
msgstr "poule" msgstr "poule"
@ -109,7 +109,7 @@ msgstr ""
#: chat/models.py:84 draw/templates/draw/tournament_content.html:277 #: chat/models.py:84 draw/templates/draw/tournament_content.html:277
#: participation/admin.py:172 participation/models.py:261 #: participation/admin.py:172 participation/models.py:261
#: participation/models.py:742 #: participation/models.py:796
#: participation/templates/participation/tournament_harmonize.html:15 #: participation/templates/participation/tournament_harmonize.html:15
#: registration/models.py:158 registration/models.py:754 #: registration/models.py:158 registration/models.py:754
#: registration/tables.py:39 #: registration/tables.py:39
@ -265,11 +265,11 @@ msgid "teams"
msgstr "équipes" msgstr "équipes"
#: draw/admin.py:92 draw/models.py:245 draw/models.py:465 #: draw/admin.py:92 draw/models.py:245 draw/models.py:465
#: participation/models.py:1064 #: participation/models.py:1118
msgid "round" msgid "round"
msgstr "tour" msgstr "tour"
#: draw/apps.py:10 draw/consumers.py:1037 tfjm/templates/navbar.html:68 #: draw/apps.py:10 draw/consumers.py:1038 tfjm/templates/navbar.html:68
msgid "Draw" msgid "Draw"
msgstr "Tirage au sort" msgstr "Tirage au sort"
@ -309,9 +309,9 @@ msgstr "Le tirage au sort du tournoi {tournament} a commencé !"
msgid "The draw for the tournament {tournament} will start." msgid "The draw for the tournament {tournament} will start."
msgstr "Le tirage au sort du tournoi {tournament} va commencer." msgstr "Le tirage au sort du tournoi {tournament} va commencer."
#: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:691 #: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:692
#: draw/consumers.py:909 draw/consumers.py:999 draw/consumers.py:1021 #: draw/consumers.py:910 draw/consumers.py:1000 draw/consumers.py:1022
#: draw/consumers.py:1112 draw/templates/draw/tournament_content.html:5 #: draw/consumers.py:1113 draw/templates/draw/tournament_content.html:5
msgid "The draw has not started yet." msgid "The draw has not started yet."
msgstr "Le tirage au sort n'a pas encore commencé." msgstr "Le tirage au sort n'a pas encore commencé."
@ -320,8 +320,8 @@ msgstr "Le tirage au sort n'a pas encore commencé."
msgid "The draw for the tournament {tournament} is aborted." msgid "The draw for the tournament {tournament} is aborted."
msgstr "Le tirage au sort du tournoi {tournament} est annulé." msgstr "Le tirage au sort du tournoi {tournament} est annulé."
#: draw/consumers.py:309 draw/consumers.py:330 draw/consumers.py:625 #: draw/consumers.py:309 draw/consumers.py:330 draw/consumers.py:626
#: draw/consumers.py:696 draw/consumers.py:914 #: draw/consumers.py:697 draw/consumers.py:915
msgid "This is not the time for this." msgid "This is not the time for this."
msgstr "Ce n'est pas le moment pour cela." msgstr "Ce n'est pas le moment pour cela."
@ -362,21 +362,21 @@ msgstr ""
"de dés, par ordre croissant. Pour le deuxième tour, les ordres de passage " "de dés, par ordre croissant. Pour le deuxième tour, les ordres de passage "
"sont déterminés à partir des ordres de passage du premier tour." "sont déterminés à partir des ordres de passage du premier tour."
#: draw/consumers.py:614 draw/consumers.py:754 draw/consumers.py:831 #: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:832
#: draw/consumers.py:865 draw/consumers.py:990 draw/consumers.py:1088 #: draw/consumers.py:866 draw/consumers.py:991 draw/consumers.py:1089
msgid "Your turn!" msgid "Your turn!"
msgstr "À votre tour !" msgstr "À votre tour !"
#: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:991 #: draw/consumers.py:616 draw/consumers.py:756 draw/consumers.py:992
#: draw/consumers.py:1089 #: draw/consumers.py:1090
msgid "It's your turn to draw a problem!" msgid "It's your turn to draw a problem!"
msgstr "C'est à vous de tirer un problème !" msgstr "C'est à vous de tirer un problème !"
#: draw/consumers.py:635 draw/consumers.py:706 draw/consumers.py:924 #: draw/consumers.py:636 draw/consumers.py:707 draw/consumers.py:925
msgid "This is not your turn." msgid "This is not your turn."
msgstr "Ce n'est pas votre tour." msgstr "Ce n'est pas votre tour."
#: draw/consumers.py:713 #: draw/consumers.py:714
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"The team <strong>{trigram}</strong> accepted the problem <string>{problem}</" "The team <strong>{trigram}</strong> accepted the problem <string>{problem}</"
@ -385,33 +385,33 @@ msgstr ""
"L'équipe <strong>{trigram}</strong> a accepté le problème <strong>{problem}</" "L'équipe <strong>{trigram}</strong> a accepté le problème <strong>{problem}</"
"strong> : {problem_name}. " "strong> : {problem_name}. "
#: draw/consumers.py:717 #: draw/consumers.py:718
msgid "One team more can accept this problem." msgid "One team more can accept this problem."
msgstr "Une équipe de plus peut accepter ce problème." msgstr "Une équipe de plus peut accepter ce problème."
#: draw/consumers.py:719 #: draw/consumers.py:720
msgid "No team can accept this problem anymore." msgid "No team can accept this problem anymore."
msgstr "Aucune autre équipe ne peut accepter ce problème." msgstr "Aucune autre équipe ne peut accepter ce problème."
#: draw/consumers.py:813 #: draw/consumers.py:814
#, python-brace-format #, python-brace-format
msgid "The draw of the pool {pool} is ended. The summary is below." msgid "The draw of the pool {pool} is ended. The summary is below."
msgstr "Le tirage de la poule {pool} est terminé. Le résumé est ci-dessous." msgstr "Le tirage de la poule {pool} est terminé. Le résumé est ci-dessous."
#: draw/consumers.py:832 draw/consumers.py:866 #: draw/consumers.py:833 draw/consumers.py:867
msgid "It's your turn to launch the dice!" msgid "It's your turn to launch the dice!"
msgstr "C'est à vous de lancer le dé !" msgstr "C'est à vous de lancer le dé !"
#: draw/consumers.py:852 #: draw/consumers.py:853
#, python-brace-format #, python-brace-format
msgid "The draw of the round {round} is ended." msgid "The draw of the round {round} is ended."
msgstr "Le tirage au sort du tour {round} est annulé." msgstr "Le tirage au sort du tour {round} est annulé."
#: draw/consumers.py:895 #: draw/consumers.py:896
msgid "The draw of the first round is ended." msgid "The draw of the first round is ended."
msgstr "Le tirage au sort du premier tour est terminé." msgstr "Le tirage au sort du premier tour est terminé."
#: draw/consumers.py:938 #: draw/consumers.py:939
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"The team <strong>{trigram}</strong> refused the problem <strong>{problem}</" "The team <strong>{trigram}</strong> refused the problem <strong>{problem}</"
@ -420,26 +420,26 @@ msgstr ""
"L'équipe <strong>{trigram}</strong> a refusé le problème <strong>{problem}</" "L'équipe <strong>{trigram}</strong> a refusé le problème <strong>{problem}</"
"strong> : {problem_name}." "strong> : {problem_name}."
#: draw/consumers.py:942 #: draw/consumers.py:943
#, python-brace-format #, python-brace-format
msgid "It remains {remaining} refusals without penalty." msgid "It remains {remaining} refusals without penalty."
msgstr "Il reste {remaining} refus sans pénalité." msgstr "Il reste {remaining} refus sans pénalité."
#: draw/consumers.py:945 #: draw/consumers.py:946
msgid "This problem was already refused by this team." msgid "This problem was already refused by this team."
msgstr "Ce problème a déjà été refusé par cette équipe." msgstr "Ce problème a déjà été refusé par cette équipe."
#: draw/consumers.py:947 #: draw/consumers.py:948
msgid "It adds a 25% penalty on the coefficient of the oral defense." msgid "It adds a 25% penalty on the coefficient of the oral defense."
msgstr "" msgstr ""
"Cela ajoute une pénalité de 25&nbsp;% sur le coefficient de l'oral de la " "Cela ajoute une pénalité de 25&nbsp;% sur le coefficient de l'oral de la "
"défense." "défense."
#: draw/consumers.py:1024 #: draw/consumers.py:1025
msgid "This is only available for the final tournament." msgid "This is only available for the final tournament."
msgstr "Cela n'est possible que pour la finale." msgstr "Cela n'est possible que pour la finale."
#: draw/consumers.py:1028 #: draw/consumers.py:1029
msgid "" msgid ""
"The draw of the round 2 is starting. The passage order is determined from " "The draw of the round 2 is starting. The passage order is determined from "
"the ranking of the first round, in order to mix the teams between the two " "the ranking of the first round, in order to mix the teams between the two "
@ -449,7 +449,7 @@ msgstr ""
"partir du classement du premier tour, afin de mélanger les équipes entre les " "partir du classement du premier tour, afin de mélanger les équipes entre les "
"deux jours." "deux jours."
#: draw/consumers.py:1038 #: draw/consumers.py:1039
msgid "The draw of the second round is starting!" msgid "The draw of the second round is starting!"
msgstr "Le tirage au sort du deuxième tour commence !" msgstr "Le tirage au sort du deuxième tour commence !"
@ -634,7 +634,7 @@ msgstr "Le numéro du tour doit être entre 1 et {nb}."
msgid "rounds" msgid "rounds"
msgstr "tours" msgstr "tours"
#: draw/models.py:268 participation/models.py:1072 #: draw/models.py:268 participation/models.py:1126
msgid "letter" msgid "letter"
msgstr "lettre" msgstr "lettre"
@ -672,12 +672,12 @@ msgstr "L'instance complète de la poule."
msgid "Pool {letter}{number}" msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}" msgstr "Poule {letter}{number}"
#: draw/models.py:447 participation/models.py:1564 #: draw/models.py:447 participation/models.py:1643
msgid "pools" msgid "pools"
msgstr "poules" msgstr "poules"
#: draw/models.py:459 participation/models.py:1050 participation/models.py:1754 #: draw/models.py:459 participation/models.py:1104 participation/models.py:1862
#: participation/models.py:1784 participation/models.py:1826 #: participation/models.py:1892 participation/models.py:1934
msgid "participation" msgid "participation"
msgstr "participation" msgstr "participation"
@ -701,8 +701,9 @@ 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:496 draw/models.py:519 participation/models.py:1586 #: draw/models.py:496 draw/models.py:519 participation/models.py:1234
#: participation/models.py:1791 #: participation/models.py:1665 participation/models.py:1899
#: participation/views.py:1487 participation/views.py:1752
#, python-brace-format #, python-brace-format
msgid "Problem #{problem}" msgid "Problem #{problem}"
msgstr "Problème n°{problem}" msgstr "Problème n°{problem}"
@ -913,31 +914,31 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}"
msgid "valid" msgid "valid"
msgstr "valide" msgstr "valide"
#: participation/admin.py:87 participation/models.py:763 #: participation/admin.py:87 participation/models.py:817
msgid "selected for final" 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:188 #: participation/admin.py:124 participation/admin.py:188
#: participation/models.py:1593 participation/tables.py:114 #: participation/models.py:1672 participation/tables.py:114
msgid "defender" msgid "defender"
msgstr "défenseur⋅se" msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:1600 #: participation/admin.py:128 participation/models.py:1679
#: participation/models.py:1838 #: participation/models.py:1946
msgid "opponent" msgid "opponent"
msgstr "opposant⋅e" msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:1607 #: participation/admin.py:132 participation/models.py:1686
#: participation/models.py:1839 #: participation/models.py:1947
msgid "reviewer" msgid "reviewer"
msgstr "rapporteur⋅rice" msgstr "rapporteur⋅rice"
#: participation/admin.py:136 participation/models.py:1614 #: participation/admin.py:136 participation/models.py:1693
#: participation/models.py:1840 #: participation/models.py:1948
msgid "observer" msgid "observer"
msgstr "observateur⋅rice" msgstr "observateur⋅rice"
#: participation/admin.py:192 participation/models.py:1789 #: participation/admin.py:192 participation/models.py:1897
msgid "problem" msgid "problem"
msgstr "numéro de problème" msgstr "numéro de problème"
@ -961,7 +962,7 @@ msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
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:94 participation/forms.py:366 #: participation/forms.py:94 participation/forms.py:367
#: registration/forms.py:126 registration/forms.py:148 #: registration/forms.py:126 registration/forms.py:148
#: registration/forms.py:170 registration/forms.py:192 #: registration/forms.py:170 registration/forms.py:192
#: registration/forms.py:214 registration/forms.py:236 #: registration/forms.py:214 registration/forms.py:236
@ -989,7 +990,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:178 participation/forms.py:368 #: participation/forms.py:178 participation/forms.py:369
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."
@ -1017,24 +1018,24 @@ 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:327 #: participation/forms.py:328
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:333 #: participation/forms.py:334
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:349 #: participation/forms.py:350
msgid "The defender, the opponent and the reviewer must be different." msgid "The defender, the opponent and the reviewer 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:353 #: participation/forms.py:354
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:372 #: participation/forms.py:373
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."
@ -1287,36 +1288,100 @@ msgstr "finale"
msgid "Google Sheet ID" msgid "Google Sheet ID"
msgstr "ID de la feuille Google Sheets" msgstr "ID de la feuille Google Sheets"
#: participation/models.py:728 registration/admin.py:125 #: participation/models.py:473 participation/models.py:474
#: participation/models.py:476 participation/models.py:714
msgid "Final ranking"
msgstr "Classement final"
#: participation/models.py:481 participation/models.py:551
#: participation/models.py:1309 participation/views.py:1726
msgid "Team"
msgstr "Équipe"
#: participation/models.py:481
msgid "Scores day 1"
msgstr "Scores jour 1"
#: participation/models.py:481
msgid "Tweaks day 1"
msgstr "Ajustements 1"
#: participation/models.py:481
msgid "Scores day 2"
msgstr "Scores jour 2"
#: participation/models.py:481
msgid "Tweaks day 2"
msgstr "Ajustements 2"
#: participation/models.py:482
msgid "Total D1 + D2"
msgstr "Total J1 + J2"
#: participation/models.py:482
msgid "Scores day 3"
msgstr "Scores jour 3"
#: participation/models.py:482
msgid "Tweaks day 3"
msgstr "Ajustements 3"
#: participation/models.py:484 participation/models.py:1309
#: participation/views.py:1733
msgid "Total"
msgstr "Total"
#: participation/models.py:484 participation/models.py:551
#: participation/models.py:1309
#: participation/templates/participation/tournament_harmonize.html:14
#: participation/views.py:1736
msgid "Rank"
msgstr "Rang"
#: participation/models.py:551 participation/models.py:716
msgid "Score"
msgstr "Score"
#: participation/models.py:551
msgid "Mention"
msgstr "Mention"
#: participation/models.py:696 participation/models.py:1572
msgid "Don't update the table structure for a better automated integration."
msgstr ""
"Ne pas mettre à jour la structure de la table pour une meilleure intégration "
"automatisée."
#: participation/models.py:782 registration/admin.py:125
msgid "tournaments" msgid "tournaments"
msgstr "tournois" msgstr "tournois"
#: participation/models.py:757 #: participation/models.py:811
msgid "valid team" msgid "valid team"
msgstr "équipe valide" msgstr "équipe valide"
#: participation/models.py:758 #: participation/models.py:812
msgid "The participation got the validation of the organizers." msgid "The participation got the validation of the organizers."
msgstr "La participation a été validée par les organisateur⋅rices." msgstr "La participation a été validée par les organisateur⋅rices."
#: participation/models.py:764 #: participation/models.py:818
msgid "The team is selected for the final tournament." msgid "The team is selected for the final tournament."
msgstr "L'équipe est sélectionnée pour la finale." msgstr "L'équipe est sélectionnée pour la finale."
#: participation/models.py:768 #: participation/models.py:822
msgid "mention" msgid "mention"
msgstr "mention" msgstr "mention"
#: participation/models.py:775 #: participation/models.py:829
msgid "mention (final)" msgid "mention (final)"
msgstr "Mention (pour la finale) :" msgstr "Mention (pour la finale) :"
#: participation/models.py:785 #: participation/models.py:839
#, python-brace-format #, python-brace-format
msgid "Participation of the team {name} ({trigram})" msgid "Participation of the team {name} ({trigram})"
msgstr "Participation de l'équipe {name} ({trigram})" msgstr "Participation de l'équipe {name} ({trigram})"
#: participation/models.py:792 #: participation/models.py:846
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>The team {trigram} has {nb_missing_payments} missing payments. Each " "<p>The team {trigram} has {nb_missing_payments} missing payments. Each "
@ -1329,11 +1394,11 @@ msgstr ""
"notification de bourse) pour participer au tournoi.</p><p>Les participant⋅es " "notification de bourse) pour participer au tournoi.</p><p>Les participant⋅es "
"qui n'ont pas encore payé sont : {participants}.</p>" "qui n'ont pas encore payé sont : {participants}.</p>"
#: participation/models.py:800 #: participation/models.py:854
msgid "Missing payments" msgid "Missing payments"
msgstr "Paiements manquants" msgstr "Paiements manquants"
#: participation/models.py:817 #: participation/models.py:871
msgid "" msgid ""
"<p>The solutions for the tournament of {tournament} are due on the {date:%Y-" "<p>The solutions for the tournament of {tournament} are due on the {date:%Y-"
"%m-%d %H:%M}.</p><p>You have currently sent <strong>{nb_solutions}</strong> " "%m-%d %H:%M}.</p><p>You have currently sent <strong>{nb_solutions}</strong> "
@ -1348,11 +1413,11 @@ msgstr ""
"pouvez envoyer vos solutions sur <a href='{url}'>votre page de " "pouvez envoyer vos solutions sur <a href='{url}'>votre page de "
"participation</a>.</p>" "participation</a>.</p>"
#: participation/models.py:827 participation/models.py:841 #: participation/models.py:881 participation/models.py:895
msgid "Solutions due" msgid "Solutions due"
msgstr "Rendu des solutions" msgstr "Rendu des solutions"
#: participation/models.py:833 #: participation/models.py:887
msgid "" msgid ""
"<p>The solutions for the tournament of {tournament} are due on the {date:%Y-" "<p>The solutions for the tournament of {tournament} are due on the {date:%Y-"
"%m-%d %H:%M}.</p><p>Remember that you can only fix minor changes to your " "%m-%d %H:%M}.</p><p>Remember that you can only fix minor changes to your "
@ -1365,7 +1430,7 @@ msgstr ""
"parties.</p><p>Vous pouvez envoyer vos solutions sur <a href='{url}'>votre " "parties.</p><p>Vous pouvez envoyer vos solutions sur <a href='{url}'>votre "
"page de participation</a>.</p>" "page de participation</a>.</p>"
#: participation/models.py:847 registration/models.py:607 #: participation/models.py:901 registration/models.py:607
msgid "" msgid ""
"<p>The draw of the solutions for the tournament {tournament} is planned on " "<p>The draw of the solutions for the tournament {tournament} is planned on "
"the {date:%Y-%m-%d %H:%M}. You can join it on <a href='{url}'>this link</a>." "the {date:%Y-%m-%d %H:%M}. You can join it on <a href='{url}'>this link</a>."
@ -1375,11 +1440,11 @@ msgstr ""
"{date:%d/%m/%Y %H:%M}. Vous pouvez y participer sur <a href='{url}'>ce lien</" "{date:%d/%m/%Y %H:%M}. Vous pouvez y participer sur <a href='{url}'>ce lien</"
"a>.</p>" "a>.</p>"
#: participation/models.py:853 registration/models.py:614 #: participation/models.py:907 registration/models.py:614
msgid "Draw of solutions" msgid "Draw of solutions"
msgstr "Tirage au sort des solutions" msgstr "Tirage au sort des solutions"
#: participation/models.py:865 #: participation/models.py:919
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>The solutions draw is ended. You can check the result on <a " "<p>The solutions draw is ended. You can check the result on <a "
@ -1391,8 +1456,8 @@ msgstr ""
"tour, vous défendrez <a href='{solution_url}'>votre solution du problème " "tour, vous défendrez <a href='{solution_url}'>votre solution du problème "
"{problem}</a>.</p>" "{problem}</a>.</p>"
#: participation/models.py:874 participation/models.py:932 #: participation/models.py:928 participation/models.py:986
#: participation/models.py:991 #: participation/models.py:1045
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>You will oppose the solution of the team {opponent} on the <a " "<p>You will oppose the solution of the team {opponent} on the <a "
@ -1403,8 +1468,8 @@ msgstr ""
"href='{solution_url}'>problème {problem}</a>. Vous pouvez envoyer votre note " "href='{solution_url}'>problème {problem}</a>. Vous pouvez envoyer votre note "
"de synthèse sur <a href='{passage_url}'>cette page</a>.</p>" "de synthèse sur <a href='{passage_url}'>cette page</a>.</p>"
#: participation/models.py:883 participation/models.py:941 #: participation/models.py:937 participation/models.py:995
#: participation/models.py:1000 #: participation/models.py:1054
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>You will report the solution of the team {reviewer} on the <a " "<p>You will report the solution of the team {reviewer} on the <a "
@ -1415,8 +1480,8 @@ msgstr ""
"href='{solution_url}'>problème {problem}</a>. Vous pouvez envoyer votre note " "href='{solution_url}'>problème {problem}</a>. Vous pouvez envoyer votre note "
"de synthèse sur <a href='{passage_url}'>cette page</a>.</p>" "de synthèse sur <a href='{passage_url}'>cette page</a>.</p>"
#: participation/models.py:893 participation/models.py:951 #: participation/models.py:947 participation/models.py:1005
#: participation/models.py:1010 #: participation/models.py:1064
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>You will observe the solution of the team {observer} on the <a " "<p>You will observe the solution of the team {observer} on the <a "
@ -1427,11 +1492,11 @@ msgstr ""
"href='{solution_url}'>problème {problem}</a>. Vous pouvez envoyer votre note " "href='{solution_url}'>problème {problem}</a>. Vous pouvez envoyer votre note "
"de synthèse sur <a href='{passage_url}'>cette page</a>.</p>" "de synthèse sur <a href='{passage_url}'>cette page</a>.</p>"
#: participation/models.py:913 registration/models.py:629 #: participation/models.py:967 registration/models.py:629
msgid "First round" msgid "First round"
msgstr "Premier tour" msgstr "Premier tour"
#: participation/models.py:925 #: participation/models.py:979
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>For the second round, you will defend <a href='{solution_url}'>your " "<p>For the second round, you will defend <a href='{solution_url}'>your "
@ -1440,12 +1505,12 @@ msgstr ""
"<p>Pour le second tour, vous défendrez <a href='{solution_url}'>votre " "<p>Pour le second tour, vous défendrez <a href='{solution_url}'>votre "
"solution du problème {problem}</a>.</p>" "solution du problème {problem}</a>.</p>"
#: participation/models.py:971 participation/models.py:1030 #: participation/models.py:1025 participation/models.py:1084
#: registration/models.py:640 #: registration/models.py:640
msgid "Second round" msgid "Second round"
msgstr "Second tour" msgstr "Second tour"
#: participation/models.py:984 #: participation/models.py:1038
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>For the third round, you will defend <a href='{solution_url}'>your " "<p>For the third round, you will defend <a href='{solution_url}'>your "
@ -1454,7 +1519,7 @@ msgstr ""
"<p>Pour le troisième tour, vous défendrez <a href='{solution_url}'>votre " "<p>Pour le troisième tour, vous défendrez <a href='{solution_url}'>votre "
"solution du problème {problem}</a>.</p>" "solution du problème {problem}</a>.</p>"
#: participation/models.py:1036 #: participation/models.py:1090
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>The tournament {tournament} is ended. You can check the results on the <a " "<p>The tournament {tournament} is ended. You can check the results on the <a "
@ -1463,57 +1528,57 @@ msgstr ""
"<p>Le tournoi {tournament} est terminé. Vous pouvez consulter les résultats " "<p>Le tournoi {tournament} est terminé. Vous pouvez consulter les résultats "
"sur la <a href='{url}'>page du tournoi</a>.</p>" "sur la <a href='{url}'>page du tournoi</a>.</p>"
#: participation/models.py:1041 #: participation/models.py:1095
msgid "Tournament ended" msgid "Tournament ended"
msgstr "Tournoi terminé" msgstr "Tournoi terminé"
#: participation/models.py:1051 participation/models.py:1094 #: participation/models.py:1105 participation/models.py:1148
msgid "participations" msgid "participations"
msgstr "participations" msgstr "participations"
#: participation/models.py:1066 participation/models.py:1067 #: participation/models.py:1120 participation/models.py:1121
#: participation/models.py:1068 #: participation/models.py:1122
#, python-brace-format #, python-brace-format
msgid "Round {round}" msgid "Round {round}"
msgstr "Tour {round}" msgstr "Tour {round}"
#: participation/models.py:1082 #: participation/models.py:1136
msgid "room" msgid "room"
msgstr "salle" msgstr "salle"
#: participation/models.py:1084 #: participation/models.py:1138
msgid "Room 1" msgid "Room 1"
msgstr "Salle 1" msgstr "Salle 1"
#: participation/models.py:1085 #: participation/models.py:1139
msgid "Room 2" msgid "Room 2"
msgstr "Salle 2" msgstr "Salle 2"
#: participation/models.py:1088 #: participation/models.py:1142
msgid "For 5-teams pools only" msgid "For 5-teams pools only"
msgstr "Pour les poules de 5 équipe uniquement" msgstr "Pour les poules de 5 équipe uniquement"
#: participation/models.py:1100 #: participation/models.py:1154
msgid "juries" msgid "juries"
msgstr "jurys" msgstr "jurys"
#: participation/models.py:1109 #: participation/models.py:1163
msgid "president of the jury" msgid "president of the jury"
msgstr "président⋅e du jury" msgstr "président⋅e du jury"
#: participation/models.py:1116 #: participation/models.py:1170
msgid "BigBlueButton URL" msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton" msgstr "Lien BigBlueButton"
#: participation/models.py:1117 #: participation/models.py:1171
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:1122 #: participation/models.py:1176
msgid "results available" msgid "results available"
msgstr "résultats disponibles" msgstr "résultats disponibles"
#: participation/models.py:1123 #: participation/models.py:1177
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."
@ -1522,33 +1587,61 @@ 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:1155 #: participation/models.py:1209
msgid "The president of the jury must be part of the jury." msgid "The president of the jury must be part of the jury."
msgstr "Læ président⋅e du jury doit faire partie du jury." msgstr "Læ président⋅e du jury doit faire partie du jury."
#: participation/models.py:1544 #: participation/models.py:1235 participation/models.py:1309
#: participation/views.py:1481 participation/views.py:1730
msgid "Problem"
msgstr "Problème"
#: participation/models.py:1245 participation/views.py:1530
#: participation/views.py:1531
msgid "Juree"
msgstr "Juré⋅e"
#: participation/models.py:1268 participation/models.py:1588
#: participation/models.py:1610 participation/views.py:1600
msgid "Average"
msgstr "Moyenne"
#: participation/models.py:1274 participation/views.py:1619
msgid "Coefficient"
msgstr "Coefficien"
#: participation/models.py:1275 participation/views.py:1662
msgid "Subtotal"
msgstr "Sous-total"
#: participation/models.py:1535
#, python-brace-format
msgid "Input must be a valid integer between {min_note} and {max_note}."
msgstr "L'entrée doit être un entier valide entre {min_note} et {max_note}."
#: participation/models.py:1623
#, python-brace-format #, python-brace-format
msgid "The jury {jury} is not part of the jury for this pool." msgid "The jury {jury} is not part of the jury for this pool."
msgstr "{jury} ne fait pas partie du jury pour cette poule." msgstr "{jury} ne fait pas partie du jury pour cette poule."
#: participation/models.py:1557 #: participation/models.py:1636
#, python-brace-format #, python-brace-format
msgid "Pool {code} for tournament {tournament} with teams {teams}" msgid "Pool {code} for tournament {tournament} with teams {teams}"
msgstr "Poule {code} du tournoi {tournament} avec les équipes {teams}" msgstr "Poule {code} du tournoi {tournament} avec les équipes {teams}"
#: participation/models.py:1577 #: participation/models.py:1656
msgid "position" msgid "position"
msgstr "position" msgstr "position"
#: participation/models.py:1584 #: participation/models.py:1663
msgid "defended solution" msgid "defended solution"
msgstr "solution défendue" msgstr "solution défendue"
#: participation/models.py:1622 #: participation/models.py:1701
msgid "penalties" msgid "penalties"
msgstr "pénalités" msgstr "pénalités"
#: participation/models.py:1624 #: participation/models.py:1703
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."
@ -1556,128 +1649,128 @@ 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:1721 participation/models.py:1724 #: participation/models.py:1829 participation/models.py:1832
#: participation/models.py:1727 participation/models.py:1730 #: participation/models.py:1835 participation/models.py:1838
#, 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:1735 #: participation/models.py:1843
#, 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:1739 participation/models.py:1748 #: participation/models.py:1847 participation/models.py:1856
#: participation/models.py:1833 participation/models.py:1876 #: participation/models.py:1941 participation/models.py:1984
msgid "passage" msgid "passage"
msgstr "passage" msgstr "passage"
#: participation/models.py:1740 #: participation/models.py:1848
msgid "passages" msgid "passages"
msgstr "passages" msgstr "passages"
#: participation/models.py:1759 #: participation/models.py:1867
msgid "difference" msgid "difference"
msgstr "différence" msgstr "différence"
#: participation/models.py:1760 #: participation/models.py:1868
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:1767 #: participation/models.py:1875
msgid "tweak" msgid "tweak"
msgstr "harmonisation" msgstr "harmonisation"
#: participation/models.py:1768 #: participation/models.py:1876
msgid "tweaks" msgid "tweaks"
msgstr "harmonisations" msgstr "harmonisations"
#: participation/models.py:1796 #: participation/models.py:1904
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:1801 participation/models.py:1845 #: participation/models.py:1909 participation/models.py:1953
msgid "file" msgid "file"
msgstr "fichier" msgstr "fichier"
#: participation/models.py:1811 #: participation/models.py:1919
#, 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:1813 #: participation/models.py:1921
msgid "for final" msgid "for final"
msgstr "pour la finale" msgstr "pour la finale"
#: participation/models.py:1816 #: participation/models.py:1924
msgid "solution" msgid "solution"
msgstr "solution" msgstr "solution"
#: participation/models.py:1817 #: participation/models.py:1925
msgid "solutions" msgid "solutions"
msgstr "solutions" msgstr "solutions"
#: participation/models.py:1851 #: participation/models.py:1959
#, 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:1859 #: participation/models.py:1967
msgid "synthesis" msgid "synthesis"
msgstr "note de synthèse" msgstr "note de synthèse"
#: participation/models.py:1860 #: participation/models.py:1968
msgid "syntheses" msgid "syntheses"
msgstr "notes de synthèse" msgstr "notes de synthèse"
#: participation/models.py:1869 #: participation/models.py:1977
msgid "jury" msgid "jury"
msgstr "jury" msgstr "jury"
#: participation/models.py:1881 #: participation/models.py:1989
msgid "defender writing note" msgid "defender writing note"
msgstr "note d'écrit défenseur⋅se" msgstr "note d'écrit défenseur⋅se"
#: participation/models.py:1887 #: participation/models.py:1995
msgid "defender oral note" msgid "defender oral note"
msgstr "note d'oral défenseur⋅se" msgstr "note d'oral défenseur⋅se"
#: participation/models.py:1893 #: participation/models.py:2001
msgid "opponent writing note" msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e" msgstr "note d'écrit opposant⋅e"
#: participation/models.py:1899 #: participation/models.py:2007
msgid "opponent oral note" msgid "opponent oral note"
msgstr "note d'oral opposant⋅e" msgstr "note d'oral opposant⋅e"
#: participation/models.py:1905 #: participation/models.py:2013
msgid "reviewer writing note" msgid "reviewer writing note"
msgstr "note d'écrit rapporteur⋅rice" msgstr "note d'écrit rapporteur⋅rice"
#: participation/models.py:1911 #: participation/models.py:2019
msgid "reviewer oral note" msgid "reviewer oral note"
msgstr "note d'oral du rapporteur⋅rice" msgstr "note d'oral du rapporteur⋅rice"
#: participation/models.py:1917 #: participation/models.py:2025
msgid "observer writing note" msgid "observer writing note"
msgstr "note d'écrit de l'observateur⋅rice" msgstr "note d'écrit de l'observateur⋅rice"
#: participation/models.py:1923 #: participation/models.py:2031
msgid "observer oral note" msgid "observer oral note"
msgstr "note d'oral de l'observateur⋅rice" msgstr "note d'oral de l'observateur⋅rice"
#: participation/models.py:1988 #: participation/models.py:2096
#, 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:1991 #: participation/models.py:2099
msgid "note" msgid "note"
msgstr "note" msgstr "note"
#: participation/models.py:1992 #: participation/models.py:2100
msgid "notes" msgid "notes"
msgstr "notes" msgstr "notes"
@ -1718,8 +1811,8 @@ msgstr "Pas d'équipe définie"
#: participation/tables.py:147 #: participation/tables.py:147
#: participation/templates/participation/note_form.html:14 #: participation/templates/participation/note_form.html:14
#: participation/templates/participation/passage_detail.html:15 #: participation/templates/participation/passage_detail.html:15
#: participation/templates/participation/passage_detail.html:168 #: participation/templates/participation/passage_detail.html:176
#: participation/templates/participation/passage_detail.html:174 #: participation/templates/participation/passage_detail.html:182
#: participation/templates/participation/pool_detail.html:13 #: participation/templates/participation/pool_detail.html:13
#: participation/templates/participation/pool_detail.html:152 #: participation/templates/participation/pool_detail.html:152
#: participation/templates/participation/team_detail.html:185 #: participation/templates/participation/team_detail.html:185
@ -1807,7 +1900,7 @@ msgid "Upload solution"
msgstr "Envoyer une solution" msgstr "Envoyer une solution"
#: participation/templates/participation/participation_detail.html:65 #: participation/templates/participation/participation_detail.html:65
#: participation/templates/participation/passage_detail.html:180 #: participation/templates/participation/passage_detail.html:188
#: participation/templates/participation/pool_detail.html:157 #: participation/templates/participation/pool_detail.html:157
#: participation/templates/participation/team_detail.html:245 #: participation/templates/participation/team_detail.html:245
#: participation/templates/participation/upload_motivation_letter.html:13 #: participation/templates/participation/upload_motivation_letter.html:13
@ -1870,12 +1963,12 @@ msgid "No synthesis was uploaded yet."
msgstr "Aucune note de synthèse n'a encore été envoyée." msgstr "Aucune note de synthèse n'a encore été envoyée."
#: participation/templates/participation/passage_detail.html:61 #: participation/templates/participation/passage_detail.html:61
#: participation/templates/participation/passage_detail.html:173 #: participation/templates/participation/passage_detail.html:181
msgid "Update notes" msgid "Update notes"
msgstr "Modifier les notes" msgstr "Modifier les notes"
#: participation/templates/participation/passage_detail.html:66 #: participation/templates/participation/passage_detail.html:66
#: participation/templates/participation/passage_detail.html:179 #: participation/templates/participation/passage_detail.html:187
msgid "Upload synthesis" msgid "Upload synthesis"
msgstr "Envoyer une note de synthèse" msgstr "Envoyer une note de synthèse"
@ -1887,51 +1980,51 @@ msgstr "Détails des notes"
msgid "Average points for the defender writing" msgid "Average points for the defender writing"
msgstr "Moyenne de l'écrit de l'équipe défenseuse" msgstr "Moyenne de l'écrit de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:88 #: participation/templates/participation/passage_detail.html:90
msgid "Average points for the defender oral" msgid "Average points for the defender oral"
msgstr "Moyenne de l'oral de l'équipe défenseuse" msgstr "Moyenne de l'oral de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:94 #: participation/templates/participation/passage_detail.html:98
msgid "Average points for the opponent writing" msgid "Average points for the opponent writing"
msgstr "Moyenne de l'écrit de l'équipe opposante" msgstr "Moyenne de l'écrit de l'équipe opposante"
#: participation/templates/participation/passage_detail.html:100 #: participation/templates/participation/passage_detail.html:104
msgid "Average points for the opponent oral" msgid "Average points for the opponent oral"
msgstr "Moyenne de l'oral de l'équipe opposante" msgstr "Moyenne de l'oral de l'équipe opposante"
#: participation/templates/participation/passage_detail.html:106 #: participation/templates/participation/passage_detail.html:110
msgid "Average points for the reviewer writing" msgid "Average points for the reviewer writing"
msgstr "Moyenne de l'écrit de l'équipe rapportrice" msgstr "Moyenne de l'écrit de l'équipe rapportrice"
#: participation/templates/participation/passage_detail.html:112 #: participation/templates/participation/passage_detail.html:116
msgid "Average points for the reviewer oral" msgid "Average points for the reviewer oral"
msgstr "Moyenne de l'oral de l'équipe rapportrice" msgstr "Moyenne de l'oral de l'équipe rapportrice"
#: participation/templates/participation/passage_detail.html:119 #: participation/templates/participation/passage_detail.html:123
msgid "Average points for the observer writing" msgid "Average points for the observer writing"
msgstr "Moyenne de l'écrit de l'équipe observatrice" msgstr "Moyenne de l'écrit de l'équipe observatrice"
#: participation/templates/participation/passage_detail.html:125 #: participation/templates/participation/passage_detail.html:129
msgid "Average points for the observer oral" msgid "Average points for the observer oral"
msgstr "Moyenne de l'oral de l'équipe observatrice" msgstr "Moyenne de l'oral de l'équipe observatrice"
#: participation/templates/participation/passage_detail.html:136 #: participation/templates/participation/passage_detail.html:140
msgid "Defender points" msgid "Defender points"
msgstr "Points de l'équipe défenseuse" msgstr "Points de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:142 #: participation/templates/participation/passage_detail.html:148
msgid "Opponent points" msgid "Opponent points"
msgstr "Points de l'équipe opposante" msgstr "Points de l'équipe opposante"
#: participation/templates/participation/passage_detail.html:148 #: participation/templates/participation/passage_detail.html:156
msgid "reviewer points" msgid "reviewer points"
msgstr "Points de l'équipe rapportrice" msgstr "Points de l'équipe rapportrice"
#: participation/templates/participation/passage_detail.html:155 #: participation/templates/participation/passage_detail.html:163
msgid "observer points" msgid "observer points"
msgstr "Points de l'équipe observatrice" msgstr "Points de l'équipe observatrice"
#: participation/templates/participation/passage_detail.html:167 #: participation/templates/participation/passage_detail.html:175
#: participation/templates/participation/passage_form.html:11 #: participation/templates/participation/passage_form.html:11
msgid "Update passage" msgid "Update passage"
msgstr "Modifier le passage" msgstr "Modifier le passage"
@ -2355,10 +2448,6 @@ msgstr "Dépublier les notes pour le second tour"
msgid "Files available for download" msgid "Files available for download"
msgstr "Fichiers disponibles au téléchargement" msgstr "Fichiers disponibles au téléchargement"
#: participation/templates/participation/tournament_harmonize.html:14
msgid "Rank"
msgstr "Rang"
#: participation/templates/participation/tournament_harmonize.html:16 #: participation/templates/participation/tournament_harmonize.html:16
#: registration/models.py:655 #: registration/models.py:655
msgid "Note" msgid "Note"
@ -2615,17 +2704,37 @@ msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e
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:1845 #: participation/views.py:1496
msgid "Role"
msgstr "Rôle"
#: participation/views.py:1502
msgid "Defender"
msgstr "Défenseur⋅se"
#: participation/views.py:1508
msgid "Opponent"
msgstr "Opposant⋅e"
#: participation/views.py:1515
msgid "Reviewer"
msgstr "Rapporteur⋅rice"
#: participation/views.py:1522
msgid "Observer"
msgstr "Observateur⋅rice"
#: participation/views.py:1893
#, python-brace-format #, python-brace-format
msgid "Notation sheets of pool {pool} of {tournament}.zip" msgid "Notation sheets of pool {pool} of {tournament}.zip"
msgstr "Feuilles de notations pour la poule {pool} du tournoi {tournament}.zip" msgstr "Feuilles de notations pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:1850 #: participation/views.py:1898
#, python-brace-format #, python-brace-format
msgid "Notation sheets of {tournament}.zip" msgid "Notation sheets of {tournament}.zip"
msgstr "Feuilles de notation de {tournament}.zip" msgstr "Feuilles de notation de {tournament}.zip"
#: participation/views.py:2017 #: participation/views.py:2065
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

@ -302,25 +302,26 @@ class UploadNotesForm(forms.Form):
line = [s for s in line if s == s] line = [s for s in line if s == s]
# Strip cases # Strip cases
line = [str(s).strip() for s in line if str(s)] line = [str(s).strip() for s in line if str(s)]
if line and line[0] == 'Problème': if line and line[0] in ["Problème", "Problem"]:
pool_size = len(line) - 1 pool_size = len(line) - 1
line_length = 2 + 6 * pool_size line_length = 2 + (8 if df.iat[1, 8] == "Observer" else 6) * pool_size
continue continue
if pool_size == 0 or len(line) < line_length: if pool_size == 0 or len(line) < line_length:
continue continue
name = line[0] name = line[0]
if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]: if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe",
"role", "juree", "average", "coefficient", "subtotal", "team"]:
continue continue
notes = line[2:line_length] notes = line[2:line_length]
print(name, notes)
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes): if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
continue continue
notes = list(map(lambda x: int(float(x)), notes)) notes = list(map(lambda x: int(float(x)), notes))
print(notes)
max_notes = pool_size * [20, 20, 10, 10, 10, 10] max_notes = pool_size * [20 if settings.TFJM_APP == "TFJM" else 10,
20 if settings.TFJM_APP == "TFJM" else 10,
10, 10, 10, 10, 10, 10]
for n, max_n in zip(notes, max_notes): for n, max_n in zip(notes, max_notes):
if n > max_n: if n > max_n:
self.add_error('file', self.add_error('file',

View File

@ -458,7 +458,7 @@ class Tournament(models.Model):
return self.notes_sheet_id return self.notes_sheet_id
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.create(f"Feuille de notes - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID) spreadsheet = gc.create(f"{_('Notation sheet')} - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
spreadsheet.update_locale("fr_FR") spreadsheet.update_locale("fr_FR")
spreadsheet.share(None, "anyone", "writer", with_link=True) spreadsheet.share(None, "anyone", "writer", with_link=True)
self.notes_sheet_id = spreadsheet.id self.notes_sheet_id = spreadsheet.id
@ -470,17 +470,21 @@ class Tournament(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.notes_sheet_id) spreadsheet = gc.open_by_key(self.notes_sheet_id)
worksheets = spreadsheet.worksheets() worksheets = spreadsheet.worksheets()
if "Classement final" not in [ws.title for ws in worksheets]: if _("Final ranking") not in [ws.title for ws in worksheets]:
worksheet = spreadsheet.add_worksheet("Classement final", 100, 26) worksheet = spreadsheet.add_worksheet(_("Final ranking"), 30, 10)
else: else:
worksheet = spreadsheet.worksheet("Classement final") worksheet = spreadsheet.worksheet(_("Final ranking"))
if worksheet.index != self.pools.count(): if worksheet.index != self.pools.count():
worksheet.update_index(self.pools.count()) worksheet.update_index(self.pools.count())
header = [["Équipe", "Score jour 1", "Harmonisation 1", "Score jour 2", "Harmonisation 2", "Total", "Rang"]] header = [[_("Team"), _("Scores day 1"), _("Tweaks day 1"), _("Scores day 2"), _("Tweaks day 2")]
+ ([_("Total D1 + D2"), _("Scores day 3"), _("Tweaks day 3")]
if settings.NB_ROUNDS >= 3 else [])
+ [_("Total"), _("Rank")]]
lines = [] lines = []
participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all() participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all()
total_col, rank_col = ("F", "G") if settings.NB_ROUNDS == 2 else ("I", "J")
for i, participation in enumerate(participations): for i, participation in enumerate(participations):
line = [f"{participation.team.name} ({participation.team.trigram})"] line = [f"{participation.team.name} ({participation.team.trigram})"]
lines.append(line) lines.append(line)
@ -494,7 +498,7 @@ class Tournament(models.Model):
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation) tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
tweak1 = tweak1_qs.get() if tweak1_qs.exists() else None tweak1 = tweak1_qs.get() if tweak1_qs.exists() else None
line.append(f"=SIERREUR('Poule {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)") line.append(f"=SIERREUR('{_('Pool')} {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
line.append(tweak1.diff if tweak1 else 0) line.append(tweak1.diff if tweak1 else 0)
if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists(): if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists():
@ -508,23 +512,49 @@ class Tournament(models.Model):
tweak2 = tweak2_qs.get() if tweak2_qs.exists() else None tweak2 = tweak2_qs.get() if tweak2_qs.exists() else None
line.append( line.append(
f"=SIERREUR('Poule {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)") f"=SIERREUR('{_('Pool')} {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)")
line.append(tweak2.diff if tweak2 else 0) line.append(tweak2.diff if tweak2 else 0)
else:
# User has no second pool yet
line.append(0)
line.append(0)
if settings.NB_ROUNDS >= 3:
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}") line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
line.append(f"=RANG($F{i + 2}; $F$2:$F${participations.count() + 1})")
final_ranking = [["", "", "", ""], ["", "", "", ""], ["Équipe", "Score", "Rang", "Mention"], if Passage.objects.filter(pool__tournament=self, pool__round=3, defender=participation).exists():
passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, defender=participation)
pool3 = passage3.pool
if pool3.participations.count() != 5:
position3 = passage3.position
else:
position3 = (passage3.position - 1) * 2 + pool3.room
tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation)
tweak3 = tweak3_qs.get() if tweak3_qs.exists() else None
line.append(
f"=SIERREUR('{_('Pool')} {pool3.short_name}'!$D{pool3.juries.count() + 10 + position3}; 0)")
line.append(tweak3.diff if tweak3 else 0)
else:
line.append(0)
line.append(0)
else:
# There is no second pool yet
line.append(0)
line.append(0)
if settings.NB_ROUNDS >= 3:
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
line.append(0)
line.append(0)
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}"
+ (f" + (PI() - 2) * $G{i + 2} + $H{i + 2}" if settings.NB_ROUNDS >= 3 else ""))
line.append(f"=RANG(${total_col}{i + 2}; ${total_col}$2:${total_col}${participations.count() + 1})")
final_ranking = [["", "", "", ""], ["", "", "", ""], [_("Team"), _("Score"), _("Rank"), _("Mention")],
[f"=SORT($A$2:$A${participations.count() + 1}; " [f"=SORT($A$2:$A${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
f"=SORT($F$2:$F${participations.count() + 1}; " f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
f"=SORT($G$2:$G${participations.count() + 1}; " f"=SORT(${rank_col}$2:${rank_col}${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", ]] f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", ]]
final_ranking += [["", "", ""] for _i in range(participations.count() - 1)] final_ranking += [["", "", ""] for _i in range(participations.count() - 1)]
notes = dict() notes = dict()
@ -538,12 +568,13 @@ class Tournament(models.Model):
final_ranking[i + 3].append(participation.mention if not self.final else participation.mention_final) final_ranking[i + 3].append(participation.mention if not self.final else participation.mention_final)
data = header + lines + final_ranking data = header + lines + final_ranking
worksheet.update(data, f"A1:G{2 * participations.count() + 4}", raw=False) worksheet.update(data, f"A1:{rank_col}{2 * participations.count() + 4}", raw=False)
format_requests = [] format_requests = []
# Set the width of the columns # Set the width of the columns
column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150)] column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150),
("H", 150), ("I", 150), ("J", 150)]
for column, width in column_widths: for column, width in column_widths:
grid_range = a1_range_to_grid_range(column, worksheet.id) grid_range = a1_range_to_grid_range(column, worksheet.id)
format_requests.append({ format_requests.append({
@ -563,7 +594,7 @@ class Tournament(models.Model):
# Set borders # Set borders
border_ranges = [("A1:Z", "0000"), border_ranges = [("A1:Z", "0000"),
(f"A1:G{participations.count() + 1}", "1111"), (f"A1:{rank_col}{participations.count() + 1}", "1111"),
(f"A{participations.count() + 4}:D{2 * participations.count() + 4}", "1111")] (f"A{participations.count() + 4}:D{2 * participations.count() + 4}", "1111")]
sides_names = ['top', 'bottom', 'left', 'right'] sides_names = ['top', 'bottom', 'left', 'right']
styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"] styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"]
@ -585,7 +616,7 @@ class Tournament(models.Model):
}) })
# Make titles bold # Make titles bold
bold_ranges = [("A1:Z", False), ("A1:G1", True), bold_ranges = [("A1:Z", False), (f"A1:{rank_col}1", True),
(f"A{participations.count() + 4}:D{participations.count() + 4}", True)] (f"A{participations.count() + 4}:D{participations.count() + 4}", True)]
for bold_range, bold in bold_ranges: for bold_range, bold in bold_ranges:
format_requests.append({ format_requests.append({
@ -598,14 +629,18 @@ class Tournament(models.Model):
# Set background color for headers and footers # Set background color for headers and footers
bg_colors = [("A1:Z", (1, 1, 1)), bg_colors = [("A1:Z", (1, 1, 1)),
("A1:G1", (0.8, 0.8, 0.8)), (f"A1:{rank_col}1", (0.8, 0.8, 0.8)),
(f"A2:B{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"A2:B{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"C2:C{participations.count() + 1}", (1, 1, 1)), (f"C2:C{participations.count() + 1}", (1, 1, 1)),
(f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"E2:E{participations.count() + 1}", (1, 1, 1)), (f"E2:E{participations.count() + 1}", (1, 1, 1)),
(f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"A{participations.count() + 4}:D{participations.count() + 4}", (0.8, 0.8, 0.8)), (f"A{participations.count() + 4}:D{participations.count() + 4}", (0.8, 0.8, 0.8)),
(f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),] (f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),]
if settings.NB_ROUNDS >= 3:
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
bg_colors.append((f"H2:I{participations.count() + 1}", (0.9, 0.9, 0.9)))
else:
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
for bg_range, bg_color in bg_colors: for bg_range, bg_color in bg_colors:
r, g, b = bg_color r, g, b = bg_color
format_requests.append({ format_requests.append({
@ -622,9 +657,15 @@ class Tournament(models.Model):
(f"D2:D{participations.count() + 1}", "0.0"), (f"D2:D{participations.count() + 1}", "0.0"),
(f"E2:E{participations.count() + 1}", "0"), (f"E2:E{participations.count() + 1}", "0"),
(f"F2:F{participations.count() + 1}", "0.0"), (f"F2:F{participations.count() + 1}", "0.0"),
(f"G2:G{participations.count() + 1}", "0"),
(f"B{participations.count() + 5}:B{2 * participations.count() + 5}", "0.0"), (f"B{participations.count() + 5}:B{2 * participations.count() + 5}", "0.0"),
(f"C{participations.count() + 5}:C{2 * participations.count() + 5}", "0"), ] (f"C{participations.count() + 5}:C{2 * participations.count() + 5}", "0"), ]
if settings.NB_ROUNDS >= 3:
number_format_ranges += [(f"G2:G{participations.count() + 1}", "0.0"),
(f"H2:H{participations.count() + 1}", "0"),
(f"I2:I{participations.count() + 1}", "0.0"),
(f"J2:J{participations.count() + 1}", "0"), ]
else:
number_format_ranges.append((f"G2:G{participations.count() + 1}", "0"))
for number_format_range, pattern in number_format_ranges: for number_format_range, pattern in number_format_ranges:
format_requests.append({ format_requests.append({
"repeatCell": { "repeatCell": {
@ -643,16 +684,16 @@ class Tournament(models.Model):
}) })
# Protect the header, the juries list, the footer and the ranking # Protect the header, the juries list, the footer and the ranking
protected_ranges = ["A1:G1", f"A2:B{participations.count() + 1}", protected_ranges = ["A1:J1", f"A2:B{participations.count() + 1}",
f"D2:D{participations.count() + 1}", f"F2:G{participations.count() + 1}", f"D2:D{participations.count() + 1}", f"F2:G{participations.count() + 1}",
f"I2:J{participations.count() + 1}",
f"A{participations.count() + 4}:C{2 * participations.count() + 4}", ] f"A{participations.count() + 4}:C{2 * participations.count() + 4}", ]
for protected_range in protected_ranges: for protected_range in protected_ranges:
format_requests.append({ format_requests.append({
"addProtectedRange": { "addProtectedRange": {
"protectedRange": { "protectedRange": {
"range": a1_range_to_grid_range(protected_range, worksheet.id), "range": a1_range_to_grid_range(protected_range, worksheet.id),
"description": "Structure du tableur à ne pas modifier " "description": _("Don't update the table structure for a better automated integration."),
"pour une meilleure prise en charge automatisée",
"warningOnly": True, "warningOnly": True,
}, },
} }
@ -666,19 +707,21 @@ class Tournament(models.Model):
# Draw has not been done yet # Draw has not been done yet
return return
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.notes_sheet_id) spreadsheet = gc.open_by_key(self.notes_sheet_id)
worksheet = spreadsheet.worksheet("Classement final") worksheet = spreadsheet.worksheet(_("Final ranking"))
score_cell = worksheet.find("Score") score_cell = worksheet.find(_("Score"))
max_row = score_cell.row - 3 max_row = score_cell.row - 3
if max_row == 1: if max_row == 1:
# There is no team # There is no team
return return
data = worksheet.get_values(f"A2:E{max_row}") data = worksheet.get_values(f"A2:H{max_row}")
for line in data: for line in data:
trigram = line[0][-4:-1] trigram = line[0][-settings.TEAM_CODE_LENGTH - 1:-1]
participation = self.participations.get(team__trigram=trigram) participation = self.participations.get(team__trigram=trigram)
pool1 = self.pools.get(round=1, participations=participation, room=1) pool1 = self.pools.get(round=1, participations=participation, room=1)
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation) tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
@ -701,6 +744,17 @@ class Tournament(models.Model):
create_defaults={'diff': tweak2_nb, 'pool': pool2, create_defaults={'diff': tweak2_nb, 'pool': pool2,
'participation': participation}) 'participation': participation})
if self.pools.filter(round=3, participations=participation).exists():
pool3 = self.pools.get(round=3, participations=participation, room=1)
tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation)
tweak3_nb = int(line[7])
if not tweak3_nb:
tweak3_qs.delete()
else:
tweak3_qs.update_or_create(defaults={'diff': tweak3_nb},
create_defaults={'diff': tweak3_nb, 'pool': pool3,
'participation': participation})
nb_participations = self.participations.filter(valid=True).count() nb_participations = self.participations.filter(valid=True).count()
mentions = worksheet.get_values(f"A{score_cell.row + 1}:D{score_cell.row + nb_participations}") mentions = worksheet.get_values(f"A{score_cell.row + 1}:D{score_cell.row + nb_participations}")
notes = dict() notes = dict()
@ -1164,26 +1218,31 @@ class Pool(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id) spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
worksheets = spreadsheet.worksheets() worksheets = spreadsheet.worksheets()
if f"Poule {self.short_name}" not in [ws.title for ws in worksheets]: if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]:
worksheet = spreadsheet.add_worksheet(f"Poule {self.short_name}", 100, 26) worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}", 100, 34)
else: else:
worksheet = spreadsheet.worksheet(f"Poule {self.short_name}") worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
if any(ws.title == "Sheet1" for ws in worksheets): if any(ws.title == "Sheet1" for ws in worksheets):
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1")) spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
pool_size = self.participations.count() pool_size = self.participations.count()
passage_width = 6 has_observer = settings.TFJM_APP == "ETEAM" and pool_size >= 4
passage_width = 6 + (2 if has_observer else 0)
passages = self.passages.all() passages = self.passages.all()
header = [ header = [
sum(([f"Problème {passage.solution_number}"] + (passage_width - 1) * [""] sum(([_("Problem #{problem}").format(problem=passage.solution_number)] + (passage_width - 1) * [""]
for passage in passages), start=["Problème", ""]), for passage in passages), start=[_("Problem"), ""]),
sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "", sum(([f"{_('Defender')} ({passage.defender.team.trigram})", "",
f"Opposant⋅e ({passage.opponent.team.trigram})", "", f"{_('Opponent')} ({passage.opponent.team.trigram})", "",
f"Rapporteur⋅rice ({passage.reviewer.team.trigram})", ""] f"{_('Reviewer')} ({passage.reviewer.team.trigram})", ""]
+ ([f"{('Observer')} ({passage.observer.team.trigram})", ""] if has_observer else [])
for passage in passages), start=["Rôle", ""]), for passage in passages), start=["Rôle", ""]),
sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"] sum(([f"{_('Writing')} (/{20 if settings.TFJM_APP == "TFJM" else 10})",
for _passage in passages), start=["Juré⋅e", ""]), f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"
f"{_('Writing')} (/10)", f"{_('Oral')} (/10)", f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"]
+ ([f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"] if has_observer else [])
for _passage in passages), start=[_("Juree"), ""]),
] ]
notes = [[]] # Begin with empty hidden line to ensure pretty design notes = [[]] # Begin with empty hidden line to ensure pretty design
@ -1193,6 +1252,8 @@ class Pool(models.Model):
note = passage.notes.filter(jury=jury).first() note = passage.notes.filter(jury=jury).first()
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral, line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
note.reviewer_writing, note.reviewer_oral]) note.reviewer_writing, note.reviewer_oral])
if has_observer:
line.extend([note.observer_writing, note.observer_oral])
notes.append(line) notes.append(line)
notes.append([]) # Add empty line to ensure pretty design notes.append([]) # Add empty line to ensure pretty design
@ -1204,11 +1265,15 @@ class Pool(models.Model):
return '' return ''
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26) return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
average = ["Moyenne", ""] average = [_("Average"), ""]
coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] for passage in passages), coeffs = sum(([passage.coeff_defender_writing, passage.coeff_defender_oral,
start=["Coefficient", ""]) passage.coeff_opponent_writing, passage.coeff_opponent_oral,
subtotal = ["Sous-total", ""] passage.coeff_reviewer_writing, passage.coeff_reviewer_oral]
footer = [average, coeffs, subtotal, 26 * [""]] + ([passage.coeff_observer_writing, passage.coeff_observer_oral] if has_observer else [])
for passage in passages),
start=[_("Coefficient"), ""])
subtotal = [_("Subtotal"), ""]
footer = [average, coeffs, subtotal, 34 * [""]]
min_row = 5 min_row = 5
max_row = min_row + self.juries.count() max_row = min_row + self.juries.count()
@ -1234,8 +1299,14 @@ class Pool(models.Model):
subtotal.extend([f"={rep_w_col}{max_row + 1} * {rep_w_col}{max_row + 2}" subtotal.extend([f"={rep_w_col}{max_row + 1} * {rep_w_col}{max_row + 2}"
f" + {rep_o_col}{max_row + 1} * {rep_o_col}{max_row + 2}", ""]) f" + {rep_o_col}{max_row + 1} * {rep_o_col}{max_row + 2}", ""])
if has_observer:
obs_w_col = getcol(min_column + passage_width * i + 6)
obs_o_col = getcol(min_column + passage_width * i + 7)
subtotal.extend([f"={obs_w_col}{max_row + 1} * {obs_w_col}{max_row + 2}"
f" + {obs_o_col}{max_row + 1} * {obs_o_col}{max_row + 2}", ""])
ranking = [ ranking = [
["Équipe", "", "Problème", "Total", "Rang"], [_("Team"), "", _("Problem"), _("Total"), _("Rank")],
] ]
all_passages = Passage.objects.filter(pool__tournament=self.tournament, all_passages = Passage.objects.filter(pool__tournament=self.tournament,
pool__round=self.round, pool__round=self.round,
@ -1258,14 +1329,22 @@ class Pool(models.Model):
reviewer_col = reviewer_passage.position - 1 reviewer_col = reviewer_passage.position - 1
formula = "=" formula = "="
formula += (f"'Poule {defender_passage.pool.short_name}'" formula += (f"'{_('Pool')} {defender_passage.pool.short_name}'"
f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender
formula += (f" + 'Poule {opponent_passage.pool.short_name}'" formula += (f" + '{_('Pool')} {opponent_passage.pool.short_name}'"
f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent
formula += (f" + 'Poule {reviewer_passage.pool.short_name}'" formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'"
f"!{getcol(min_column + reviewer_col * passage_width + 4)}{reviewer_row + 3}") # reviewer f"!{getcol(min_column + reviewer_col * passage_width + 4)}{reviewer_row + 3}") # reviewer
if has_observer:
observer_passage = Passage.objects.get(observer=participation,
pool__tournament=self.tournament, pool__round=self.round)
observer_row = 5 + observer_passage.pool.juries.count()
observer_col = observer_passage.position - 1
formula += (f" + '{_('Pool')} {observer_passage.pool.short_name}'"
f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}")
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "", ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
f"='Poule {defender_passage.pool.short_name}'" f"='{_('Pool')} {defender_passage.pool.short_name}'"
f"!${getcol(3 + defender_col * passage_width)}$1", f"!${getcol(3 + defender_col * passage_width)}$1",
formula, formula,
f"=RANG(D{max_row + 6 + i}; " f"=RANG(D{max_row + 6 + i}; "
@ -1273,8 +1352,8 @@ class Pool(models.Model):
all_values = header + notes + footer + ranking all_values = header + notes + footer + ranking
worksheet.batch_clear([f"A1:Z{max_row + 5 + pool_size}"]) worksheet.batch_clear([f"A1:AH{max_row + 5 + pool_size}"])
worksheet.update("A1:Z", all_values, raw=False) worksheet.update("A1:AH", all_values, raw=False)
format_requests = [] format_requests = []
@ -1300,13 +1379,13 @@ class Pool(models.Model):
for i in range(pool_size + 1): for i in range(pool_size + 1):
merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}") merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}")
format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:Z", worksheet.id)}}) format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:AH", worksheet.id)}})
for name in merge_cells: for name in merge_cells:
grid_range = a1_range_to_grid_range(name, worksheet.id) grid_range = a1_range_to_grid_range(name, worksheet.id)
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}}) format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
# Make titles bold # Make titles bold
bold_ranges = [("A1:Z", False), ("A1:Z3", True), bold_ranges = [("A1:AH", False), ("A1:AH3", True),
(f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)] (f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)]
for bold_range, bold in bold_ranges: for bold_range, bold in bold_ranges:
format_requests.append({ format_requests.append({
@ -1318,7 +1397,7 @@ class Pool(models.Model):
}) })
# Set background color for headers and footers # Set background color for headers and footers
bg_colors = [("A1:Z", (1, 1, 1)), bg_colors = [("A1:AH", (1, 1, 1)),
(f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)), (f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)),
(f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)), (f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)),
(f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)), (f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)),
@ -1407,7 +1486,7 @@ class Pool(models.Model):
}) })
# Define borders # Define borders
border_ranges = [("A1:Z", "0000"), border_ranges = [("A1:AH", "0000"),
(f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"), (f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"),
(f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"), (f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"),
(f"A1:B{max_row + 3}", "1113"), (f"A1:B{max_row + 3}", "1113"),
@ -1443,7 +1522,7 @@ class Pool(models.Model):
for j in range(passage_width): for j in range(passage_width):
column = getcol(min_column + i * passage_width + j) column = getcol(min_column + i * passage_width + j)
min_note = 0 min_note = 0
max_note = 20 if j < 2 else 10 max_note = 20 if j < 2 and settings.TFJM_APP == "TFJM" else 10
format_requests.append({ format_requests.append({
"setDataValidation": { "setDataValidation": {
"range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id), "range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id),
@ -1453,8 +1532,8 @@ class Pool(models.Model):
"values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); ' "values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); '
f'{column}4>={min_note}; {column}4<={max_note})'},], f'{column}4>={min_note}; {column}4<={max_note})'},],
}, },
"inputMessage": f"La saisie doit être un entier valide " "inputMessage": (_("Input must be a valid integer between {min_note} and {max_note}.")
f"compris entre {min_note} et {max_note}.", .format(min_note=min_note, max_note=max_note)),
"strict": True, "strict": True,
}, },
} }
@ -1482,16 +1561,15 @@ class Pool(models.Model):
}) })
# Protect the header, the juries list, the footer and the ranking # Protect the header, the juries list, the footer and the ranking
protected_ranges = ["A1:Z4", protected_ranges = ["A1:AH4",
f"A{min_row}:B{max_row}", f"A{min_row}:B{max_row}",
f"A{max_row}:Z{max_row + 5 + pool_size}"] f"A{max_row}:AH{max_row + 5 + pool_size}"]
for protected_range in protected_ranges: for protected_range in protected_ranges:
format_requests.append({ format_requests.append({
"addProtectedRange": { "addProtectedRange": {
"protectedRange": { "protectedRange": {
"range": a1_range_to_grid_range(protected_range, worksheet.id), "range": a1_range_to_grid_range(protected_range, worksheet.id),
"description": "Structure du tableur à ne pas modifier " "description": _("Don't update the table structure for a better automated integration."),
"pour une meilleure prise en charge automatisée",
"warningOnly": True, "warningOnly": True,
}, },
} }
@ -1505,9 +1583,9 @@ class Pool(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id) spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
worksheet = spreadsheet.worksheet(f"Poule {self.short_name}") worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
average_cell = worksheet.find("Moyenne") average_cell = worksheet.find(_("Average"))
min_row = 5 min_row = 5
max_row = average_cell.row - 1 max_row = average_cell.row - 1
juries_visible = worksheet.get(f"A{min_row}:B{max_row}") juries_visible = worksheet.get(f"A{min_row}:B{max_row}")
@ -1527,16 +1605,17 @@ class Pool(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
self.tournament.create_spreadsheet() self.tournament.create_spreadsheet()
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id) spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
worksheet = spreadsheet.worksheet(f"Poule {self.short_name}") worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
average_cell = worksheet.find("Moyenne") average_cell = worksheet.find(_("Average"))
min_row = 5 min_row = 5
max_row = average_cell.row - 2 max_row = average_cell.row - 2
data = worksheet.get_values(f"A{min_row}:Z{max_row}") data = worksheet.get_values(f"A{min_row}:AH{max_row}")
if not data or not data[0]: if not data or not data[0]:
return return
passage_width = 6 has_observer = settings.TFJM_APP == "ETEAM" and self.participations.count() >= 4
passage_width = 6 + (2 if has_observer else 0)
for line in data: for line in data:
jury_name = line[0] jury_name = line[0]
jury_id = line[1] jury_id = line[1]
@ -1640,56 +1719,87 @@ class Passage(models.Model):
def average_defender_writing(self) -> float: def average_defender_writing(self) -> float:
return self.avg(note.defender_writing for note in self.notes.all()) return self.avg(note.defender_writing for note in self.notes.all())
@property
def coeff_defender_writing(self) -> float:
return 1 if settings.TFJM_APP == "TFJM" else 2
@property @property
def average_defender_oral(self) -> float: def average_defender_oral(self) -> float:
return self.avg(note.defender_oral for note in self.notes.all()) return self.avg(note.defender_oral for note in self.notes.all())
@property
def coeff_defender_oral(self) -> float:
coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
coeff *= 1 - 0.25 * self.defender_penalties
return coeff
@property @property
def average_defender(self) -> float: def average_defender(self) -> float:
writing_coeff = 1 if settings.TFJM_APP == "TFJM" else 2 return (self.coeff_defender_writing * self.average_defender_writing
oral_coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3 + self.coeff_defender_oral * self.average_defender_oral)
oral_coeff *= 1 - 0.25 * self.defender_penalties
return writing_coeff * self.average_defender_writing + oral_coeff * self.average_defender_oral
@property @property
def average_opponent_writing(self) -> float: def average_opponent_writing(self) -> float:
return self.avg(note.opponent_writing for note in self.notes.all()) return self.avg(note.opponent_writing for note in self.notes.all())
@property
def coeff_opponent_writing(self) -> float:
return 0.9 if not self.observer else 0.6
@property @property
def average_opponent_oral(self) -> float: def average_opponent_oral(self) -> float:
return self.avg(note.opponent_oral for note in self.notes.all()) return self.avg(note.opponent_oral for note in self.notes.all())
@property
def coeff_opponent_oral(self) -> float:
return 2
@property @property
def average_opponent(self) -> float: def average_opponent(self) -> float:
writing_coeff = 0.9 if not self.observer else 0.6 return (self.coeff_opponent_writing * self.average_opponent_writing
oral_coeff = 2 + self.coeff_opponent_oral * self.average_opponent_oral)
return writing_coeff * self.average_opponent_writing + oral_coeff * self.average_opponent_oral
@property @property
def average_reviewer_writing(self) -> float: def average_reviewer_writing(self) -> float:
return self.avg(note.reviewer_writing for note in self.notes.all()) return self.avg(note.reviewer_writing for note in self.notes.all())
@property
def coeff_reviewer_writing(self):
return 0.9 if not self.observer else 0.6
@property @property
def average_reviewer_oral(self) -> float: def average_reviewer_oral(self) -> float:
return self.avg(note.reviewer_oral for note in self.notes.all()) return self.avg(note.reviewer_oral for note in self.notes.all())
@property
def coeff_reviewer_oral(self):
return 1 if settings.TFJM_APP == "TFJM" else 1.2
@property @property
def average_reviewer(self) -> float: def average_reviewer(self) -> float:
writing_coeff = 0.9 if not self.observer else 0.6 return (self.coeff_reviewer_writing * self.average_reviewer_writing
oral_coeff = 1 if settings.TFJM_APP == "TFJM" else 1.2 + self.coeff_reviewer_oral * self.average_reviewer_oral)
return writing_coeff * self.average_reviewer_writing + oral_coeff * self.average_reviewer_oral
@property @property
def average_observer_writing(self) -> float: def average_observer_writing(self) -> float:
return self.avg(note.observer_writing for note in self.notes.all()) return self.avg(note.observer_writing for note in self.notes.all())
@property
def coeff_observer_writing(self):
return 0.6
@property @property
def average_observer_oral(self) -> float: def average_observer_oral(self) -> float:
return self.avg(note.observer_oral for note in self.notes.all()) return self.avg(note.observer_oral for note in self.notes.all())
@property
def coeff_observer_oral(self):
return 0.5
@property @property
def average_observer(self) -> float: def average_observer(self) -> float:
return 0.6 * self.average_observer_writing + 0.5 * self.average_observer_oral return (self.coeff_observer_writing * self.average_observer_writing
+ self.coeff_observer_oral * self.average_observer_oral)
@property @property
def averages(self): def averages(self):
@ -1707,9 +1817,7 @@ class Passage(models.Model):
avg = self.average_defender if participation == self.defender else self.average_opponent \ avg = self.average_defender if participation == self.defender else self.average_opponent \
if participation == self.opponent else self.average_reviewer if participation == self.reviewer \ if participation == self.opponent else self.average_reviewer if participation == self.reviewer \
else self.average_observer if participation == self.observer else 0 else self.average_observer if participation == self.observer else 0
avg *= self.pool.coeff
if self.pool.round == 3 and settings.TFJM_APP == "ETEAM":
avg *= math.pi - 2
return avg return avg
@ -1957,7 +2065,7 @@ class Note(models.Model):
passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk) passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk)
spreadsheet_id = passage.pool.tournament.notes_sheet_id spreadsheet_id = passage.pool.tournament.notes_sheet_id
spreadsheet = gc.open_by_key(spreadsheet_id) spreadsheet = gc.open_by_key(spreadsheet_id)
worksheet = spreadsheet.worksheet(f"Poule {passage.pool.short_name}") worksheet = spreadsheet.worksheet(f"{_('Pool')} {passage.pool.short_name}")
jury_id_cell = worksheet.find(str(self.jury_id), in_column=2) jury_id_cell = worksheet.find(str(self.jury_id), in_column=2)
if not jury_id_cell: if not jury_id_cell:
raise ValueError("The jury ID cell was not found in the spreadsheet.") raise ValueError("The jury ID cell was not found in the spreadsheet.")

View File

@ -82,13 +82,17 @@
{% trans "Average points for the defender writing" %} {% trans "Average points for the defender writing" %}
({{ passage.defender.team.trigram }}) : ({{ passage.defender.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_defender_writing|floatformat }}/20</dd> <dd class="col-sm-4">
{{ passage.average_defender_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
</dd>
<dt class="col-sm-8"> <dt class="col-sm-8">
{% trans "Average points for the defender oral" %} {% trans "Average points for the defender oral" %}
({{ passage.defender.team.trigram }}) : ({{ passage.defender.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_defender_oral|floatformat }}/20</dd> <dd class="col-sm-4">
{{ passage.average_defender_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
</dd>
<dt class="col-sm-8"> <dt class="col-sm-8">
{% trans "Average points for the opponent writing" %} {% trans "Average points for the opponent writing" %}
@ -136,19 +140,23 @@
{% trans "Defender points" %} {% trans "Defender points" %}
({{ passage.defender.team.trigram }}) : ({{ passage.defender.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_defender|floatformat }}/52</dd> <dd class="col-sm-4">
{{ passage.average_defender|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
</dd>
<dt class="col-sm-8"> <dt class="col-sm-8">
{% trans "Opponent points" %} {% trans "Opponent points" %}
({{ passage.opponent.team.trigram }}) : ({{ passage.opponent.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_opponent|floatformat }}/29</dd> <dd class="col-sm-4">
{{ passage.average_opponent|floatformat }}/{% if TFJM_APP == "TFJM" %}29{% else %}{% if passage.observer %}26{% else %}29{% endif %}{% endif %}
</dd>
<dt class="col-sm-8"> <dt class="col-sm-8">
{% trans "reviewer points" %} {% trans "reviewer points" %}
({{ passage.reviewer.team.trigram }}) : ({{ passage.reviewer.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_reviewer|floatformat }}/19</dd> <dd class="col-sm-4">{{ passage.average_reviewer|floatformat }}/{% if TFJM_APP == "TFJM" %}19{% else %}{% if passage.observer %}18{% else %}21{% endif %}{% endif %}</dd>
{% if passage.observer %} {% if passage.observer %}
<dt class="col-sm-8"> <dt class="col-sm-8">
@ -156,7 +164,7 @@
({{ passage.observer.team.trigram }}) : ({{ passage.observer.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/10</dd> <dd class="col-sm-4">{{ passage.average_observer|floatformat }}/6</dd>
{% endif %} {% endif %}
</dl> </dl>
</div> </div>

View File

@ -24,7 +24,7 @@ from django.http import FileResponse, Http404, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone, translation
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import localtime from django.utils.timezone import localtime
@ -1254,7 +1254,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
return self.form_invalid(form) return self.form_invalid(form)
for vr, notes in parsed_notes.items(): for vr, notes in parsed_notes.items():
notes_count = 6 notes_count = 6 + (2 if pool.participations.count() >= 4 and settings.TFJM_APP == "ETEAM" else 0)
for i, passage in enumerate(pool.passages.all()): for i, passage in enumerate(pool.passages.all()):
note = Note.objects.get_or_create(jury=vr, passage=passage)[0] note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
passage_notes = notes[notes_count * i:notes_count * (i + 1)] passage_notes = notes[notes_count * i:notes_count * (i + 1)]
@ -1289,8 +1289,11 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
return self.handle_no_permission() 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
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
pool_size = self.object.passages.count() pool_size = self.object.passages.count()
passage_width = 6 has_observer = self.object.participations.count() >= 4 and settings.TFJM_APP == "ETEAM"
passage_width = 6 + (2 if has_observer else 0)
line_length = pool_size * passage_width line_length = pool_size * passage_width
def getcol(number: int) -> str: def getcol(number: int) -> str:
@ -1475,79 +1478,96 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_pb = TableRow() header_pb = TableRow()
table.addElement(header_pb) table.addElement(header_pb)
problems_tc = TableCell(valuetype="string", stylename=title_style_topleft) problems_tc = TableCell(valuetype="string", stylename=title_style_topleft)
problems_tc.addElement(P(text="Problème")) problems_tc.addElement(P(text=_("Problem")))
problems_tc.setAttribute('numbercolumnsspanned', "2") problems_tc.setAttribute('numbercolumnsspanned', "2")
header_pb.addElement(problems_tc) header_pb.addElement(problems_tc)
header_pb.addElement(CoveredTableCell()) header_pb.addElement(CoveredTableCell())
for passage in self.object.passages.all(): for passage in self.object.passages.all():
tc = TableCell(valuetype="string", stylename=title_style_topleftright) tc = TableCell(valuetype="string", stylename=title_style_topleftright)
tc.addElement(P(text=f"Problème {passage.solution_number}")) tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
tc.setAttribute('numbercolumnsspanned', "6") tc.setAttribute('numbercolumnsspanned', str(passage_width))
header_pb.addElement(tc) header_pb.addElement(tc)
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=5)) header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=passage_width - 1))
# Add roles on the second line of the table # Add roles on the second line of the table
header_role = TableRow() header_role = TableRow()
table.addElement(header_role) table.addElement(header_role)
role_tc = TableCell(valuetype="string", stylename=title_style_left) role_tc = TableCell(valuetype="string", stylename=title_style_left)
role_tc.addElement(P(text="Rôle")) role_tc.addElement(P(text=_("Role")))
role_tc.setAttribute('numbercolumnsspanned', "2") role_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(role_tc) header_role.addElement(role_tc)
header_role.addElement(CoveredTableCell()) header_role.addElement(CoveredTableCell())
for i in range(pool_size): for i in range(pool_size):
defender_tc = TableCell(valuetype="string", stylename=title_style_left) defender_tc = TableCell(valuetype="string", stylename=title_style_left)
defender_tc.addElement(P(text="Défenseur⋅se")) defender_tc.addElement(P(text=_("Defender")))
defender_tc.setAttribute('numbercolumnsspanned', "2") defender_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(defender_tc) header_role.addElement(defender_tc)
header_role.addElement(CoveredTableCell()) header_role.addElement(CoveredTableCell())
opponent_tc = TableCell(valuetype="string", stylename=title_style) opponent_tc = TableCell(valuetype="string", stylename=title_style)
opponent_tc.addElement(P(text="Opposant⋅e")) opponent_tc.addElement(P(text=_("Opponent")))
opponent_tc.setAttribute('numbercolumnsspanned', "2") opponent_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(opponent_tc) header_role.addElement(opponent_tc)
header_role.addElement(CoveredTableCell()) header_role.addElement(CoveredTableCell())
reviewer_tc = TableCell(valuetype="string", reviewer_tc = TableCell(valuetype="string",
stylename=title_style_right) stylename=title_style if has_observer else title_style_right)
reviewer_tc.addElement(P(text="Rapporteur⋅rice")) reviewer_tc.addElement(P(text=_("Reviewer")))
reviewer_tc.setAttribute('numbercolumnsspanned', "2") reviewer_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(reviewer_tc) header_role.addElement(reviewer_tc)
header_role.addElement(CoveredTableCell()) header_role.addElement(CoveredTableCell())
if has_observer:
observer_tc = TableCell(valuetype="string", stylename=title_style_right)
observer_tc.addElement(P(text=_("Observer")))
observer_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(observer_tc)
header_role.addElement(CoveredTableCell())
# Add maximum notes on the third line # Add maximum notes on the third line
header_notes = TableRow() header_notes = TableRow()
table.addElement(header_notes) table.addElement(header_notes)
jury_tc = TableCell(valuetype="string", value="Juré⋅e", stylename=title_style_botleft) jury_tc = TableCell(valuetype="string", value=_("Juree"), stylename=title_style_botleft)
jury_tc.addElement(P(text="Juré⋅e")) jury_tc.addElement(P(text=_("Juree")))
jury_tc.setAttribute('numbercolumnsspanned', "2") jury_tc.setAttribute('numbercolumnsspanned', "2")
header_notes.addElement(jury_tc) header_notes.addElement(jury_tc)
header_notes.addElement(CoveredTableCell()) header_notes.addElement(CoveredTableCell())
for i in range(pool_size): for i in range(pool_size):
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft) defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
defender_w_tc.addElement(P(text="Écrit (/20)")) defender_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
header_notes.addElement(defender_w_tc) header_notes.addElement(defender_w_tc)
defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot) defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
defender_o_tc.addElement(P(text="Oral (/20)")) defender_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
header_notes.addElement(defender_o_tc) header_notes.addElement(defender_o_tc)
opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot) opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
opponent_w_tc.addElement(P(text="Écrit (/10)")) opponent_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
header_notes.addElement(opponent_w_tc) header_notes.addElement(opponent_w_tc)
opponent_o_tc = TableCell(valuetype="string", stylename=title_style_bot) opponent_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
opponent_o_tc.addElement(P(text="Oral (/10)")) opponent_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
header_notes.addElement(opponent_o_tc) header_notes.addElement(opponent_o_tc)
reviewer_w_tc = TableCell(valuetype="string", stylename=title_style_bot) reviewer_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
reviewer_w_tc.addElement(P(text="Écrit (/10)")) reviewer_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
header_notes.addElement(reviewer_w_tc) header_notes.addElement(reviewer_w_tc)
reviewer_o_tc = TableCell(valuetype="string", stylename=title_style_botright) reviewer_o_tc = TableCell(valuetype="string",
reviewer_o_tc.addElement(P(text="Oral (/10)")) stylename=title_style_bot if has_observer else title_style_botright)
reviewer_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
header_notes.addElement(reviewer_o_tc) header_notes.addElement(reviewer_o_tc)
if has_observer:
observer_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
observer_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
header_notes.addElement(observer_w_tc)
observer_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
observer_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
header_notes.addElement(observer_o_tc)
# Add a notation line for each jury # Add a notation line for each jury
for jury in self.object.juries.all(): for jury in self.object.juries.all():
jury_row = TableRow() jury_row = TableRow()
@ -1577,7 +1597,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
average_row = TableRow() average_row = TableRow()
table.addElement(average_row) table.addElement(average_row)
average_tc = TableCell(valuetype="string", stylename=title_style_topleftright) average_tc = TableCell(valuetype="string", stylename=title_style_topleftright)
average_tc.addElement(P(text="Moyenne")) average_tc.addElement(P(text=_("Average")))
average_tc.setAttribute('numbercolumnsspanned', "2") average_tc.setAttribute('numbercolumnsspanned', "2")
average_row.addElement(average_tc) average_row.addElement(average_tc)
average_row.addElement(CoveredTableCell()) average_row.addElement(CoveredTableCell())
@ -1596,40 +1616,50 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
coeff_row = TableRow() coeff_row = TableRow()
table.addElement(coeff_row) table.addElement(coeff_row)
coeff_tc = TableCell(valuetype="string", stylename=title_style_leftright) coeff_tc = TableCell(valuetype="string", stylename=title_style_leftright)
coeff_tc.addElement(P(text="Coefficient")) coeff_tc.addElement(P(text=_("Coefficient")))
coeff_tc.setAttribute('numbercolumnsspanned', "2") coeff_tc.setAttribute('numbercolumnsspanned', "2")
coeff_row.addElement(coeff_tc) coeff_row.addElement(coeff_tc)
coeff_row.addElement(CoveredTableCell()) coeff_row.addElement(CoveredTableCell())
for passage in self.object.passages.all(): for passage in self.object.passages.all():
defender_w_tc = TableCell(valuetype="float", value=1, stylename=style_left) defender_w_tc = TableCell(valuetype="float", value=passage.coeff_defender_writing, stylename=style_left)
defender_w_tc.addElement(P(text="1")) defender_w_tc.addElement(P(text=str(passage.coeff_defender_writing)))
coeff_row.addElement(defender_w_tc) coeff_row.addElement(defender_w_tc)
defender_o_tc = TableCell(valuetype="float", value=1.6 - 0.4 * passage.defender_penalties, stylename=style) defender_o_tc = TableCell(valuetype="float", value=passage.coeff_defender_oral, stylename=style)
defender_o_tc.addElement(P(text=str(2 - 0.4 * passage.defender_penalties))) defender_o_tc.addElement(P(text=str(passage.coeff_defender_oral)))
coeff_row.addElement(defender_o_tc) coeff_row.addElement(defender_o_tc)
opponent_w_tc = TableCell(valuetype="float", value=0.9, stylename=style) opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style)
opponent_w_tc.addElement(P(text="1")) opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
coeff_row.addElement(opponent_w_tc) coeff_row.addElement(opponent_w_tc)
opponent_o_tc = TableCell(valuetype="float", value=2, stylename=style) opponent_o_tc = TableCell(valuetype="float", value=passage.coeff_opponent_oral, stylename=style)
opponent_o_tc.addElement(P(text="2")) opponent_o_tc.addElement(P(text=str(passage.coeff_opponent_oral)))
coeff_row.addElement(opponent_o_tc) coeff_row.addElement(opponent_o_tc)
reviewer_w_tc = TableCell(valuetype="float", value=0.9, stylename=style) reviewer_w_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_writing, stylename=style)
reviewer_w_tc.addElement(P(text="1")) reviewer_w_tc.addElement(P(text=str(passage.coeff_reviewer_writing)))
coeff_row.addElement(reviewer_w_tc) coeff_row.addElement(reviewer_w_tc)
reviewer_o_tc = TableCell(valuetype="float", value=1, stylename=style_right) reviewer_o_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_oral,
reviewer_o_tc.addElement(P(text="1")) stylename=style if has_observer else style_right)
reviewer_o_tc.addElement(P(text=str(passage.coeff_reviewer_oral)))
coeff_row.addElement(reviewer_o_tc) coeff_row.addElement(reviewer_o_tc)
if has_observer:
observer_w_tc = TableCell(valuetype="float", value=passage.coeff_observer_writing, stylename=style)
observer_w_tc.addElement(P(text=str(passage.coeff_observer_writing)))
coeff_row.addElement(observer_w_tc)
observer_o_tc = TableCell(valuetype="float", value=passage.coeff_observer_oral, stylename=style_right)
observer_o_tc.addElement(P(text=str(passage.coeff_observer_oral)))
coeff_row.addElement(observer_o_tc)
# Add the subtotal on the next line # Add the subtotal on the next line
subtotal_row = TableRow() subtotal_row = TableRow()
table.addElement(subtotal_row) table.addElement(subtotal_row)
subtotal_tc = TableCell(valuetype="string", stylename=title_style_botleft) subtotal_tc = TableCell(valuetype="string", stylename=title_style_botleft)
subtotal_tc.addElement(P(text="Sous-total")) subtotal_tc.addElement(P(text=_("Subtotal")))
subtotal_tc.setAttribute('numbercolumnsspanned', "2") subtotal_tc.setAttribute('numbercolumnsspanned', "2")
subtotal_row.addElement(subtotal_tc) subtotal_row.addElement(subtotal_tc)
subtotal_row.addElement(CoveredTableCell()) subtotal_row.addElement(CoveredTableCell())
@ -1656,7 +1686,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
rep_w_col = getcol(min_column + passage_width * i + 4) rep_w_col = getcol(min_column + passage_width * i + 4)
rep_o_col = getcol(min_column + passage_width * i + 5) rep_o_col = getcol(min_column + passage_width * i + 5)
reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer, stylename=style_botright) reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer,
stylename=style_bot if has_observer else style_botright)
reviewer_tc.addElement(P(text=str(passage.average_reviewer))) reviewer_tc.addElement(P(text=str(passage.average_reviewer)))
reviewer_tc.setAttribute('numbercolumnsspanned', "2") reviewer_tc.setAttribute('numbercolumnsspanned', "2")
reviewer_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]" reviewer_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]"
@ -1664,6 +1695,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
subtotal_row.addElement(reviewer_tc) subtotal_row.addElement(reviewer_tc)
subtotal_row.addElement(CoveredTableCell()) subtotal_row.addElement(CoveredTableCell())
if has_observer:
obs_w_col = getcol(min_column + passage_width * i + 6)
obs_o_col = getcol(min_column + passage_width * i + 7)
observer_tc = TableCell(valuetype="float", value=passage.average_observer, stylename=style_botright)
observer_tc.addElement(P(text=str(passage.average_observer)))
observer_tc.setAttribute('numbercolumnsspanned', "2")
observer_tc.setAttribute("formula", f"of:=[.{obs_w_col}{max_row + 1}] * [.{obs_w_col}{max_row + 2}]"
f" + [.{obs_o_col}{max_row + 1}] * [.{obs_o_col}{max_row + 2}]")
subtotal_row.addElement(observer_tc)
subtotal_row.addElement(CoveredTableCell())
table.addElement(TableRow()) table.addElement(TableRow())
if self.object.participations.count() == 5: if self.object.participations.count() == 5:
@ -1681,17 +1723,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
scores_header = TableRow() scores_header = TableRow()
table.addElement(scores_header) table.addElement(scores_header)
team_tc = TableCell(valuetype="string", stylename=title_style_topbotleft) team_tc = TableCell(valuetype="string", stylename=title_style_topbotleft)
team_tc.addElement(P(text="Équipe")) team_tc.addElement(P(text=_("Team")))
team_tc.setAttribute('numbercolumnsspanned', "2") team_tc.setAttribute('numbercolumnsspanned', "2")
scores_header.addElement(team_tc) scores_header.addElement(team_tc)
problem_tc = TableCell(valuetype="string", stylename=title_style_topbot) problem_tc = TableCell(valuetype="string", stylename=title_style_topbot)
problem_tc.addElement(P(text="Problème")) problem_tc.addElement(P(text=_("Problem")))
scores_header.addElement(problem_tc) scores_header.addElement(problem_tc)
total_tc = TableCell(valuetype="string", stylename=title_style_topbot) total_tc = TableCell(valuetype="string", stylename=title_style_topbot)
total_tc.addElement(P(text="Total")) total_tc.addElement(P(text=_("Total")))
scores_header.addElement(total_tc) scores_header.addElement(total_tc)
rank_tc = TableCell(valuetype="string", stylename=title_style_topbotright) rank_tc = TableCell(valuetype="string", stylename=title_style_topbotright)
rank_tc.addElement(P(text="Rang")) rank_tc.addElement(P(text=_("Rank")))
scores_header.addElement(rank_tc) scores_header.addElement(rank_tc)
sorted_participations = sorted(self.object.participations.all(), key=lambda p: -self.object.average(p)) sorted_participations = sorted(self.object.participations.all(), key=lambda p: -self.object.average(p))
@ -1707,13 +1749,15 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
problem_tc = TableCell(valuetype="string", problem_tc = TableCell(valuetype="string",
stylename=style_bot if passage.position == pool_size else style) stylename=style_bot if passage.position == pool_size else style)
problem_tc.addElement(P(text=f"Problème {passage.solution_number}")) problem_tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]") problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
team_row.addElement(problem_tc) team_row.addElement(problem_tc)
defender_pos = passage.position - 1 defender_pos = passage.position - 1
opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1 opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1
reviewer_pos = self.object.passages.get(reviewer=passage.defender).position - 1 reviewer_pos = self.object.passages.get(reviewer=passage.defender).position - 1
observer_pos = self.object.passages.get(observer=passage.defender).position - 1 \
if has_observer else None
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender), score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
stylename=style_bot if passage.position == pool_size else style) stylename=style_bot if passage.position == pool_size else style)
@ -1721,7 +1765,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
formula = "of:=" formula = "of:="
formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender
formula += " + " + getcol(min_column + opponent_pos * passage_width + 2) + str(max_row + 3) # Opponent formula += " + " + getcol(min_column + opponent_pos * passage_width + 2) + str(max_row + 3) # Opponent
formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # reviewer formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # Reviewer
if has_observer:
# Observer
formula += " + " + getcol(min_column + observer_pos * passage_width + 6) + str(max_row + 3)
score_tc.setAttribute("formula", formula) score_tc.setAttribute("formula", formula)
team_row.addElement(score_tc) team_row.addElement(score_tc)
@ -1730,7 +1777,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
stylename=style_botright if passage.position == pool_size else style_right) stylename=style_botright if passage.position == pool_size else style_right)
rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1))) rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1)))
rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; " rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; "
f"[.{score_col}${max_row + 6}]:[.{score_col}${max_row + 5 + pool_size}])") f"[.{score_col}${max_row + 6}]:"
f"[.{score_col}${max_row + 5 + pool_size}])")
team_row.addElement(rank_tc) team_row.addElement(rank_tc)
table.addElement(TableRow()) table.addElement(TableRow())
@ -1755,8 +1803,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
return FileResponse(streaming_content=open("/tmp/notes.ods", "rb"), return FileResponse(streaming_content=open("/tmp/notes.ods", "rb"),
content_type="application/vnd.oasis.opendocument.spreadsheet", content_type="application/vnd.oasis.opendocument.spreadsheet",
filename=f"Feuille de notes - {self.object.tournament.name} " filename=f"{_('Notation sheet')} - {self.object.tournament.name} "
f"- Poule {self.object.short_name}.ods") f"- {_('Pool')} {self.object.short_name}.ods")
class NotationSheetTemplateView(VolunteerMixin, DetailView): class NotationSheetTemplateView(VolunteerMixin, DetailView):