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 ""
"Project-Id-Version: TFJM\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"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\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
#: participation/admin.py:79 participation/admin.py:140
#: participation/admin.py:171 participation/models.py:648
#: participation/models.py:672 participation/models.py:853
#: participation/admin.py:171 participation/models.py:656
#: participation/models.py:680 participation/models.py:861
#: registration/models.py:674
#: registration/templates/registration/payment_form.html:53
msgid "tournament"
msgstr "tournoi"
#: draw/admin.py:92 draw/models.py:234 draw/models.py:433
#: participation/models.py:857
#: participation/models.py:865
msgid "round"
msgstr "tour"
@ -49,64 +49,64 @@ msgstr "Tirage au sort"
msgid "You are not an organizer."
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice."
#: draw/consumers.py:153
#: draw/consumers.py:162
msgid "The draw is already started."
msgstr "Le tirage a déjà commencé."
#: draw/consumers.py:159
#: draw/consumers.py:168
msgid "Invalid format"
msgstr "Format invalide"
#: draw/consumers.py:164
#: draw/consumers.py:173
#, python-brace-format
msgid "The sum must be equal to the number of teams: expected {len}, got {sum}"
msgstr ""
"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."
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!"
msgstr "Le tirage a commencé !"
#: draw/consumers.py:231
#: draw/consumers.py:240
#, python-brace-format
msgid "The draw for the tournament {tournament} will start."
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:893 draw/consumers.py:982 draw/consumers.py:1004
#: draw/consumers.py:1094 draw/templates/draw/tournament_content.html:5
#: draw/consumers.py:251 draw/consumers.py:277 draw/consumers.py:685
#: draw/consumers.py:902 draw/consumers.py:991 draw/consumers.py:1013
#: draw/consumers.py:1103 draw/templates/draw/tournament_content.html:5
msgid "The draw has not started yet."
msgstr "Le tirage au sort n'a pas encore commencé."
#: draw/consumers.py:255
#: draw/consumers.py:264
#, python-brace-format
msgid "The draw for the tournament {tournament} is aborted."
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:681 draw/consumers.py:898
#: draw/consumers.py:304 draw/consumers.py:325 draw/consumers.py:620
#: draw/consumers.py:690 draw/consumers.py:907
msgid "This is not the time for this."
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."
msgstr "Vous avez déjà lancé le dé."
#: draw/consumers.py:314
#: draw/consumers.py:323
msgid "It is not your turn."
msgstr "Ce n'est pas votre tour."
#: draw/consumers.py:401
#: draw/consumers.py:410
#, python-brace-format
msgid "Dices from teams {teams} are identical. Please relaunch your dices."
msgstr ""
"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."
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"
msgstr "tours"
#: draw/models.py:257 participation/models.py:871
#: draw/models.py:257 participation/models.py:879
msgid "letter"
msgstr "lettre"
@ -214,17 +214,17 @@ msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}"
#: draw/models.py:414 draw/models.py:441 participation/admin.py:136
#: participation/admin.py:155 participation/models.py:1340
#: participation/models.py:1349 participation/tables.py:84
#: participation/admin.py:155 participation/models.py:1350
#: participation/models.py:1359 participation/tables.py:84
msgid "pool"
msgstr "poule"
#: draw/models.py:415 participation/models.py:1341
#: draw/models.py:415 participation/models.py:1351
msgid "pools"
msgstr "poules"
#: draw/models.py:427 participation/models.py:843 participation/models.py:1510
#: participation/models.py:1540 participation/models.py:1582
#: draw/models.py:427 participation/models.py:851 participation/models.py:1520
#: participation/models.py:1550 participation/models.py:1592
msgid "participation"
msgstr "participation"
@ -248,8 +248,8 @@ msgid ""
msgstr ""
"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
#: participation/models.py:1547
#: draw/models.py:464 draw/models.py:487 participation/models.py:1373
#: participation/models.py:1557
#, python-brace-format
msgid "Problem #{problem}"
msgstr "Problème n°{problem}"
@ -332,7 +332,7 @@ msgid "Continue draw"
msgstr "Continuer le tirage"
#: 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
#: registration/models.py:157 registration/models.py:665
#: registration/tables.py:39
@ -378,8 +378,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
msgid "Close"
msgstr "Fermer"
#: draw/views.py:31 participation/views.py:156 participation/views.py:470
#: participation/views.py:501
#: draw/views.py:31 participation/views.py:161 participation/views.py:475
#: participation/views.py:506
msgid "You are not in a team."
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"
msgstr "valide"
#: participation/admin.py:87 participation/models.py:684
#: participation/admin.py:87 participation/models.py:692
msgid "selected for final"
msgstr "sélectionnée pour la finale"
#: 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"
msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:1377
#: participation/models.py:1594
#: participation/admin.py:128 participation/models.py:1387
#: participation/models.py:1604
msgid "opponent"
msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:1384
#: participation/models.py:1595
#: participation/admin.py:132 participation/models.py:1394
#: participation/models.py:1605
msgid "reporter"
msgstr "rapporteur⋅rice"
#: participation/admin.py:187 participation/models.py:1545
#: participation/admin.py:187 participation/models.py:1555
msgid "problem"
msgstr "numéro de problème"
#: participation/forms.py:30
#: participation/forms.py:32
msgid "This name is already used."
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."
msgstr "Le trigramme doit être composé de trois lettres majuscules."
#: participation/forms.py:40
#: participation/forms.py:42
msgid "This trigram is already used."
msgstr "Ce trigramme est déjà utilisé."
#: participation/forms.py:55
#: participation/forms.py:57
msgid "No team was found with this access code."
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."
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:166 registration/forms.py:188
#: registration/forms.py:237 registration/forms.py:270
msgid "The uploaded file size must be under 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:190 registration/forms.py:239
#: registration/forms.py:272
msgid "The uploaded file must be a PDF, PNG of JPEG file."
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²."
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:"
msgstr "Message à adresser à l'équipe :"
#: participation/forms.py:158
#: participation/forms.py:160
msgid "The uploaded file size must be under 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."
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."
msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages."
#: participation/forms.py:218
#: participation/forms.py:220
msgid "Add"
msgstr "Ajouter"
#: participation/forms.py:233
#: participation/forms.py:235
msgid "This user already exists, but is a participant."
msgstr "Cet⋅te utilisateur⋅rice existe déjà, mais en tant que participant⋅e."
#: participation/forms.py:244
msgid "CSV file:"
msgstr "Tableur au format CSV :"
#: participation/forms.py:246
msgid "Spreadsheet file:"
msgstr "Fichier tableur :"
#: participation/forms.py:268
#: participation/forms.py:272
msgid ""
"This file contains non-UTF-8 and non-ISO-8859-1 content. Please send your "
"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 "
"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?"
msgstr ""
"Impossible de déterminer la taille de la poule. Êtes-vous sûr⋅e que le "
"fichier est correct ?"
#: participation/forms.py:303
#: participation/forms.py:313
msgid "The following note is higher of the maximum expected value:"
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:"
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."
msgstr ""
"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."
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."
msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages."
@ -797,28 +797,28 @@ msgstr "finale"
msgid "Google Sheet ID"
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"
msgstr "tournois"
#: participation/models.py:678
#: participation/models.py:686
msgid "valid team"
msgstr "équipe valide"
#: participation/models.py:679
#: participation/models.py:687
msgid "The participation got the validation of the organizers."
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."
msgstr "L'équipe est sélectionnée pour la finale."
#: participation/models.py:692
#: participation/models.py:700
#, python-brace-format
msgid "Participation of the team {name} ({trigram})"
msgstr "Participation de l'équipe {name} ({trigram})"
#: participation/models.py:699
#: participation/models.py:707
#, python-brace-format
msgid ""
"<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 "
"qui n'ont pas encore payé sont : {participants}.</p>"
#: participation/models.py:707
#: participation/models.py:715
msgid "Missing payments"
msgstr "Paiements manquants"
#: participation/models.py:714
#: participation/models.py:722
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> "
@ -850,11 +850,11 @@ msgstr ""
"pouvez envoyer vos solutions sur <a href='{url}'>votre page de "
"participation</a>.</p>"
#: participation/models.py:723
#: participation/models.py:731
msgid "Solutions due"
msgstr "Rendu des solutions"
#: participation/models.py:729 registration/models.py:518
#: participation/models.py:737 registration/models.py:518
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>."
@ -864,11 +864,11 @@ msgstr ""
"{date:%d/%m/%Y %H:%M}. Vous pouvez y participer sur <a href='{url}'>ce lien</"
"a>.</p>"
#: participation/models.py:735 registration/models.py:525
#: participation/models.py:743 registration/models.py:525
msgid "Draw of solutions"
msgstr "Tirage au sort des solutions"
#: participation/models.py:746
#: participation/models.py:754
#, python-brace-format
msgid ""
"<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 "
"{problem}</a>.</p>"
#: participation/models.py:755 participation/models.py:798
#: participation/models.py:763 participation/models.py:806
#, python-brace-format
msgid ""
"<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 "
"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
msgid ""
"<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 "
"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"
msgstr "Premier tour"
#: participation/models.py:791
#: participation/models.py:799
#, python-brace-format
msgid ""
"<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 "
"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"
msgstr "Second tour"
#: participation/models.py:829
#: participation/models.py:837
#, python-brace-format
msgid ""
"<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 "
"sur la <a href='{url}'>page du tournoi</a>.</p>"
#: participation/models.py:834
#: participation/models.py:842
msgid "Tournament ended"
msgstr "Tournoi terminé"
#: participation/models.py:844 participation/models.py:877
#: participation/models.py:852 participation/models.py:885
msgid "participations"
msgstr "participations"
#: participation/models.py:859 participation/models.py:860
#: participation/models.py:867 participation/models.py:868
#, python-brace-format
msgid "Round {round}"
msgstr "Tour {round}"
#: participation/models.py:883
#: participation/models.py:891
msgid "juries"
msgstr "jurys"
#: participation/models.py:892
#: participation/models.py:900
msgid "president of the jury"
msgstr "président⋅e du jury"
#: participation/models.py:899
#: participation/models.py:907
msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton"
#: participation/models.py:900
#: participation/models.py:908
msgid "The link of the BBB visio for this pool."
msgstr "Le lien du salon BBB pour cette poule."
#: participation/models.py:905
#: participation/models.py:913
msgid "results available"
msgstr "résultats disponibles"
#: participation/models.py:906
#: participation/models.py:914
msgid ""
"Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given."
@ -970,37 +970,37 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées."
#: participation/models.py:931
#: participation/models.py:939
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:1323
#: participation/models.py:1331
#, 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:1334
#: participation/models.py:1344
#, python-brace-format
msgid "Pool of day {round} for tournament {tournament} with teams {teams}"
msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}"
#: participation/models.py:1354
#: participation/models.py:1364
msgid "position"
msgstr "position"
#: participation/models.py:1361
#: participation/models.py:1371
msgid "defended solution"
msgstr "solution défendue"
#: participation/models.py:1394
#: participation/models.py:1404
msgid "observer"
msgstr "observateur⋅rice"
#: participation/models.py:1399
#: participation/models.py:1409
msgid "penalties"
msgstr "pénalités"
#: participation/models.py:1401
#: participation/models.py:1411
msgid ""
"Number of penalties for the defender. The defender will loose a 0.5 "
"coefficient per penalty."
@ -1008,124 +1008,124 @@ 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:1477 participation/models.py:1480
#: participation/models.py:1483 participation/models.py:1486
#: participation/models.py:1487 participation/models.py:1490
#: participation/models.py:1493 participation/models.py:1496
#, 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:1491
#: participation/models.py:1501
#, python-brace-format
msgid "Passage of {defender} for problem {problem}"
msgstr "Passage de {defender} pour le problème {problem}"
#: participation/models.py:1495 participation/models.py:1504
#: participation/models.py:1589 participation/models.py:1631
#: participation/models.py:1505 participation/models.py:1514
#: participation/models.py:1599 participation/models.py:1641
msgid "passage"
msgstr "passage"
#: participation/models.py:1496
#: participation/models.py:1506
msgid "passages"
msgstr "passages"
#: participation/models.py:1515
#: participation/models.py:1525
msgid "difference"
msgstr "différence"
#: participation/models.py:1516
#: participation/models.py:1526
msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final"
#: participation/models.py:1523
#: participation/models.py:1533
msgid "tweak"
msgstr "harmonisation"
#: participation/models.py:1524
#: participation/models.py:1534
msgid "tweaks"
msgstr "harmonisations"
#: participation/models.py:1552
#: participation/models.py:1562
msgid "solution for the final tournament"
msgstr "solution pour la finale"
#: participation/models.py:1557 participation/models.py:1600
#: participation/models.py:1567 participation/models.py:1610
msgid "file"
msgstr "fichier"
#: participation/models.py:1567
#: participation/models.py:1577
#, 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:1569
#: participation/models.py:1579
msgid "for final"
msgstr "pour la finale"
#: participation/models.py:1572
#: participation/models.py:1582
msgid "solution"
msgstr "solution"
#: participation/models.py:1573
#: participation/models.py:1583
msgid "solutions"
msgstr "solutions"
#: participation/models.py:1606
#: participation/models.py:1616
#, 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:1614
#: participation/models.py:1624
msgid "synthesis"
msgstr "note de synthèse"
#: participation/models.py:1615
#: participation/models.py:1625
msgid "syntheses"
msgstr "notes de synthèse"
#: participation/models.py:1624
#: participation/models.py:1634
msgid "jury"
msgstr "jury"
#: participation/models.py:1636
#: participation/models.py:1646
msgid "defender writing note"
msgstr "note d'écrit défenseur⋅se"
#: participation/models.py:1642
#: participation/models.py:1652
msgid "defender oral note"
msgstr "note d'oral défenseur⋅se"
#: participation/models.py:1648
#: participation/models.py:1658
msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e"
#: participation/models.py:1654
#: participation/models.py:1664
msgid "opponent oral note"
msgstr "note d'oral opposant⋅e"
#: participation/models.py:1660
#: participation/models.py:1670
msgid "reporter writing note"
msgstr "note d'écrit rapporteur⋅rice"
#: participation/models.py:1666
#: participation/models.py:1676
msgid "reporter oral note"
msgstr "note d'oral du rapporteur⋅rice"
#: participation/models.py:1672
#: participation/models.py:1682
msgid "observer note"
msgstr "note de l'observation"
#: participation/models.py:1733
#: participation/models.py:1743
#, python-brace-format
msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}"
#: participation/models.py:1736
#: participation/models.py:1746
msgid "note"
msgstr "note"
#: participation/models.py:1737
#: participation/models.py:1747
msgid "notes"
msgstr "notes"
@ -1269,7 +1269,7 @@ msgstr "Envoyer une solution"
#: participation/templates/participation/pool_detail.html:162
#: participation/templates/participation/team_detail.html:210
#: 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_synthesis.html:18
#: registration/templates/registration/upload_health_sheet.html:17
@ -1453,8 +1453,8 @@ msgid "Ranking"
msgstr "Classement"
#: participation/templates/participation/pool_detail.html:136
msgid "Upload notes from a CSV file"
msgstr "Soumettre les notes à partir d'un fichier CSV"
msgid "Upload notes from a spreadsheet file"
msgstr "Soumettre les notes à partir d'un tableur"
#: participation/templates/participation/pool_detail.html:140
msgid "Download notation spreadsheet"
@ -1658,7 +1658,7 @@ msgid "Invalidate"
msgstr "Invalider"
#: participation/templates/participation/team_detail.html:209
#: participation/views.py:327
#: participation/views.py:332
msgid "Upload motivation letter"
msgstr "Envoyer la lettre de motivation"
@ -1667,7 +1667,7 @@ msgid "Update team"
msgstr "Modifier l'équipe"
#: participation/templates/participation/team_detail.html:219
#: participation/views.py:464
#: participation/views.py:469
msgid "Leave team"
msgstr "Quitter l'équipe"
@ -1820,14 +1820,12 @@ msgstr "Retour aux détails de l'utilisateur⋅rice"
#: participation/templates/participation/upload_notes.html:11
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."
msgstr ""
"N'oubliez pas d'exporter votre tableur en tant que fichier CSV avant de le "
"téléverser ici. Les lignes remplies de zéros sont ignorées. Les juré⋅es "
"inconnu⋅es ne sont pas pris⋅es en compte."
"Les lignes remplies de zéros sont ignorées. Les juré⋅es 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"
msgstr "Télécharger la fiche de notation vierge"
@ -1839,44 +1837,44 @@ msgstr "Modèles :"
msgid "Warning: non-free format"
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
msgid "Create team"
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."
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."
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
msgid "Join team"
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."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
#: participation/views.py:183
#: participation/views.py:188
#, python-brace-format
msgid "Detail of team {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."
msgstr ""
"Vous ne participez pas, vous ne pouvez pas demander la validation de "
"l'équipe."
#: participation/views.py:215
#: participation/views.py:220
msgid "The validation of the team is already done or pending."
msgstr "La validation de l'équipe est déjà faite ou en cours."
#: participation/views.py:218
#: participation/views.py:223
msgid ""
"The team can't be validated: missing email address confirmations, "
"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 "
"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."
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."
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."
msgstr "Vous devez spécifier si vous validez l'inscription ou non."
#: participation/views.py:305
#: participation/views.py:310
#, python-brace-format
msgid "Update team {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
msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}"
#: participation/views.py:398
#: participation/views.py:403
#, python-brace-format
msgid "Authorizations of team {trigram}.zip"
msgstr "Autorisations de l'équipe {trigram}.zip"
#: participation/views.py:402
#: participation/views.py:407
#, python-brace-format
msgid "Authorizations of {tournament}.zip"
msgstr "Autorisations du tournoi {tournament}.zip"
#: participation/views.py:418
#: participation/views.py:423
#, python-brace-format
msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
#: participation/views.py:426
#: participation/views.py:431
#, python-brace-format
msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}"
#: participation/views.py:434
#: participation/views.py:439
#, python-brace-format
msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}"
#: participation/views.py:442
#: participation/views.py:447
#, python-brace-format
msgid "Vaccine sheet of {participant}.{ext}"
msgstr "Carnet de vaccination de {participant}.{ext}"
#: participation/views.py:516
#: participation/views.py:521
msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée."
#: participation/views.py:530
#: participation/views.py:535
#, python-brace-format
msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}"
#: participation/views.py:612
#: participation/views.py:617
#, python-brace-format
msgid "Payments of {tournament}"
msgstr "Paiements de {tournament}"
#: participation/views.py:708
#: participation/views.py:713
msgid "Notes published!"
msgstr "Notes publiées !"
#: participation/views.py:710
#: participation/views.py:715
msgid "Notes hidden!"
msgstr "Notes dissimulées !"
#: participation/views.py:741
#: participation/views.py:746
#, python-brace-format
msgid "Harmonize notes of {tournament} - Day {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."
msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
#: participation/views.py:939
#: participation/views.py:944
#, python-brace-format
msgid "Solutions of team {trigram}.zip"
msgstr "Solutions de l'équipe {trigram}.zip"
#: participation/views.py:939
#: participation/views.py:944
#, python-brace-format
msgid "Syntheses of team {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
msgid "Solutions of {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
msgid "Syntheses of {tournament}.zip"
msgstr "Notes de synthèse de {tournament}.zip"
#: participation/views.py:980
#: participation/views.py:985
#, python-brace-format
msgid "Solutions for pool {pool} of tournament {tournament}.zip"
msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:981
#: participation/views.py:986
#, python-brace-format
msgid "Syntheses for pool {pool} of tournament {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
msgid "Jury of pool {pool} for {tournament} with teams {teams}"
msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}"
#: participation/views.py:1039
#: participation/views.py:1044
#, python-brace-format
msgid "The jury {name} is already in the pool!"
msgstr "{name} est déjà dans la poule !"
#: participation/views.py:1059
#: participation/views.py:1064
msgid "New TFJM² jury account"
msgstr "Nouveau compte de juré⋅e pour le TFJM²"
#: participation/views.py:1080
#: participation/views.py:1085
#, python-brace-format
msgid "The jury {name} has been successfully added!"
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
msgid "The jury {name} has been successfully removed!"
msgstr "{name} a été retiré⋅e avec succès du jury !"
#: participation/views.py:1142
#: participation/views.py:1147
#, python-brace-format
msgid "The jury {name} has been successfully promoted president!"
msgstr "{name} a été nommé⋅e président⋅e du jury !"
#: participation/views.py:1170
#: participation/views.py:1175
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 :"
#: participation/views.py:1187
#: participation/views.py:1192
msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées."
#: participation/views.py:1820
#: participation/views.py:1825
#, 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:1825
#: participation/views.py:1830
#, python-brace-format
msgid "Notation sheets of {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."
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
import csv
import math
from io import StringIO
import re
from typing import Iterable
@ -13,6 +14,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.utils.translation import gettext_lazy as _
import pandas
from pypdf import PdfReader
from registration.models import VolunteerRegistration
@ -241,50 +243,50 @@ class AddJuryForm(forms.ModelForm):
class UploadNotesForm(forms.Form):
file = forms.FileField(
label=_("CSV file:"),
validators=[FileExtensionValidator(allowed_extensions=["csv"])],
label=_("Spreadsheet file:"),
validators=[FileExtensionValidator(allowed_extensions=["csv", "ods"])],
)
def __init__(self, *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):
cleaned_data = super().clean()
if 'file' in cleaned_data:
file = cleaned_data['file']
with file:
try:
data: bytes = file.read()
if file.name.endswith('.csv'):
with file:
try:
content = data.decode()
except UnicodeDecodeError:
# This is not UTF-8, grrrr
content = data.decode('latin1')
for delimiter in [',', ';', '\t', '|']:
if content.split('\n')[0].count(delimiter) > 1:
break
else:
self.add_error('file',
_("Unable to detect the CSV delimiter. Please use a comma-separated file."))
return cleaned_data
data: bytes = file.read()
try:
content = data.decode()
except UnicodeDecodeError:
# This is not UTF-8, grrrr
content = data.decode('latin1')
csvfile = csv.reader(StringIO(content), delimiter=delimiter)
self.process(csvfile, cleaned_data)
except UnicodeDecodeError:
self.add_error('file', _("This file contains non-UTF-8 and non-ISO-8859-1 content. "
"Please send your sheet as a CSV file."))
table = pandas.read_csv(StringIO(content), sep=None, header=None)
self.process(table, cleaned_data)
except UnicodeDecodeError:
self.add_error('file', _("This file contains non-UTF-8 and non-ISO-8859-1 content. "
"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
def process(self, csvfile: Iterable[str], cleaned_data: dict):
def process(self, df: pandas.DataFrame, cleaned_data: dict):
parsed_notes = {}
valid_lengths = [2 + 6 * 3, 2 + 7 * 4, 2 + 6 * 5] # Per pool sizes
pool_size = 0
line_length = 0
for line in csvfile:
line = [s.strip() for s in line if s]
for line in df.values.tolist():
# 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':
pool_size = len(line) - 1
if pool_size < 3 or pool_size > 5:
@ -297,12 +299,12 @@ class UploadNotesForm(forms.Form):
continue
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
notes = line[2:line_length]
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
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 []))
for n, max_n in zip(notes, max_notes):
@ -312,7 +314,7 @@ class UploadNotesForm(forms.Form):
+ str(n) + " > " + str(max_n))
# Search by volunteer id
jury = VolunteerRegistration.objects.filter(pk=line[1])
jury = VolunteerRegistration.objects.filter(pk=int(float(line[1])))
if jury.count() != 1:
raise ValidationError({'file': _("The following user was not found:") + " " + name})
jury = jury.get()

View File

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

View File

@ -9,7 +9,6 @@
<div class="alert alert-warning">
{% url 'participation:pool_jury' pk=pool.jury as 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.
Unknown juries are not considered.
{% endblocktrans %}

View File

@ -13,12 +13,10 @@ django-polymorphic~=3.1.0
django-tables2~=2.7.0
djangorestframework~=3.14.0
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
gunicorn~=21.2.0
odfpy~=1.4.1
pandas~=2.2.1
phonenumbers~=8.13.27
psycopg2-binary~=2.9.9
pypdf~=3.17.4