1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2024-12-24 17:42:23 +00:00

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 ""
"Project-Id-Version: TFJM\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"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\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
#: draw/models.py:27 participation/admin.py:79 participation/admin.py:144
#: participation/admin.py:176 participation/models.py:727
#: participation/models.py:751 participation/models.py:1060
#: participation/admin.py:176 participation/models.py:781
#: participation/models.py:805 participation/models.py:1114
#: registration/models.py:763
#: registration/templates/registration/payment_form.html:53
msgid "tournament"
@ -95,7 +95,7 @@ msgstr ""
#: chat/models.py:73 draw/models.py:446 draw/models.py:473
#: 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
msgid "pool"
msgstr "poule"
@ -109,7 +109,7 @@ msgstr ""
#: chat/models.py:84 draw/templates/draw/tournament_content.html:277
#: participation/admin.py:172 participation/models.py:261
#: participation/models.py:742
#: participation/models.py:796
#: participation/templates/participation/tournament_harmonize.html:15
#: registration/models.py:158 registration/models.py:754
#: registration/tables.py:39
@ -265,11 +265,11 @@ msgid "teams"
msgstr "équipes"
#: draw/admin.py:92 draw/models.py:245 draw/models.py:465
#: participation/models.py:1064
#: participation/models.py:1118
msgid "round"
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"
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."
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:909 draw/consumers.py:999 draw/consumers.py:1021
#: draw/consumers.py:1112 draw/templates/draw/tournament_content.html:5
#: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:692
#: draw/consumers.py:910 draw/consumers.py:1000 draw/consumers.py:1022
#: draw/consumers.py:1113 draw/templates/draw/tournament_content.html:5
msgid "The draw has not started yet."
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."
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:696 draw/consumers.py:914
#: draw/consumers.py:309 draw/consumers.py:330 draw/consumers.py:626
#: draw/consumers.py:697 draw/consumers.py:915
msgid "This is not the time for this."
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 "
"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:865 draw/consumers.py:990 draw/consumers.py:1088
#: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:832
#: draw/consumers.py:866 draw/consumers.py:991 draw/consumers.py:1089
msgid "Your turn!"
msgstr "À votre tour !"
#: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:991
#: draw/consumers.py:1089
#: draw/consumers.py:616 draw/consumers.py:756 draw/consumers.py:992
#: draw/consumers.py:1090
msgid "It's your turn to draw a problem!"
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."
msgstr "Ce n'est pas votre tour."
#: draw/consumers.py:713
#: draw/consumers.py:714
#, python-brace-format
msgid ""
"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}</"
"strong> : {problem_name}. "
#: draw/consumers.py:717
#: draw/consumers.py:718
msgid "One team more can accept this problem."
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."
msgstr "Aucune autre équipe ne peut accepter ce problème."
#: draw/consumers.py:813
#: draw/consumers.py:814
#, python-brace-format
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."
#: 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!"
msgstr "C'est à vous de lancer le dé !"
#: draw/consumers.py:852
#: draw/consumers.py:853
#, python-brace-format
msgid "The draw of the round {round} is ended."
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."
msgstr "Le tirage au sort du premier tour est terminé."
#: draw/consumers.py:938
#: draw/consumers.py:939
#, python-brace-format
msgid ""
"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}</"
"strong> : {problem_name}."
#: draw/consumers.py:942
#: draw/consumers.py:943
#, python-brace-format
msgid "It remains {remaining} refusals without penalty."
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."
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."
msgstr ""
"Cela ajoute une pénalité de 25&nbsp;% sur le coefficient de l'oral de la "
"défense."
#: draw/consumers.py:1024
#: draw/consumers.py:1025
msgid "This is only available for the final tournament."
msgstr "Cela n'est possible que pour la finale."
#: draw/consumers.py:1028
#: draw/consumers.py:1029
msgid ""
"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 "
@ -449,7 +449,7 @@ msgstr ""
"partir du classement du premier tour, afin de mélanger les équipes entre les "
"deux jours."
#: draw/consumers.py:1038
#: draw/consumers.py:1039
msgid "The draw of the second round is starting!"
msgstr "Le tirage au sort du deuxième tour commence !"
@ -626,7 +626,7 @@ msgid "The current pool where teams select their problems."
msgstr "La poule en cours, où les équipes choisissent leurs problèmes"
#: draw/models.py:239
#, python-brace-format
#, python-brace-format
msgid "The number of the round must be between 1 and {nb}."
msgstr "Le numéro du tour doit être entre 1 et {nb}."
@ -634,7 +634,7 @@ msgstr "Le numéro du tour doit être entre 1 et {nb}."
msgid "rounds"
msgstr "tours"
#: draw/models.py:268 participation/models.py:1072
#: draw/models.py:268 participation/models.py:1126
msgid "letter"
msgstr "lettre"
@ -672,12 +672,12 @@ msgstr "L'instance complète de la poule."
msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}"
#: draw/models.py:447 participation/models.py:1564
#: draw/models.py:447 participation/models.py:1643
msgid "pools"
msgstr "poules"
#: draw/models.py:459 participation/models.py:1050 participation/models.py:1754
#: participation/models.py:1784 participation/models.py:1826
#: draw/models.py:459 participation/models.py:1104 participation/models.py:1862
#: participation/models.py:1892 participation/models.py:1934
msgid "participation"
msgstr "participation"
@ -701,8 +701,9 @@ msgid ""
msgstr ""
"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
#: participation/models.py:1791
#: draw/models.py:496 draw/models.py:519 participation/models.py:1234
#: participation/models.py:1665 participation/models.py:1899
#: participation/views.py:1487 participation/views.py:1752
#, python-brace-format
msgid "Problem #{problem}"
msgstr "Problème n°{problem}"
@ -913,31 +914,31 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}"
msgid "valid"
msgstr "valide"
#: participation/admin.py:87 participation/models.py:763
#: participation/admin.py:87 participation/models.py:817
msgid "selected for final"
msgstr "sélectionnée pour la finale"
#: 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"
msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:1600
#: participation/models.py:1838
#: participation/admin.py:128 participation/models.py:1679
#: participation/models.py:1946
msgid "opponent"
msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:1607
#: participation/models.py:1839
#: participation/admin.py:132 participation/models.py:1686
#: participation/models.py:1947
msgid "reviewer"
msgstr "rapporteur⋅rice"
#: participation/admin.py:136 participation/models.py:1614
#: participation/models.py:1840
#: participation/admin.py:136 participation/models.py:1693
#: participation/models.py:1948
msgid "observer"
msgstr "observateur⋅rice"
#: participation/admin.py:192 participation/models.py:1789
#: participation/admin.py:192 participation/models.py:1897
msgid "problem"
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."
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:170 registration/forms.py:192
#: 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."
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."
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 "
"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:"
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:"
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."
msgstr ""
"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."
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."
msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages."
@ -1287,36 +1288,100 @@ msgstr "finale"
msgid "Google Sheet ID"
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"
msgstr "tournois"
#: participation/models.py:757
#: participation/models.py:811
msgid "valid team"
msgstr "équipe valide"
#: participation/models.py:758
#: participation/models.py:812
msgid "The participation got the validation of the organizers."
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."
msgstr "L'équipe est sélectionnée pour la finale."
#: participation/models.py:768
#: participation/models.py:822
msgid "mention"
msgstr "mention"
#: participation/models.py:775
#: participation/models.py:829
msgid "mention (final)"
msgstr "Mention (pour la finale) :"
#: participation/models.py:785
#: participation/models.py:839
#, python-brace-format
msgid "Participation of the team {name} ({trigram})"
msgstr "Participation de l'équipe {name} ({trigram})"
#: participation/models.py:792
#: participation/models.py:846
#, python-brace-format
msgid ""
"<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 "
"qui n'ont pas encore payé sont : {participants}.</p>"
#: participation/models.py:800
#: participation/models.py:854
msgid "Missing payments"
msgstr "Paiements manquants"
#: participation/models.py:817
#: participation/models.py:871
msgid ""
"<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> "
@ -1348,11 +1413,11 @@ msgstr ""
"pouvez envoyer vos solutions sur <a href='{url}'>votre page de "
"participation</a>.</p>"
#: participation/models.py:827 participation/models.py:841
#: participation/models.py:881 participation/models.py:895
msgid "Solutions due"
msgstr "Rendu des solutions"
#: participation/models.py:833
#: participation/models.py:887
msgid ""
"<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 "
@ -1365,7 +1430,7 @@ msgstr ""
"parties.</p><p>Vous pouvez envoyer vos solutions sur <a href='{url}'>votre "
"page de participation</a>.</p>"
#: participation/models.py:847 registration/models.py:607
#: participation/models.py:901 registration/models.py:607
msgid ""
"<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>."
@ -1375,11 +1440,11 @@ msgstr ""
"{date:%d/%m/%Y %H:%M}. Vous pouvez y participer sur <a href='{url}'>ce lien</"
"a>.</p>"
#: participation/models.py:853 registration/models.py:614
#: participation/models.py:907 registration/models.py:614
msgid "Draw of solutions"
msgstr "Tirage au sort des solutions"
#: participation/models.py:865
#: participation/models.py:919
#, python-brace-format
msgid ""
"<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 "
"{problem}</a>.</p>"
#: participation/models.py:874 participation/models.py:932
#: participation/models.py:991
#: participation/models.py:928 participation/models.py:986
#: participation/models.py:1045
#, python-brace-format
msgid ""
"<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 "
"de synthèse sur <a href='{passage_url}'>cette page</a>.</p>"
#: participation/models.py:883 participation/models.py:941
#: participation/models.py:1000
#: participation/models.py:937 participation/models.py:995
#: participation/models.py:1054
#, python-brace-format
msgid ""
"<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 "
"de synthèse sur <a href='{passage_url}'>cette page</a>.</p>"
#: participation/models.py:893 participation/models.py:951
#: participation/models.py:1010
#: participation/models.py:947 participation/models.py:1005
#: participation/models.py:1064
#, python-brace-format
msgid ""
"<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 "
"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"
msgstr "Premier tour"
#: participation/models.py:925
#: participation/models.py:979
#, python-brace-format
msgid ""
"<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 "
"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
msgid "Second round"
msgstr "Second tour"
#: participation/models.py:984
#: participation/models.py:1038
#, python-brace-format
msgid ""
"<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 "
"solution du problème {problem}</a>.</p>"
#: participation/models.py:1036
#: participation/models.py:1090
#, python-brace-format
msgid ""
"<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 "
"sur la <a href='{url}'>page du tournoi</a>.</p>"
#: participation/models.py:1041
#: participation/models.py:1095
msgid "Tournament ended"
msgstr "Tournoi terminé"
#: participation/models.py:1051 participation/models.py:1094
#: participation/models.py:1105 participation/models.py:1148
msgid "participations"
msgstr "participations"
#: participation/models.py:1066 participation/models.py:1067
#: participation/models.py:1068
#: participation/models.py:1120 participation/models.py:1121
#: participation/models.py:1122
#, python-brace-format
msgid "Round {round}"
msgstr "Tour {round}"
#: participation/models.py:1082
#: participation/models.py:1136
msgid "room"
msgstr "salle"
#: participation/models.py:1084
#: participation/models.py:1138
msgid "Room 1"
msgstr "Salle 1"
#: participation/models.py:1085
#: participation/models.py:1139
msgid "Room 2"
msgstr "Salle 2"
#: participation/models.py:1088
#: participation/models.py:1142
msgid "For 5-teams pools only"
msgstr "Pour les poules de 5 équipe uniquement"
#: participation/models.py:1100
#: participation/models.py:1154
msgid "juries"
msgstr "jurys"
#: participation/models.py:1109
#: participation/models.py:1163
msgid "president of the jury"
msgstr "président⋅e du jury"
#: participation/models.py:1116
#: participation/models.py:1170
msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton"
#: participation/models.py:1117
#: participation/models.py:1171
msgid "The link of the BBB visio for this pool."
msgstr "Le lien du salon BBB pour cette poule."
#: participation/models.py:1122
#: participation/models.py:1176
msgid "results available"
msgstr "résultats disponibles"
#: participation/models.py:1123
#: participation/models.py:1177
msgid ""
"Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given."
@ -1522,33 +1587,61 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées."
#: participation/models.py:1155
#: participation/models.py:1209
msgid "The president of the jury must be part of the jury."
msgstr "Læ président⋅e du jury doit faire partie du jury."
#: participation/models.py: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
msgid "The jury {jury} is not part of the jury for this pool."
msgstr "{jury} ne fait pas partie du jury pour cette poule."
#: participation/models.py:1557
#: participation/models.py:1636
#, python-brace-format
msgid "Pool {code} for tournament {tournament} with teams {teams}"
msgstr "Poule {code} du tournoi {tournament} avec les équipes {teams}"
#: participation/models.py:1577
#: participation/models.py:1656
msgid "position"
msgstr "position"
#: participation/models.py:1584
#: participation/models.py:1663
msgid "defended solution"
msgstr "solution défendue"
#: participation/models.py:1622
#: participation/models.py:1701
msgid "penalties"
msgstr "pénalités"
#: participation/models.py:1624
#: participation/models.py:1703
msgid ""
"Number of penalties for the defender. The defender will loose a 0.5 "
"coefficient per penalty."
@ -1556,128 +1649,128 @@ msgstr ""
"Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 "
"sur sa présentation orale par pénalité."
#: participation/models.py:1721 participation/models.py:1724
#: participation/models.py:1727 participation/models.py:1730
#: participation/models.py:1829 participation/models.py:1832
#: participation/models.py:1835 participation/models.py:1838
#, python-brace-format
msgid "Team {trigram} is not registered in the pool."
msgstr "L'équipe {trigram} n'est pas inscrite dans la poule."
#: participation/models.py:1735
#: participation/models.py:1843
#, python-brace-format
msgid "Passage of {defender} for problem {problem}"
msgstr "Passage de {defender} pour le problème {problem}"
#: participation/models.py:1739 participation/models.py:1748
#: participation/models.py:1833 participation/models.py:1876
#: participation/models.py:1847 participation/models.py:1856
#: participation/models.py:1941 participation/models.py:1984
msgid "passage"
msgstr "passage"
#: participation/models.py:1740
#: participation/models.py:1848
msgid "passages"
msgstr "passages"
#: participation/models.py:1759
#: participation/models.py:1867
msgid "difference"
msgstr "différence"
#: participation/models.py:1760
#: participation/models.py:1868
msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final"
#: participation/models.py:1767
#: participation/models.py:1875
msgid "tweak"
msgstr "harmonisation"
#: participation/models.py:1768
#: participation/models.py:1876
msgid "tweaks"
msgstr "harmonisations"
#: participation/models.py:1796
#: participation/models.py:1904
msgid "solution for the final tournament"
msgstr "solution pour la finale"
#: participation/models.py:1801 participation/models.py:1845
#: participation/models.py:1909 participation/models.py:1953
msgid "file"
msgstr "fichier"
#: participation/models.py:1811
#: participation/models.py:1919
#, python-brace-format
msgid "Solution of team {team} for problem {problem}"
msgstr "Solution de l'équipe {team} pour le problème {problem}"
#: participation/models.py:1813
#: participation/models.py:1921
msgid "for final"
msgstr "pour la finale"
#: participation/models.py:1816
#: participation/models.py:1924
msgid "solution"
msgstr "solution"
#: participation/models.py:1817
#: participation/models.py:1925
msgid "solutions"
msgstr "solutions"
#: participation/models.py:1851
#: participation/models.py:1959
#, python-brace-format
msgid "Synthesis of {team} as {type} for problem {problem} of {defender}"
msgstr ""
"Note de synthèse de l'équipe {team} en tant que {type} pour le problème "
"{problem} de {defender}"
#: participation/models.py:1859
#: participation/models.py:1967
msgid "synthesis"
msgstr "note de synthèse"
#: participation/models.py:1860
#: participation/models.py:1968
msgid "syntheses"
msgstr "notes de synthèse"
#: participation/models.py:1869
#: participation/models.py:1977
msgid "jury"
msgstr "jury"
#: participation/models.py:1881
#: participation/models.py:1989
msgid "defender writing note"
msgstr "note d'écrit défenseur⋅se"
#: participation/models.py:1887
#: participation/models.py:1995
msgid "defender oral note"
msgstr "note d'oral défenseur⋅se"
#: participation/models.py:1893
#: participation/models.py:2001
msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e"
#: participation/models.py:1899
#: participation/models.py:2007
msgid "opponent oral note"
msgstr "note d'oral opposant⋅e"
#: participation/models.py:1905
#: participation/models.py:2013
msgid "reviewer writing note"
msgstr "note d'écrit rapporteur⋅rice"
#: participation/models.py:1911
#: participation/models.py:2019
msgid "reviewer oral note"
msgstr "note d'oral du rapporteur⋅rice"
#: participation/models.py:1917
#: participation/models.py:2025
msgid "observer writing note"
msgstr "note d'écrit de l'observateur⋅rice"
#: participation/models.py:1923
#: participation/models.py:2031
msgid "observer oral note"
msgstr "note d'oral de l'observateur⋅rice"
#: participation/models.py:1988
#: participation/models.py:2096
#, python-brace-format
msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}"
#: participation/models.py:1991
#: participation/models.py:2099
msgid "note"
msgstr "note"
#: participation/models.py:1992
#: participation/models.py:2100
msgid "notes"
msgstr "notes"
@ -1718,8 +1811,8 @@ msgstr "Pas d'équipe définie"
#: participation/tables.py:147
#: participation/templates/participation/note_form.html:14
#: participation/templates/participation/passage_detail.html:15
#: participation/templates/participation/passage_detail.html:168
#: participation/templates/participation/passage_detail.html:174
#: participation/templates/participation/passage_detail.html:176
#: participation/templates/participation/passage_detail.html:182
#: participation/templates/participation/pool_detail.html:13
#: participation/templates/participation/pool_detail.html:152
#: participation/templates/participation/team_detail.html:185
@ -1807,7 +1900,7 @@ msgid "Upload solution"
msgstr "Envoyer une solution"
#: 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/team_detail.html:245
#: 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."
#: participation/templates/participation/passage_detail.html:61
#: participation/templates/participation/passage_detail.html:173
#: participation/templates/participation/passage_detail.html:181
msgid "Update notes"
msgstr "Modifier les notes"
#: participation/templates/participation/passage_detail.html:66
#: participation/templates/participation/passage_detail.html:179
#: participation/templates/participation/passage_detail.html:187
msgid "Upload synthesis"
msgstr "Envoyer une note de synthèse"
@ -1887,51 +1980,51 @@ msgstr "Détails des notes"
msgid "Average points for the defender writing"
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"
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"
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"
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"
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"
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"
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"
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"
msgstr "Points de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:142
#: participation/templates/participation/passage_detail.html:148
msgid "Opponent points"
msgstr "Points de l'équipe opposante"
#: participation/templates/participation/passage_detail.html:148
#: participation/templates/participation/passage_detail.html:156
msgid "reviewer points"
msgstr "Points de l'équipe rapportrice"
#: participation/templates/participation/passage_detail.html:155
#: participation/templates/participation/passage_detail.html:163
msgid "observer points"
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
msgid "Update passage"
msgstr "Modifier le passage"
@ -2355,10 +2448,6 @@ msgstr "Dépublier les notes pour le second tour"
msgid "Files available for download"
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
#: registration/models.py:655
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."
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
msgid "Notation sheets of pool {pool} of {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
msgid "Notation sheets of {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."
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]
# Strip cases
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
line_length = 2 + 6 * pool_size
line_length = 2 + (8 if df.iat[1, 8] == "Observer" else 6) * pool_size
continue
if pool_size == 0 or len(line) < line_length:
continue
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
notes = line[2:line_length]
print(name, notes)
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
continue
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):
if n > max_n:
self.add_error('file',

View File

@ -458,7 +458,7 @@ class Tournament(models.Model):
return self.notes_sheet_id
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.share(None, "anyone", "writer", with_link=True)
self.notes_sheet_id = spreadsheet.id
@ -470,17 +470,21 @@ class Tournament(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.notes_sheet_id)
worksheets = spreadsheet.worksheets()
if "Classement final" not in [ws.title for ws in worksheets]:
worksheet = spreadsheet.add_worksheet("Classement final", 100, 26)
if _("Final ranking") not in [ws.title for ws in worksheets]:
worksheet = spreadsheet.add_worksheet(_("Final ranking"), 30, 10)
else:
worksheet = spreadsheet.worksheet("Classement final")
worksheet = spreadsheet.worksheet(_("Final ranking"))
if worksheet.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 = []
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):
line = [f"{participation.team.name} ({participation.team.trigram})"]
lines.append(line)
@ -494,7 +498,7 @@ class Tournament(models.Model):
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
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)
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
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)
if settings.NB_ROUNDS >= 3:
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
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:
# User has no second pool yet
# There is no second pool yet
line.append(0)
line.append(0)
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})")
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)
final_ranking = [["", "", "", ""], ["", "", "", ""], ["Équipe", "Score", "Rang", "Mention"],
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"$F$2:$F${participations.count() + 1}; FALSE)",
f"=SORT($F$2:$F${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)",
f"=SORT($G$2:$G${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", ]]
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; "
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
f"=SORT(${rank_col}$2:${rank_col}${participations.count() + 1}; "
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", ]]
final_ranking += [["", "", ""] for _i in range(participations.count() - 1)]
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)
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 = []
# 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:
grid_range = a1_range_to_grid_range(column, worksheet.id)
format_requests.append({
@ -563,7 +594,7 @@ class Tournament(models.Model):
# Set borders
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")]
sides_names = ['top', 'bottom', 'left', 'right']
styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"]
@ -585,7 +616,7 @@ class Tournament(models.Model):
})
# 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)]
for bold_range, bold in bold_ranges:
format_requests.append({
@ -598,14 +629,18 @@ class Tournament(models.Model):
# Set background color for headers and footers
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"C2:C{participations.count() + 1}", (1, 1, 1)),
(f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)),
(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() + 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:
r, g, b = bg_color
format_requests.append({
@ -622,9 +657,15 @@ class Tournament(models.Model):
(f"D2:D{participations.count() + 1}", "0.0"),
(f"E2:E{participations.count() + 1}", "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"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:
format_requests.append({
"repeatCell": {
@ -643,16 +684,16 @@ class Tournament(models.Model):
})
# 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"I2:J{participations.count() + 1}",
f"A{participations.count() + 4}:C{2 * participations.count() + 4}", ]
for protected_range in protected_ranges:
format_requests.append({
"addProtectedRange": {
"protectedRange": {
"range": a1_range_to_grid_range(protected_range, worksheet.id),
"description": "Structure du tableur à ne pas modifier "
"pour une meilleure prise en charge automatisée",
"description": _("Don't update the table structure for a better automated integration."),
"warningOnly": True,
},
}
@ -666,19 +707,21 @@ class Tournament(models.Model):
# Draw has not been done yet
return
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
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
if max_row == 1:
# There is no team
return
data = worksheet.get_values(f"A2:E{max_row}")
data = worksheet.get_values(f"A2:H{max_row}")
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)
pool1 = self.pools.get(round=1, participations=participation, room=1)
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
@ -701,6 +744,17 @@ class Tournament(models.Model):
create_defaults={'diff': tweak2_nb, 'pool': pool2,
'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()
mentions = worksheet.get_values(f"A{score_cell.row + 1}:D{score_cell.row + nb_participations}")
notes = dict()
@ -1164,26 +1218,31 @@ class Pool(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
worksheets = spreadsheet.worksheets()
if f"Poule {self.short_name}" not in [ws.title for ws in worksheets]:
worksheet = spreadsheet.add_worksheet(f"Poule {self.short_name}", 100, 26)
if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]:
worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}", 100, 34)
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):
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
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()
header = [
sum(([f"Problème {passage.solution_number}"] + (passage_width - 1) * [""]
for passage in passages), start=["Problème", ""]),
sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "",
f"Opposant⋅e ({passage.opponent.team.trigram})", "",
f"Rapporteur⋅rice ({passage.reviewer.team.trigram})", ""]
sum(([_("Problem #{problem}").format(problem=passage.solution_number)] + (passage_width - 1) * [""]
for passage in passages), start=[_("Problem"), ""]),
sum(([f"{_('Defender')} ({passage.defender.team.trigram})", "",
f"{_('Opponent')} ({passage.opponent.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", ""]),
sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"]
for _passage in passages), start=["Juré⋅e", ""]),
sum(([f"{_('Writing')} (/{20 if settings.TFJM_APP == "TFJM" else 10})",
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
@ -1193,6 +1252,8 @@ class Pool(models.Model):
note = passage.notes.filter(jury=jury).first()
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
note.reviewer_writing, note.reviewer_oral])
if has_observer:
line.extend([note.observer_writing, note.observer_oral])
notes.append(line)
notes.append([]) # Add empty line to ensure pretty design
@ -1204,11 +1265,15 @@ class Pool(models.Model):
return ''
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
average = ["Moyenne", ""]
coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] for passage in passages),
start=["Coefficient", ""])
subtotal = ["Sous-total", ""]
footer = [average, coeffs, subtotal, 26 * [""]]
average = [_("Average"), ""]
coeffs = sum(([passage.coeff_defender_writing, passage.coeff_defender_oral,
passage.coeff_opponent_writing, passage.coeff_opponent_oral,
passage.coeff_reviewer_writing, passage.coeff_reviewer_oral]
+ ([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
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}"
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 = [
["Équipe", "", "Problème", "Total", "Rang"],
[_("Team"), "", _("Problem"), _("Total"), _("Rank")],
]
all_passages = Passage.objects.filter(pool__tournament=self.tournament,
pool__round=self.round,
@ -1258,14 +1329,22 @@ class Pool(models.Model):
reviewer_col = reviewer_passage.position - 1
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
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
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
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})", "",
f"='Poule {defender_passage.pool.short_name}'"
f"='{_('Pool')} {defender_passage.pool.short_name}'"
f"!${getcol(3 + defender_col * passage_width)}$1",
formula,
f"=RANG(D{max_row + 6 + i}; "
@ -1273,8 +1352,8 @@ class Pool(models.Model):
all_values = header + notes + footer + ranking
worksheet.batch_clear([f"A1:Z{max_row + 5 + pool_size}"])
worksheet.update("A1:Z", all_values, raw=False)
worksheet.batch_clear([f"A1:AH{max_row + 5 + pool_size}"])
worksheet.update("A1:AH", all_values, raw=False)
format_requests = []
@ -1300,13 +1379,13 @@ class Pool(models.Model):
for i in range(pool_size + 1):
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:
grid_range = a1_range_to_grid_range(name, worksheet.id)
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
# 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)]
for bold_range, bold in bold_ranges:
format_requests.append({
@ -1318,7 +1397,7 @@ class Pool(models.Model):
})
# 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"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)),
@ -1407,7 +1486,7 @@ class Pool(models.Model):
})
# 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"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"),
(f"A1:B{max_row + 3}", "1113"),
@ -1443,7 +1522,7 @@ class Pool(models.Model):
for j in range(passage_width):
column = getcol(min_column + i * passage_width + j)
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({
"setDataValidation": {
"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]+$"); '
f'{column}4>={min_note}; {column}4<={max_note})'},],
},
"inputMessage": f"La saisie doit être un entier valide "
f"compris entre {min_note} et {max_note}.",
"inputMessage": (_("Input must be a valid integer between {min_note} and {max_note}.")
.format(min_note=min_note, max_note=max_note)),
"strict": True,
},
}
@ -1482,16 +1561,15 @@ class Pool(models.Model):
})
# 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{max_row}:Z{max_row + 5 + pool_size}"]
f"A{max_row}:AH{max_row + 5 + pool_size}"]
for protected_range in protected_ranges:
format_requests.append({
"addProtectedRange": {
"protectedRange": {
"range": a1_range_to_grid_range(protected_range, worksheet.id),
"description": "Structure du tableur à ne pas modifier "
"pour une meilleure prise en charge automatisée",
"description": _("Don't update the table structure for a better automated integration."),
"warningOnly": True,
},
}
@ -1505,9 +1583,9 @@ class Pool(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
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
max_row = average_cell.row - 1
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)
self.tournament.create_spreadsheet()
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
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]:
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:
jury_name = line[0]
jury_id = line[1]
@ -1640,56 +1719,87 @@ class Passage(models.Model):
def average_defender_writing(self) -> float:
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
def average_defender_oral(self) -> float:
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
def average_defender(self) -> float:
writing_coeff = 1 if settings.TFJM_APP == "TFJM" else 2
oral_coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
oral_coeff *= 1 - 0.25 * self.defender_penalties
return writing_coeff * self.average_defender_writing + oral_coeff * self.average_defender_oral
return (self.coeff_defender_writing * self.average_defender_writing
+ self.coeff_defender_oral * self.average_defender_oral)
@property
def average_opponent_writing(self) -> float:
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
def average_opponent_oral(self) -> float:
return self.avg(note.opponent_oral for note in self.notes.all())
@property
def coeff_opponent_oral(self) -> float:
return 2
@property
def average_opponent(self) -> float:
writing_coeff = 0.9 if not self.observer else 0.6
oral_coeff = 2
return writing_coeff * self.average_opponent_writing + oral_coeff * self.average_opponent_oral
return (self.coeff_opponent_writing * self.average_opponent_writing
+ self.coeff_opponent_oral * self.average_opponent_oral)
@property
def average_reviewer_writing(self) -> float:
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
def average_reviewer_oral(self) -> float:
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
def average_reviewer(self) -> float:
writing_coeff = 0.9 if not self.observer else 0.6
oral_coeff = 1 if settings.TFJM_APP == "TFJM" else 1.2
return writing_coeff * self.average_reviewer_writing + oral_coeff * self.average_reviewer_oral
return (self.coeff_reviewer_writing * self.average_reviewer_writing
+ self.coeff_reviewer_oral * self.average_reviewer_oral)
@property
def average_observer_writing(self) -> float:
return self.avg(note.observer_writing for note in self.notes.all())
@property
def coeff_observer_writing(self):
return 0.6
@property
def average_observer_oral(self) -> float:
return self.avg(note.observer_oral for note in self.notes.all())
@property
def coeff_observer_oral(self):
return 0.5
@property
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
def averages(self):
@ -1707,9 +1817,7 @@ class Passage(models.Model):
avg = self.average_defender if participation == self.defender else self.average_opponent \
if participation == self.opponent else self.average_reviewer if participation == self.reviewer \
else self.average_observer if participation == self.observer else 0
if self.pool.round == 3 and settings.TFJM_APP == "ETEAM":
avg *= math.pi - 2
avg *= self.pool.coeff
return avg
@ -1957,7 +2065,7 @@ class Note(models.Model):
passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk)
spreadsheet_id = passage.pool.tournament.notes_sheet_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)
if not jury_id_cell:
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" %}
({{ passage.defender.team.trigram }}) :
</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">
{% trans "Average points for the defender oral" %}
({{ passage.defender.team.trigram }}) :
</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">
{% trans "Average points for the opponent writing" %}
@ -113,14 +117,14 @@
({{ passage.reviewer.team.trigram }}) :
</dt>
<dd class="col-sm-4">{{ passage.average_reviewer_oral|floatformat }}/10</dd>
{% if passage.observer %}
<dt class="col-sm-8">
{% trans "Average points for the observer writing" %}
({{ passage.observer.team.trigram }}) :
</dt>
<dd class="col-sm-4">{{ passage.average_observer_writing|floatformat }}/10</dd>
<dt class="col-sm-8">
{% trans "Average points for the observer oral" %}
({{ passage.observer.team.trigram }}) :
@ -136,27 +140,31 @@
{% trans "Defender points" %}
({{ passage.defender.team.trigram }}) :
</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">
{% trans "Opponent points" %}
({{ passage.opponent.team.trigram }}) :
</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">
{% trans "reviewer points" %}
({{ passage.reviewer.team.trigram }}) :
</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 %}
<dt class="col-sm-8">
{% trans "observer points" %}
({{ passage.observer.team.trigram }}) :
</dt>
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/10</dd>
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/6</dd>
{% endif %}
</dl>
</div>

