Support ODS and CSV formats to read notes from a spreadsheet

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-04-07 09:34:52 +02:00
parent 188b83ce2d
commit b942baea17
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
5 changed files with 211 additions and 214 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-04-03 23:29+0200\n" "POT-Creation-Date: 2024-04-07 09:33+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"
@ -29,15 +29,15 @@ msgstr "équipes"
#: draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 draw/models.py:26 #: draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 draw/models.py:26
#: participation/admin.py:79 participation/admin.py:140 #: participation/admin.py:79 participation/admin.py:140
#: participation/admin.py:171 participation/models.py:648 #: participation/admin.py:171 participation/models.py:656
#: participation/models.py:672 participation/models.py:853 #: participation/models.py:680 participation/models.py:861
#: registration/models.py:674 #: registration/models.py:674
#: registration/templates/registration/payment_form.html:53 #: registration/templates/registration/payment_form.html:53
msgid "tournament" msgid "tournament"
msgstr "tournoi" msgstr "tournoi"
#: draw/admin.py:92 draw/models.py:234 draw/models.py:433 #: draw/admin.py:92 draw/models.py:234 draw/models.py:433
#: participation/models.py:857 #: participation/models.py:865
msgid "round" msgid "round"
msgstr "tour" msgstr "tour"
@ -49,64 +49,64 @@ msgstr "Tirage au sort"
msgid "You are not an organizer." msgid "You are not an organizer."
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice." msgstr "Vous n'êtes pas un⋅e organisateur⋅rice."
#: draw/consumers.py:153 #: draw/consumers.py:162
msgid "The draw is already started." msgid "The draw is already started."
msgstr "Le tirage a déjà commencé." msgstr "Le tirage a déjà commencé."
#: draw/consumers.py:159 #: draw/consumers.py:168
msgid "Invalid format" msgid "Invalid format"
msgstr "Format invalide" msgstr "Format invalide"
#: draw/consumers.py:164 #: draw/consumers.py:173
#, python-brace-format #, python-brace-format
msgid "The sum must be equal to the number of teams: expected {len}, got {sum}" msgid "The sum must be equal to the number of teams: expected {len}, got {sum}"
msgstr "" msgstr ""
"La somme doit être égale au nombre d'équipes : attendu {len}, obtenu {sum}" "La somme doit être égale au nombre d'équipes : attendu {len}, obtenu {sum}"
#: draw/consumers.py:169 #: draw/consumers.py:178
msgid "There can be at most one pool with 5 teams." msgid "There can be at most one pool with 5 teams."
msgstr "Il ne peut y avoir au plus qu'une seule poule de 5 équipes." msgstr "Il ne peut y avoir au plus qu'une seule poule de 5 équipes."
#: draw/consumers.py:209 #: draw/consumers.py:218
msgid "Draw started!" msgid "Draw started!"
msgstr "Le tirage a commencé !" msgstr "Le tirage a commencé !"
#: draw/consumers.py:231 #: draw/consumers.py:240
#, python-brace-format #, python-brace-format
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:242 draw/consumers.py:268 draw/consumers.py:676 #: draw/consumers.py:251 draw/consumers.py:277 draw/consumers.py:685
#: draw/consumers.py:893 draw/consumers.py:982 draw/consumers.py:1004 #: draw/consumers.py:902 draw/consumers.py:991 draw/consumers.py:1013
#: draw/consumers.py:1094 draw/templates/draw/tournament_content.html:5 #: draw/consumers.py:1103 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é."
#: draw/consumers.py:255 #: draw/consumers.py:264
#, python-brace-format #, python-brace-format
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:295 draw/consumers.py:316 draw/consumers.py:611 #: draw/consumers.py:304 draw/consumers.py:325 draw/consumers.py:620
#: draw/consumers.py:681 draw/consumers.py:898 #: draw/consumers.py:690 draw/consumers.py:907
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."
#: draw/consumers.py:308 draw/consumers.py:311 #: draw/consumers.py:317 draw/consumers.py:320
msgid "You've already launched the dice." msgid "You've already launched the dice."
msgstr "Vous avez déjà lancé le dé." msgstr "Vous avez déjà lancé le dé."
#: draw/consumers.py:314 #: draw/consumers.py:323
msgid "It is not your turn." msgid "It is not your turn."
msgstr "Ce n'est pas votre tour." msgstr "Ce n'est pas votre tour."
#: draw/consumers.py:401 #: draw/consumers.py:410
#, python-brace-format #, python-brace-format
msgid "Dices from teams {teams} are identical. Please relaunch your dices." msgid "Dices from teams {teams} are identical. Please relaunch your dices."
msgstr "" msgstr ""
"Les dés des équipes {teams} sont identiques. Merci de relancer vos dés." "Les dés des équipes {teams} sont identiques. Merci de relancer vos dés."
#: draw/consumers.py:1007 #: draw/consumers.py:1016
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."
@ -175,7 +175,7 @@ msgstr "La poule en cours, où les équipes choisissent leurs problèmes"
msgid "rounds" msgid "rounds"
msgstr "tours" msgstr "tours"
#: draw/models.py:257 participation/models.py:871 #: draw/models.py:257 participation/models.py:879
msgid "letter" msgid "letter"
msgstr "lettre" msgstr "lettre"
@ -214,17 +214,17 @@ msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}" msgstr "Poule {letter}{number}"
#: draw/models.py:414 draw/models.py:441 participation/admin.py:136 #: draw/models.py:414 draw/models.py:441 participation/admin.py:136
#: participation/admin.py:155 participation/models.py:1340 #: participation/admin.py:155 participation/models.py:1350
#: participation/models.py:1349 participation/tables.py:84 #: participation/models.py:1359 participation/tables.py:84
msgid "pool" msgid "pool"
msgstr "poule" msgstr "poule"
#: draw/models.py:415 participation/models.py:1341 #: draw/models.py:415 participation/models.py:1351
msgid "pools" msgid "pools"
msgstr "poules" msgstr "poules"
#: draw/models.py:427 participation/models.py:843 participation/models.py:1510 #: draw/models.py:427 participation/models.py:851 participation/models.py:1520
#: participation/models.py:1540 participation/models.py:1582 #: participation/models.py:1550 participation/models.py:1592
msgid "participation" msgid "participation"
msgstr "participation" msgstr "participation"
@ -248,8 +248,8 @@ msgid ""
msgstr "" msgstr ""
"L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1." "L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1."
#: draw/models.py:464 draw/models.py:487 participation/models.py:1363 #: draw/models.py:464 draw/models.py:487 participation/models.py:1373
#: participation/models.py:1547 #: participation/models.py:1557
#, python-brace-format #, python-brace-format
msgid "Problem #{problem}" msgid "Problem #{problem}"
msgstr "Problème n°{problem}" msgstr "Problème n°{problem}"
@ -332,7 +332,7 @@ msgid "Continue draw"
msgstr "Continuer le tirage" msgstr "Continuer le tirage"
#: draw/templates/draw/tournament_content.html:216 participation/admin.py:167 #: draw/templates/draw/tournament_content.html:216 participation/admin.py:167
#: participation/models.py:252 participation/models.py:663 #: participation/models.py:252 participation/models.py:671
#: participation/templates/participation/tournament_harmonize.html:15 #: participation/templates/participation/tournament_harmonize.html:15
#: registration/models.py:157 registration/models.py:665 #: registration/models.py:157 registration/models.py:665
#: registration/tables.py:39 #: registration/tables.py:39
@ -378,8 +378,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
msgid "Close" msgid "Close"
msgstr "Fermer" msgstr "Fermer"
#: draw/views.py:31 participation/views.py:156 participation/views.py:470 #: draw/views.py:31 participation/views.py:161 participation/views.py:475
#: participation/views.py:501 #: participation/views.py:506
msgid "You are not in a team." msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe." msgstr "Vous n'êtes pas dans une équipe."
@ -452,96 +452,96 @@ 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:684 #: participation/admin.py:87 participation/models.py:692
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:183 #: participation/admin.py:124 participation/admin.py:183
#: participation/models.py:1370 participation/tables.py:112 #: participation/models.py:1380 participation/tables.py:112
msgid "defender" msgid "defender"
msgstr "défenseur⋅se" msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:1377 #: participation/admin.py:128 participation/models.py:1387
#: participation/models.py:1594 #: participation/models.py:1604
msgid "opponent" msgid "opponent"
msgstr "opposant⋅e" msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:1384 #: participation/admin.py:132 participation/models.py:1394
#: participation/models.py:1595 #: participation/models.py:1605
msgid "reporter" msgid "reporter"
msgstr "rapporteur⋅rice" msgstr "rapporteur⋅rice"
#: participation/admin.py:187 participation/models.py:1545 #: participation/admin.py:187 participation/models.py:1555
msgid "problem" msgid "problem"
msgstr "numéro de problème" msgstr "numéro de problème"
#: participation/forms.py:30 #: participation/forms.py:32
msgid "This name is already used." msgid "This name is already used."
msgstr "Ce nom est déjà utilisé." msgstr "Ce nom est déjà utilisé."
#: participation/forms.py:37 participation/models.py:42 #: participation/forms.py:39 participation/models.py:42
msgid "The trigram must be composed of three uppercase letters." msgid "The trigram must be composed of three uppercase letters."
msgstr "Le trigramme doit être composé de trois lettres majuscules." msgstr "Le trigramme doit être composé de trois lettres majuscules."
#: participation/forms.py:40 #: participation/forms.py:42
msgid "This trigram is already used." msgid "This trigram is already used."
msgstr "Ce trigramme est déjà utilisé." msgstr "Ce trigramme est déjà utilisé."
#: participation/forms.py:55 #: participation/forms.py:57
msgid "No team was found with this access code." msgid "No team was found with this access code."
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
#: participation/forms.py:59 participation/views.py:472 #: participation/forms.py:61 participation/views.py:477
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:88 participation/forms.py:340 #: participation/forms.py:90 participation/forms.py:350
#: registration/forms.py:122 registration/forms.py:144 #: registration/forms.py:122 registration/forms.py:144
#: registration/forms.py:166 registration/forms.py:188 #: registration/forms.py:166 registration/forms.py:188
#: registration/forms.py:237 registration/forms.py:270 #: registration/forms.py:237 registration/forms.py:270
msgid "The uploaded file size must be under 2 Mo." msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 2 Mo." msgstr "Le fichier envoyé doit peser moins de 2 Mo."
#: participation/forms.py:90 registration/forms.py:124 #: participation/forms.py:92 registration/forms.py:124
#: registration/forms.py:146 registration/forms.py:168 #: registration/forms.py:146 registration/forms.py:168
#: registration/forms.py:190 registration/forms.py:239 #: registration/forms.py:190 registration/forms.py:239
#: registration/forms.py:272 #: registration/forms.py:272
msgid "The uploaded file must be a PDF, PNG of JPEG file." msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG." msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
#: participation/forms.py:108 #: participation/forms.py:110
msgid "I engage myself to participate to the whole TFJM²." msgid "I engage myself to participate to the whole TFJM²."
msgstr "Je m'engage à participer à l'intégralité du TFJM²." msgstr "Je m'engage à participer à l'intégralité du TFJM²."
#: participation/forms.py:123 #: participation/forms.py:125
msgid "Message to address to the team:" msgid "Message to address to the team:"
msgstr "Message à adresser à l'équipe :" msgstr "Message à adresser à l'équipe :"
#: participation/forms.py:158 #: participation/forms.py:160
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:160 participation/forms.py:342 #: participation/forms.py:162 participation/forms.py:352
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."
#: participation/forms.py:164 #: participation/forms.py:166
msgid "The PDF file must not have more than 30 pages." msgid "The PDF file must not have more than 30 pages."
msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages." msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages."
#: participation/forms.py:218 #: participation/forms.py:220
msgid "Add" msgid "Add"
msgstr "Ajouter" msgstr "Ajouter"
#: participation/forms.py:233 #: participation/forms.py:235
msgid "This user already exists, but is a participant." msgid "This user already exists, but is a participant."
msgstr "Cet⋅te utilisateur⋅rice existe déjà, mais en tant que participant⋅e." msgstr "Cet⋅te utilisateur⋅rice existe déjà, mais en tant que participant⋅e."
#: participation/forms.py:244 #: participation/forms.py:246
msgid "CSV file:" msgid "Spreadsheet file:"
msgstr "Tableur au format CSV :" msgstr "Fichier tableur :"
#: participation/forms.py:268 #: participation/forms.py:272
msgid "" msgid ""
"This file contains non-UTF-8 and non-ISO-8859-1 content. Please send your " "This file contains non-UTF-8 and non-ISO-8859-1 content. Please send your "
"sheet as a CSV file." "sheet as a CSV file."
@ -549,30 +549,30 @@ msgstr ""
"Ce fichier contient des éléments non-UTF-8 et non-ISO-8859-1. Merci " "Ce fichier contient des éléments non-UTF-8 et non-ISO-8859-1. Merci "
"d'envoyer votre tableur au format CSV." "d'envoyer votre tableur au format CSV."
#: participation/forms.py:283 #: participation/forms.py:293
msgid "Can't determine the pool size. Are you sure your file is correct?" msgid "Can't determine the pool size. Are you sure your file is correct?"
msgstr "" msgstr ""
"Impossible de déterminer la taille de la poule. Êtes-vous sûr⋅e que le " "Impossible de déterminer la taille de la poule. Êtes-vous sûr⋅e que le "
"fichier est correct ?" "fichier est correct ?"
#: participation/forms.py:303 #: participation/forms.py:313
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:309 #: participation/forms.py:319
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:323 #: participation/forms.py:333
msgid "The defender, the opponent and the reporter must be different." msgid "The defender, the opponent and the reporter must be different."
msgstr "" msgstr ""
"Les équipes défenseuse, opposante et rapportrice doivent être différent⋅es." "Les équipes défenseuse, opposante et rapportrice doivent être différent⋅es."
#: participation/forms.py:327 #: participation/forms.py:337
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:346 #: participation/forms.py:356
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."
@ -797,28 +797,28 @@ 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:649 registration/admin.py:125 #: participation/models.py:657 registration/admin.py:125
msgid "tournaments" msgid "tournaments"
msgstr "tournois" msgstr "tournois"
#: participation/models.py:678 #: participation/models.py:686
msgid "valid team" msgid "valid team"
msgstr "équipe valide" msgstr "équipe valide"
#: participation/models.py:679 #: participation/models.py:687
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:685 #: participation/models.py:693
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:692 #: participation/models.py:700
#, 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:699 #: participation/models.py:707
#, 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 "
@ -831,11 +831,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:707 #: participation/models.py:715
msgid "Missing payments" msgid "Missing payments"
msgstr "Paiements manquants" msgstr "Paiements manquants"
#: participation/models.py:714 #: participation/models.py:722
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> "
@ -850,11 +850,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:723 #: participation/models.py:731
msgid "Solutions due" msgid "Solutions due"
msgstr "Rendu des solutions" msgstr "Rendu des solutions"
#: participation/models.py:729 registration/models.py:518 #: participation/models.py:737 registration/models.py:518
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>."
@ -864,11 +864,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:735 registration/models.py:525 #: participation/models.py:743 registration/models.py:525
msgid "Draw of solutions" msgid "Draw of solutions"
msgstr "Tirage au sort des solutions" msgstr "Tirage au sort des solutions"
#: participation/models.py:746 #: participation/models.py:754
#, 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 "
@ -880,7 +880,7 @@ 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:755 participation/models.py:798 #: participation/models.py:763 participation/models.py:806
#, 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 "
@ -891,7 +891,7 @@ 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:764 participation/models.py:807 #: participation/models.py:772 participation/models.py:815
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"<p>You will report the solution of the team {reporter} on the <a " "<p>You will report the solution of the team {reporter} on the <a "
@ -902,11 +902,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:780 registration/models.py:540 #: participation/models.py:788 registration/models.py:540
msgid "First round" msgid "First round"
msgstr "Premier tour" msgstr "Premier tour"
#: participation/models.py:791 #: participation/models.py:799
#, 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 "
@ -915,11 +915,11 @@ 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:823 registration/models.py:551 #: participation/models.py:831 registration/models.py:551
msgid "Second round" msgid "Second round"
msgstr "Second tour" msgstr "Second tour"
#: participation/models.py:829 #: participation/models.py:837
#, 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 "
@ -928,40 +928,40 @@ 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:834 #: participation/models.py:842
msgid "Tournament ended" msgid "Tournament ended"
msgstr "Tournoi terminé" msgstr "Tournoi terminé"
#: participation/models.py:844 participation/models.py:877 #: participation/models.py:852 participation/models.py:885
msgid "participations" msgid "participations"
msgstr "participations" msgstr "participations"
#: participation/models.py:859 participation/models.py:860 #: participation/models.py:867 participation/models.py:868
#, python-brace-format #, python-brace-format
msgid "Round {round}" msgid "Round {round}"
msgstr "Tour {round}" msgstr "Tour {round}"
#: participation/models.py:883 #: participation/models.py:891
msgid "juries" msgid "juries"
msgstr "jurys" msgstr "jurys"
#: participation/models.py:892 #: participation/models.py:900
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:899 #: participation/models.py:907
msgid "BigBlueButton URL" msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton" msgstr "Lien BigBlueButton"
#: participation/models.py:900 #: participation/models.py:908
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:905 #: participation/models.py:913
msgid "results available" msgid "results available"
msgstr "résultats disponibles" msgstr "résultats disponibles"
#: participation/models.py:906 #: participation/models.py:914
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."
@ -970,37 +970,37 @@ 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:931 #: participation/models.py:939
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:1323 #: participation/models.py:1331
#, 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:1334 #: participation/models.py:1344
#, python-brace-format #, python-brace-format
msgid "Pool of day {round} for tournament {tournament} with teams {teams}" msgid "Pool of day {round} for tournament {tournament} with teams {teams}"
msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}" msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}"
#: participation/models.py:1354 #: participation/models.py:1364
msgid "position" msgid "position"
msgstr "position" msgstr "position"
#: participation/models.py:1361 #: participation/models.py:1371
msgid "defended solution" msgid "defended solution"
msgstr "solution défendue" msgstr "solution défendue"
#: participation/models.py:1394 #: participation/models.py:1404
msgid "observer" msgid "observer"
msgstr "observateur⋅rice" msgstr "observateur⋅rice"
#: participation/models.py:1399 #: participation/models.py:1409
msgid "penalties" msgid "penalties"
msgstr "pénalités" msgstr "pénalités"
#: participation/models.py:1401 #: participation/models.py:1411
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."
@ -1008,124 +1008,124 @@ msgstr ""
"Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 " "Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 "
"sur sa présentation orale par pénalité." "sur sa présentation orale par pénalité."
#: participation/models.py:1477 participation/models.py:1480 #: participation/models.py:1487 participation/models.py:1490
#: participation/models.py:1483 participation/models.py:1486 #: participation/models.py:1493 participation/models.py:1496
#, 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:1491 #: participation/models.py:1501
#, 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:1495 participation/models.py:1504 #: participation/models.py:1505 participation/models.py:1514
#: participation/models.py:1589 participation/models.py:1631 #: participation/models.py:1599 participation/models.py:1641
msgid "passage" msgid "passage"
msgstr "passage" msgstr "passage"
#: participation/models.py:1496 #: participation/models.py:1506
msgid "passages" msgid "passages"
msgstr "passages" msgstr "passages"
#: participation/models.py:1515 #: participation/models.py:1525
msgid "difference" msgid "difference"
msgstr "différence" msgstr "différence"
#: participation/models.py:1516 #: participation/models.py:1526
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:1523 #: participation/models.py:1533
msgid "tweak" msgid "tweak"
msgstr "harmonisation" msgstr "harmonisation"
#: participation/models.py:1524 #: participation/models.py:1534
msgid "tweaks" msgid "tweaks"
msgstr "harmonisations" msgstr "harmonisations"
#: participation/models.py:1552 #: participation/models.py:1562
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:1557 participation/models.py:1600 #: participation/models.py:1567 participation/models.py:1610
msgid "file" msgid "file"
msgstr "fichier" msgstr "fichier"
#: participation/models.py:1567 #: participation/models.py:1577
#, 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:1569 #: participation/models.py:1579
msgid "for final" msgid "for final"
msgstr "pour la finale" msgstr "pour la finale"
#: participation/models.py:1572 #: participation/models.py:1582
msgid "solution" msgid "solution"
msgstr "solution" msgstr "solution"
#: participation/models.py:1573 #: participation/models.py:1583
msgid "solutions" msgid "solutions"
msgstr "solutions" msgstr "solutions"
#: participation/models.py:1606 #: participation/models.py:1616
#, 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:1614 #: participation/models.py:1624
msgid "synthesis" msgid "synthesis"
msgstr "note de synthèse" msgstr "note de synthèse"
#: participation/models.py:1615 #: participation/models.py:1625
msgid "syntheses" msgid "syntheses"
msgstr "notes de synthèse" msgstr "notes de synthèse"
#: participation/models.py:1624 #: participation/models.py:1634
msgid "jury" msgid "jury"
msgstr "jury" msgstr "jury"
#: participation/models.py:1636 #: participation/models.py:1646
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:1642 #: participation/models.py:1652
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:1648 #: participation/models.py:1658
msgid "opponent writing note" msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e" msgstr "note d'écrit opposant⋅e"
#: participation/models.py:1654 #: participation/models.py:1664
msgid "opponent oral note" msgid "opponent oral note"
msgstr "note d'oral opposant⋅e" msgstr "note d'oral opposant⋅e"
#: participation/models.py:1660 #: participation/models.py:1670
msgid "reporter writing note" msgid "reporter writing note"
msgstr "note d'écrit rapporteur⋅rice" msgstr "note d'écrit rapporteur⋅rice"
#: participation/models.py:1666 #: participation/models.py:1676
msgid "reporter oral note" msgid "reporter oral note"
msgstr "note d'oral du rapporteur⋅rice" msgstr "note d'oral du rapporteur⋅rice"
#: participation/models.py:1672 #: participation/models.py:1682
msgid "observer note" msgid "observer note"
msgstr "note de l'observation" msgstr "note de l'observation"
#: participation/models.py:1733 #: participation/models.py:1743
#, 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:1736 #: participation/models.py:1746
msgid "note" msgid "note"
msgstr "note" msgstr "note"
#: participation/models.py:1737 #: participation/models.py:1747
msgid "notes" msgid "notes"
msgstr "notes" msgstr "notes"
@ -1269,7 +1269,7 @@ msgstr "Envoyer une solution"
#: participation/templates/participation/pool_detail.html:162 #: participation/templates/participation/pool_detail.html:162
#: participation/templates/participation/team_detail.html:210 #: participation/templates/participation/team_detail.html:210
#: participation/templates/participation/upload_motivation_letter.html:13 #: participation/templates/participation/upload_motivation_letter.html:13
#: participation/templates/participation/upload_notes.html:25 #: participation/templates/participation/upload_notes.html:24
#: participation/templates/participation/upload_solution.html:11 #: participation/templates/participation/upload_solution.html:11
#: participation/templates/participation/upload_synthesis.html:18 #: participation/templates/participation/upload_synthesis.html:18
#: registration/templates/registration/upload_health_sheet.html:17 #: registration/templates/registration/upload_health_sheet.html:17
@ -1453,8 +1453,8 @@ msgid "Ranking"
msgstr "Classement" msgstr "Classement"
#: participation/templates/participation/pool_detail.html:136 #: participation/templates/participation/pool_detail.html:136
msgid "Upload notes from a CSV file" msgid "Upload notes from a spreadsheet file"
msgstr "Soumettre les notes à partir d'un fichier CSV" msgstr "Soumettre les notes à partir d'un tableur"
#: participation/templates/participation/pool_detail.html:140 #: participation/templates/participation/pool_detail.html:140
msgid "Download notation spreadsheet" msgid "Download notation spreadsheet"
@ -1658,7 +1658,7 @@ msgid "Invalidate"
msgstr "Invalider" msgstr "Invalider"
#: participation/templates/participation/team_detail.html:209 #: participation/templates/participation/team_detail.html:209
#: participation/views.py:327 #: participation/views.py:332
msgid "Upload motivation letter" msgid "Upload motivation letter"
msgstr "Envoyer la lettre de motivation" msgstr "Envoyer la lettre de motivation"
@ -1667,7 +1667,7 @@ msgid "Update team"
msgstr "Modifier l'équipe" msgstr "Modifier l'équipe"
#: participation/templates/participation/team_detail.html:219 #: participation/templates/participation/team_detail.html:219
#: participation/views.py:464 #: participation/views.py:469
msgid "Leave team" msgid "Leave team"
msgstr "Quitter l'équipe" msgstr "Quitter l'équipe"
@ -1820,14 +1820,12 @@ msgstr "Retour aux détails de l'utilisateur⋅rice"
#: participation/templates/participation/upload_notes.html:11 #: participation/templates/participation/upload_notes.html:11
msgid "" msgid ""
"Remember to export your spreadsheet as a CSV file before uploading it here. "
"Rows that are full of zeros are ignored. Unknown juries are not considered." "Rows that are full of zeros are ignored. Unknown juries are not considered."
msgstr "" msgstr ""
"N'oubliez pas d'exporter votre tableur en tant que fichier CSV avant de le " "Les lignes remplies de zéros sont ignorées. Les juré⋅es inconnu⋅es ne sont "
"téléverser ici. Les lignes remplies de zéros sont ignorées. Les juré⋅es " "pas pris⋅es en compte."
"inconnu⋅es ne sont pas pris⋅es en compte."
#: participation/templates/participation/upload_notes.html:19 #: participation/templates/participation/upload_notes.html:18
msgid "Download empty notation sheet" msgid "Download empty notation sheet"
msgstr "Télécharger la fiche de notation vierge" msgstr "Télécharger la fiche de notation vierge"
@ -1839,44 +1837,44 @@ msgstr "Modèles :"
msgid "Warning: non-free format" msgid "Warning: non-free format"
msgstr "Attention : format non libre" msgstr "Attention : format non libre"
#: participation/views.py:56 tfjm/templates/base.html:79 #: participation/views.py:61 tfjm/templates/base.html:79
#: tfjm/templates/navbar.html:35 #: tfjm/templates/navbar.html:35
msgid "Create team" msgid "Create team"
msgstr "Créer une équipe" msgstr "Créer une équipe"
#: participation/views.py:65 participation/views.py:106 #: participation/views.py:70 participation/views.py:111
msgid "You don't participate, so you can't create a team." msgid "You don't participate, so you can't create a team."
msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
#: participation/views.py:67 participation/views.py:108 #: participation/views.py:72 participation/views.py:113
msgid "You are already in a team." msgid "You are already in a team."
msgstr "Vous êtes déjà dans une équipe." msgstr "Vous êtes déjà dans une équipe."
#: participation/views.py:97 tfjm/templates/base.html:74 #: participation/views.py:102 tfjm/templates/base.html:74
#: tfjm/templates/navbar.html:40 #: tfjm/templates/navbar.html:40
msgid "Join team" msgid "Join team"
msgstr "Rejoindre une équipe" msgstr "Rejoindre une équipe"
#: participation/views.py:157 participation/views.py:502 #: participation/views.py:162 participation/views.py:507
msgid "You don't participate, so you don't have any team." msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
#: participation/views.py:183 #: participation/views.py:188
#, python-brace-format #, python-brace-format
msgid "Detail of team {trigram}" msgid "Detail of team {trigram}"
msgstr "Détails de l'équipe {trigram}" msgstr "Détails de l'équipe {trigram}"
#: participation/views.py:212 #: participation/views.py:217
msgid "You don't participate, so you can't request the validation of the team." msgid "You don't participate, so you can't request the validation of the team."
msgstr "" msgstr ""
"Vous ne participez pas, vous ne pouvez pas demander la validation de " "Vous ne participez pas, vous ne pouvez pas demander la validation de "
"l'équipe." "l'équipe."
#: participation/views.py:215 #: participation/views.py:220
msgid "The validation of the team is already done or pending." msgid "The validation of the team is already done or pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." msgstr "La validation de l'équipe est déjà faite ou en cours."
#: participation/views.py:218 #: participation/views.py:223
msgid "" msgid ""
"The team can't be validated: missing email address confirmations, " "The team can't be validated: missing email address confirmations, "
"authorizations, people, motivation letter or the tournament is not set." "authorizations, people, motivation letter or the tournament is not set."
@ -1885,167 +1883,167 @@ msgstr ""
"d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre " "d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre "
"de motivation, soit le tournoi n'a pas été choisi." "de motivation, soit le tournoi n'a pas été choisi."
#: participation/views.py:240 #: participation/views.py:245
msgid "You are not an organizer of the tournament." msgid "You are not an organizer of the tournament."
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi." msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
#: participation/views.py:243 #: participation/views.py:248
msgid "This team has no pending validation." msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente." msgstr "L'équipe n'a pas de validation en attente."
#: participation/views.py:270 #: participation/views.py:275
msgid "You must specify if you validate the registration or not." msgid "You must specify if you validate the registration or not."
msgstr "Vous devez spécifier si vous validez l'inscription ou non." msgstr "Vous devez spécifier si vous validez l'inscription ou non."
#: participation/views.py:305 #: participation/views.py:310
#, python-brace-format #, python-brace-format
msgid "Update team {trigram}" msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {trigram}" msgstr "Mise à jour de l'équipe {trigram}"
#: participation/views.py:366 participation/views.py:449 #: participation/views.py:371 participation/views.py:454
#, python-brace-format #, python-brace-format
msgid "Motivation letter of {team}.{ext}" msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}" msgstr "Lettre de motivation de {team}.{ext}"
#: participation/views.py:398 #: participation/views.py:403
#, python-brace-format #, python-brace-format
msgid "Authorizations of team {trigram}.zip" msgid "Authorizations of team {trigram}.zip"
msgstr "Autorisations de l'équipe {trigram}.zip" msgstr "Autorisations de l'équipe {trigram}.zip"
#: participation/views.py:402 #: participation/views.py:407
#, python-brace-format #, python-brace-format
msgid "Authorizations of {tournament}.zip" msgid "Authorizations of {tournament}.zip"
msgstr "Autorisations du tournoi {tournament}.zip" msgstr "Autorisations du tournoi {tournament}.zip"
#: participation/views.py:418 #: participation/views.py:423
#, python-brace-format #, python-brace-format
msgid "Photo authorization of {participant}.{ext}" msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}" msgstr "Autorisation de droit à l'image de {participant}.{ext}"
#: participation/views.py:426 #: participation/views.py:431
#, python-brace-format #, python-brace-format
msgid "Parental authorization of {participant}.{ext}" msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}" msgstr "Autorisation parentale de {participant}.{ext}"
#: participation/views.py:434 #: participation/views.py:439
#, python-brace-format #, python-brace-format
msgid "Health sheet of {participant}.{ext}" msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}" msgstr "Fiche sanitaire de {participant}.{ext}"
#: participation/views.py:442 #: participation/views.py:447
#, python-brace-format #, python-brace-format
msgid "Vaccine sheet of {participant}.{ext}" msgid "Vaccine sheet of {participant}.{ext}"
msgstr "Carnet de vaccination de {participant}.{ext}" msgstr "Carnet de vaccination de {participant}.{ext}"
#: participation/views.py:516 #: participation/views.py:521
msgid "The team is not validated yet." msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée." msgstr "L'équipe n'est pas encore validée."
#: participation/views.py:530 #: participation/views.py:535
#, python-brace-format #, python-brace-format
msgid "Participation of team {trigram}" msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}" msgstr "Participation de l'équipe {trigram}"
#: participation/views.py:612 #: participation/views.py:617
#, python-brace-format #, python-brace-format
msgid "Payments of {tournament}" msgid "Payments of {tournament}"
msgstr "Paiements de {tournament}" msgstr "Paiements de {tournament}"
#: participation/views.py:708 #: participation/views.py:713
msgid "Notes published!" msgid "Notes published!"
msgstr "Notes publiées !" msgstr "Notes publiées !"
#: participation/views.py:710 #: participation/views.py:715
msgid "Notes hidden!" msgid "Notes hidden!"
msgstr "Notes dissimulées !" msgstr "Notes dissimulées !"
#: participation/views.py:741 #: participation/views.py:746
#, python-brace-format #, python-brace-format
msgid "Harmonize notes of {tournament} - Day {round}" msgid "Harmonize notes of {tournament} - Day {round}"
msgstr "Harmoniser les notes de {tournament} - Jour {round}" msgstr "Harmoniser les notes de {tournament} - Jour {round}"
#: participation/views.py:822 #: participation/views.py:827
msgid "You can't upload a solution after the deadline." msgid "You can't upload a solution after the deadline."
msgstr "Vous ne pouvez pas envoyer de solution après la date limite." msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
#: participation/views.py:939 #: participation/views.py:944
#, python-brace-format #, python-brace-format
msgid "Solutions of team {trigram}.zip" msgid "Solutions of team {trigram}.zip"
msgstr "Solutions de l'équipe {trigram}.zip" msgstr "Solutions de l'équipe {trigram}.zip"
#: participation/views.py:939 #: participation/views.py:944
#, python-brace-format #, python-brace-format
msgid "Syntheses of team {trigram}.zip" msgid "Syntheses of team {trigram}.zip"
msgstr "Notes de synthèse de l'équipe {trigram}.zip" msgstr "Notes de synthèse de l'équipe {trigram}.zip"
#: participation/views.py:956 participation/views.py:971 #: participation/views.py:961 participation/views.py:976
#, python-brace-format #, python-brace-format
msgid "Solutions of {tournament}.zip" msgid "Solutions of {tournament}.zip"
msgstr "Solutions de {tournament}.zip" msgstr "Solutions de {tournament}.zip"
#: participation/views.py:956 participation/views.py:971 #: participation/views.py:961 participation/views.py:976
#, python-brace-format #, python-brace-format
msgid "Syntheses of {tournament}.zip" msgid "Syntheses of {tournament}.zip"
msgstr "Notes de synthèse de {tournament}.zip" msgstr "Notes de synthèse de {tournament}.zip"
#: participation/views.py:980 #: participation/views.py:985
#, python-brace-format #, python-brace-format
msgid "Solutions for pool {pool} of tournament {tournament}.zip" msgid "Solutions for pool {pool} of tournament {tournament}.zip"
msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip" msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:981 #: participation/views.py:986
#, python-brace-format #, python-brace-format
msgid "Syntheses for pool {pool} of tournament {tournament}.zip" msgid "Syntheses for pool {pool} of tournament {tournament}.zip"
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip" msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:1023 #: participation/views.py:1028
#, python-brace-format #, python-brace-format
msgid "Jury of pool {pool} for {tournament} with teams {teams}" msgid "Jury of pool {pool} for {tournament} with teams {teams}"
msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}" msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}"
#: participation/views.py:1039 #: participation/views.py:1044
#, python-brace-format #, python-brace-format
msgid "The jury {name} is already in the pool!" msgid "The jury {name} is already in the pool!"
msgstr "{name} est déjà dans la poule !" msgstr "{name} est déjà dans la poule !"
#: participation/views.py:1059 #: participation/views.py:1064
msgid "New TFJM² jury account" msgid "New TFJM² jury account"
msgstr "Nouveau compte de juré⋅e pour le TFJM²" msgstr "Nouveau compte de juré⋅e pour le TFJM²"
#: participation/views.py:1080 #: participation/views.py:1085
#, python-brace-format #, python-brace-format
msgid "The jury {name} has been successfully added!" msgid "The jury {name} has been successfully added!"
msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !" msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !"
#: participation/views.py:1116 #: participation/views.py:1121
#, python-brace-format #, python-brace-format
msgid "The jury {name} has been successfully removed!" msgid "The jury {name} has been successfully removed!"
msgstr "{name} a été retiré⋅e avec succès du jury !" msgstr "{name} a été retiré⋅e avec succès du jury !"
#: participation/views.py:1142 #: participation/views.py:1147
#, python-brace-format #, python-brace-format
msgid "The jury {name} has been successfully promoted president!" msgid "The jury {name} has been successfully promoted president!"
msgstr "{name} a été nommé⋅e président⋅e du jury !" msgstr "{name} a été nommé⋅e président⋅e du jury !"
#: participation/views.py:1170 #: participation/views.py:1175
msgid "The following user is not registered as a jury:" msgid "The following user is not registered as a jury:"
msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :" msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :"
#: participation/views.py:1187 #: participation/views.py:1192
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:1820 #: participation/views.py:1825
#, 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:1825 #: participation/views.py:1830
#, 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:1976 #: participation/views.py:2007
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

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import csv import csv
import math
from io import StringIO from io import StringIO
import re import re
from typing import Iterable from typing import Iterable
@ -13,6 +14,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import pandas
from pypdf import PdfReader from pypdf import PdfReader
from registration.models import VolunteerRegistration from registration.models import VolunteerRegistration
@ -241,50 +243,50 @@ class AddJuryForm(forms.ModelForm):
class UploadNotesForm(forms.Form): class UploadNotesForm(forms.Form):
file = forms.FileField( file = forms.FileField(
label=_("CSV file:"), label=_("Spreadsheet file:"),
validators=[FileExtensionValidator(allowed_extensions=["csv"])], validators=[FileExtensionValidator(allowed_extensions=["csv", "ods"])],
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['file'].widget.attrs['accept'] = 'text/csv' self.fields['file'].widget.attrs['accept'] = 'text/csv,application/vnd.oasis.opendocument.spreadsheet'
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
if 'file' in cleaned_data: if 'file' in cleaned_data:
file = cleaned_data['file'] file = cleaned_data['file']
with file: if file.name.endswith('.csv'):
try: with file:
data: bytes = file.read()
try: try:
content = data.decode() data: bytes = file.read()
except UnicodeDecodeError: try:
# This is not UTF-8, grrrr content = data.decode()
content = data.decode('latin1') except UnicodeDecodeError:
for delimiter in [',', ';', '\t', '|']: # This is not UTF-8, grrrr
if content.split('\n')[0].count(delimiter) > 1: content = data.decode('latin1')
break
else:
self.add_error('file',
_("Unable to detect the CSV delimiter. Please use a comma-separated file."))
return cleaned_data
csvfile = csv.reader(StringIO(content), delimiter=delimiter) table = pandas.read_csv(StringIO(content), sep=None, header=None)
self.process(csvfile, cleaned_data) self.process(table, cleaned_data)
except UnicodeDecodeError: except UnicodeDecodeError:
self.add_error('file', _("This file contains non-UTF-8 and non-ISO-8859-1 content. " self.add_error('file', _("This file contains non-UTF-8 and non-ISO-8859-1 content. "
"Please send your sheet as a CSV file.")) "Please send your sheet as a CSV file."))
elif file.name.endswith('.ods'):
table = pandas.read_excel(file, header=None, engine='odf')
self.process(table, cleaned_data)
return cleaned_data return cleaned_data
def process(self, csvfile: Iterable[str], cleaned_data: dict): def process(self, df: pandas.DataFrame, cleaned_data: dict):
parsed_notes = {} parsed_notes = {}
valid_lengths = [2 + 6 * 3, 2 + 7 * 4, 2 + 6 * 5] # Per pool sizes valid_lengths = [2 + 6 * 3, 2 + 7 * 4, 2 + 6 * 5] # Per pool sizes
pool_size = 0 pool_size = 0
line_length = 0 line_length = 0
for line in csvfile: for line in df.values.tolist():
line = [s.strip() for s in line if s] # Remove NaN
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] == 'Problème':
pool_size = len(line) - 1 pool_size = len(line) - 1
if pool_size < 3 or pool_size > 5: if pool_size < 3 or pool_size > 5:
@ -297,12 +299,12 @@ class UploadNotesForm(forms.Form):
continue continue
name = line[0] name = line[0]
if name.lower() in ["rôle", "juré", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]: if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]:
continue continue
notes = line[2:line_length] notes = line[2:line_length]
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(int, notes)) notes = list(map(lambda x: int(float(x)), notes))
max_notes = pool_size * ([20, 20, 10, 10, 10, 10] + ([4] if pool_size == 4 else [])) max_notes = pool_size * ([20, 20, 10, 10, 10, 10] + ([4] if pool_size == 4 else []))
for n, max_n in zip(notes, max_notes): for n, max_n in zip(notes, max_notes):
@ -312,7 +314,7 @@ class UploadNotesForm(forms.Form):
+ str(n) + " > " + str(max_n)) + str(n) + " > " + str(max_n))
# Search by volunteer id # Search by volunteer id
jury = VolunteerRegistration.objects.filter(pk=line[1]) jury = VolunteerRegistration.objects.filter(pk=int(float(line[1])))
if jury.count() != 1: if jury.count() != 1:
raise ValidationError({'file': _("The following user was not found:") + " " + name}) raise ValidationError({'file': _("The following user was not found:") + " " + name})
jury = jury.get() jury = jury.get()