View File

@ -24,7 +24,7 @@ from django.http import FileResponse, Http404, HttpResponse
from django.shortcuts import redirect
from django.template.loader import render_to_string
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.decorators import method_decorator
from django.utils.timezone import localtime
@ -1254,7 +1254,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
return self.form_invalid(form)
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()):
note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
passage_notes = notes[notes_count * i:notes_count * (i + 1)]
@ -1289,8 +1289,11 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
return self.handle_no_permission()
def render_to_response(self, context, **response_kwargs): # noqa: C901
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
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
def getcol(number: int) -> str:
@ -1475,79 +1478,96 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_pb = TableRow()
table.addElement(header_pb)
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")
header_pb.addElement(problems_tc)
header_pb.addElement(CoveredTableCell())
for passage in self.object.passages.all():
tc = TableCell(valuetype="string", stylename=title_style_topleftright)
tc.addElement(P(text=f"Problème {passage.solution_number}"))
tc.setAttribute('numbercolumnsspanned', "6")
tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
tc.setAttribute('numbercolumnsspanned', str(passage_width))
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
header_role = TableRow()
table.addElement(header_role)
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")
header_role.addElement(role_tc)
header_role.addElement(CoveredTableCell())
for i in range(pool_size):
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")
header_role.addElement(defender_tc)
header_role.addElement(CoveredTableCell())
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")
header_role.addElement(opponent_tc)
header_role.addElement(CoveredTableCell())
reviewer_tc = TableCell(valuetype="string",
stylename=title_style_right)
reviewer_tc.addElement(P(text="Rapporteur⋅rice"))
stylename=title_style if has_observer else title_style_right)
reviewer_tc.addElement(P(text=_("Reviewer")))
reviewer_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(reviewer_tc)
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
header_notes = TableRow()
table.addElement(header_notes)
jury_tc = TableCell(valuetype="string", value="Juré⋅e", stylename=title_style_botleft)
jury_tc.addElement(P(text="Juré⋅e"))
jury_tc = TableCell(valuetype="string", value=_("Juree"), stylename=title_style_botleft)
jury_tc.addElement(P(text=_("Juree")))
jury_tc.setAttribute('numbercolumnsspanned', "2")
header_notes.addElement(jury_tc)
header_notes.addElement(CoveredTableCell())
for i in range(pool_size):
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)
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)
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)
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)
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)
reviewer_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
reviewer_o_tc.addElement(P(text="Oral (/10)"))
reviewer_o_tc = TableCell(valuetype="string",
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)
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
for jury in self.object.juries.all():
jury_row = TableRow()
@ -1577,7 +1597,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
average_row = TableRow()
table.addElement(average_row)
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_row.addElement(average_tc)
average_row.addElement(CoveredTableCell())
@ -1596,40 +1616,50 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
coeff_row = TableRow()
table.addElement(coeff_row)
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_row.addElement(coeff_tc)
coeff_row.addElement(CoveredTableCell())
for passage in self.object.passages.all():
defender_w_tc = TableCell(valuetype="float", value=1, stylename=style_left)
defender_w_tc.addElement(P(text="1"))
defender_w_tc = TableCell(valuetype="float", value=passage.coeff_defender_writing, stylename=style_left)
defender_w_tc.addElement(P(text=str(passage.coeff_defender_writing)))
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.addElement(P(text=str(2 - 0.4 * passage.defender_penalties)))
defender_o_tc = TableCell(valuetype="float", value=passage.coeff_defender_oral, stylename=style)
defender_o_tc.addElement(P(text=str(passage.coeff_defender_oral)))
coeff_row.addElement(defender_o_tc)
opponent_w_tc = TableCell(valuetype="float", value=0.9, stylename=style)
opponent_w_tc.addElement(P(text="1"))
opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style)
opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
coeff_row.addElement(opponent_w_tc)
opponent_o_tc = TableCell(valuetype="float", value=2, stylename=style)
opponent_o_tc.addElement(P(text="2"))
opponent_o_tc = TableCell(valuetype="float", value=passage.coeff_opponent_oral, stylename=style)
opponent_o_tc.addElement(P(text=str(passage.coeff_opponent_oral)))
coeff_row.addElement(opponent_o_tc)
reviewer_w_tc = TableCell(valuetype="float", value=0.9, stylename=style)
reviewer_w_tc.addElement(P(text="1"))
reviewer_w_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_writing, stylename=style)
reviewer_w_tc.addElement(P(text=str(passage.coeff_reviewer_writing)))
coeff_row.addElement(reviewer_w_tc)
reviewer_o_tc = TableCell(valuetype="float", value=1, stylename=style_right)
reviewer_o_tc.addElement(P(text="1"))
reviewer_o_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_oral,
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)
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
subtotal_row = TableRow()
table.addElement(subtotal_row)
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_row.addElement(subtotal_tc)
subtotal_row.addElement(CoveredTableCell())
@ -1656,7 +1686,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
rep_w_col = getcol(min_column + passage_width * i + 4)
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.setAttribute('numbercolumnsspanned', "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(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())
if self.object.participations.count() == 5:
@ -1681,17 +1723,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
scores_header = TableRow()
table.addElement(scores_header)
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")
scores_header.addElement(team_tc)
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)
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)
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)
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",
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)}]")
team_row.addElement(problem_tc)
defender_pos = passage.position - 1
opponent_pos = self.object.passages.get(opponent=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),
stylename=style_bot if passage.position == pool_size else style)
@ -1721,7 +1765,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
formula = "of:="
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 + 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)
team_row.addElement(score_tc)
@ -1730,7 +1777,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
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.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)
table.addElement(TableRow())
@ -1755,8 +1803,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
return FileResponse(streaming_content=open("/tmp/notes.ods", "rb"),
content_type="application/vnd.oasis.opendocument.spreadsheet",
filename=f"Feuille de notes - {self.object.tournament.name} "
f"- Poule {self.object.short_name}.ods")
filename=f"{_('Notation sheet')} - {self.object.tournament.name} "
f"- {_('Pool')} {self.object.short_name}.ods")
class NotationSheetTemplateView(VolunteerMixin, DetailView):