View File

@ -133,7 +133,7 @@
<div class="btn btn-group"> <div class="btn btn-group">
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#uploadNotesModal"> <button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#uploadNotesModal">
<i class="fas fa-upload"></i> <i class="fas fa-upload"></i>
{% trans "Upload notes from a CSV file" %} {% trans "Upload notes from a spreadsheet file" %}
</button> </button>
<a class="btn btn-sm btn-info" href="{% url 'participation:pool_notes_template' pk=pool.pk %}"> <a class="btn btn-sm btn-info" href="{% url 'participation:pool_notes_template' pk=pool.pk %}">
<i class="fas fa-download"></i> <i class="fas fa-download"></i>

View File

@ -9,7 +9,6 @@
<div class="alert alert-warning"> <div class="alert alert-warning">
{% url 'participation:pool_jury' pk=pool.jury as jury_url %} {% url 'participation:pool_jury' pk=pool.jury as jury_url %}
{% blocktrans trimmed with jury_url=jury_url %} {% blocktrans trimmed with jury_url=jury_url %}
Remember to export your spreadsheet as a CSV file before uploading it here.
Rows that are full of zeros are ignored. Rows that are full of zeros are ignored.
Unknown juries are not considered. Unknown juries are not considered.
{% endblocktrans %} {% endblocktrans %}

View File

@ -13,12 +13,10 @@ django-polymorphic~=3.1.0
django-tables2~=2.7.0 django-tables2~=2.7.0
djangorestframework~=3.14.0 djangorestframework~=3.14.0
django-rest-polymorphic~=0.1.10 django-rest-polymorphic~=0.1.10
google-api-python-client~=2.124.0
google-auth-httplib2~=0.2.0
google-auth-oauthlib~=1.2.0
gspread~=6.1.0 gspread~=6.1.0
gunicorn~=21.2.0 gunicorn~=21.2.0
odfpy~=1.4.1 odfpy~=1.4.1
pandas~=2.2.1
phonenumbers~=8.13.27 phonenumbers~=8.13.27
psycopg2-binary~=2.9.9 psycopg2-binary~=2.9.9
pypdf~=3.17.4 pypdf~=3.17.4