diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index da3f4e5..feb21d7 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: TFJM\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-05 11:45+0200\n" +"POT-Creation-Date: 2024-07-05 16:44+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emmy D'Anello \n" "Language-Team: LANGUAGE \n" @@ -78,8 +78,8 @@ msgstr "Type de permission nécessaire pour écrire un message dans un canal." #: chat/models.py:62 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 #: draw/models.py:27 participation/admin.py:79 participation/admin.py:144 -#: participation/admin.py:176 participation/models.py:727 -#: participation/models.py:751 participation/models.py:1060 +#: participation/admin.py:176 participation/models.py:781 +#: participation/models.py:805 participation/models.py:1114 #: registration/models.py:763 #: registration/templates/registration/payment_form.html:53 msgid "tournament" @@ -95,7 +95,7 @@ msgstr "" #: chat/models.py:73 draw/models.py:446 draw/models.py:473 #: participation/admin.py:140 participation/admin.py:160 -#: participation/models.py:1563 participation/models.py:1572 +#: participation/models.py:1642 participation/models.py:1651 #: participation/tables.py:84 msgid "pool" msgstr "poule" @@ -109,7 +109,7 @@ msgstr "" #: chat/models.py:84 draw/templates/draw/tournament_content.html:277 #: participation/admin.py:172 participation/models.py:261 -#: participation/models.py:742 +#: participation/models.py:796 #: participation/templates/participation/tournament_harmonize.html:15 #: registration/models.py:158 registration/models.py:754 #: registration/tables.py:39 @@ -265,11 +265,11 @@ msgid "teams" msgstr "équipes" #: draw/admin.py:92 draw/models.py:245 draw/models.py:465 -#: participation/models.py:1064 +#: participation/models.py:1118 msgid "round" msgstr "tour" -#: draw/apps.py:10 draw/consumers.py:1037 tfjm/templates/navbar.html:68 +#: draw/apps.py:10 draw/consumers.py:1038 tfjm/templates/navbar.html:68 msgid "Draw" msgstr "Tirage au sort" @@ -309,9 +309,9 @@ msgstr "Le tirage au sort du tournoi {tournament} a commencé !" msgid "The draw for the tournament {tournament} will start." msgstr "Le tirage au sort du tournoi {tournament} va commencer." -#: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:691 -#: draw/consumers.py:909 draw/consumers.py:999 draw/consumers.py:1021 -#: draw/consumers.py:1112 draw/templates/draw/tournament_content.html:5 +#: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:692 +#: draw/consumers.py:910 draw/consumers.py:1000 draw/consumers.py:1022 +#: draw/consumers.py:1113 draw/templates/draw/tournament_content.html:5 msgid "The draw has not started yet." msgstr "Le tirage au sort n'a pas encore commencé." @@ -320,8 +320,8 @@ msgstr "Le tirage au sort n'a pas encore commencé." msgid "The draw for the tournament {tournament} is aborted." msgstr "Le tirage au sort du tournoi {tournament} est annulé." -#: draw/consumers.py:309 draw/consumers.py:330 draw/consumers.py:625 -#: draw/consumers.py:696 draw/consumers.py:914 +#: draw/consumers.py:309 draw/consumers.py:330 draw/consumers.py:626 +#: draw/consumers.py:697 draw/consumers.py:915 msgid "This is not the time for this." msgstr "Ce n'est pas le moment pour cela." @@ -362,21 +362,21 @@ msgstr "" "de dés, par ordre croissant. Pour le deuxième tour, les ordres de passage " "sont déterminés à partir des ordres de passage du premier tour." -#: draw/consumers.py:614 draw/consumers.py:754 draw/consumers.py:831 -#: draw/consumers.py:865 draw/consumers.py:990 draw/consumers.py:1088 +#: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:832 +#: draw/consumers.py:866 draw/consumers.py:991 draw/consumers.py:1089 msgid "Your turn!" msgstr "À votre tour !" -#: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:991 -#: draw/consumers.py:1089 +#: draw/consumers.py:616 draw/consumers.py:756 draw/consumers.py:992 +#: draw/consumers.py:1090 msgid "It's your turn to draw a problem!" msgstr "C'est à vous de tirer un problème !" -#: draw/consumers.py:635 draw/consumers.py:706 draw/consumers.py:924 +#: draw/consumers.py:636 draw/consumers.py:707 draw/consumers.py:925 msgid "This is not your turn." msgstr "Ce n'est pas votre tour." -#: draw/consumers.py:713 +#: draw/consumers.py:714 #, python-brace-format msgid "" "The team {trigram} accepted the problem {problem}{trigram} a accepté le problème {problem} : {problem_name}. " -#: draw/consumers.py:717 +#: draw/consumers.py:718 msgid "One team more can accept this problem." msgstr "Une équipe de plus peut accepter ce problème." -#: draw/consumers.py:719 +#: draw/consumers.py:720 msgid "No team can accept this problem anymore." msgstr "Aucune autre équipe ne peut accepter ce problème." -#: draw/consumers.py:813 +#: draw/consumers.py:814 #, python-brace-format msgid "The draw of the pool {pool} is ended. The summary is below." msgstr "Le tirage de la poule {pool} est terminé. Le résumé est ci-dessous." -#: draw/consumers.py:832 draw/consumers.py:866 +#: draw/consumers.py:833 draw/consumers.py:867 msgid "It's your turn to launch the dice!" msgstr "C'est à vous de lancer le dé !" -#: draw/consumers.py:852 +#: draw/consumers.py:853 #, python-brace-format msgid "The draw of the round {round} is ended." msgstr "Le tirage au sort du tour {round} est annulé." -#: draw/consumers.py:895 +#: draw/consumers.py:896 msgid "The draw of the first round is ended." msgstr "Le tirage au sort du premier tour est terminé." -#: draw/consumers.py:938 +#: draw/consumers.py:939 #, python-brace-format msgid "" "The team {trigram} refused the problem {problem}{trigram} a refusé le problème {problem} : {problem_name}." -#: draw/consumers.py:942 +#: draw/consumers.py:943 #, python-brace-format msgid "It remains {remaining} refusals without penalty." msgstr "Il reste {remaining} refus sans pénalité." -#: draw/consumers.py:945 +#: draw/consumers.py:946 msgid "This problem was already refused by this team." msgstr "Ce problème a déjà été refusé par cette équipe." -#: draw/consumers.py:947 +#: draw/consumers.py:948 msgid "It adds a 25% penalty on the coefficient of the oral defense." msgstr "" "Cela ajoute une pénalité de 25 % sur le coefficient de l'oral de la " "défense." -#: draw/consumers.py:1024 +#: draw/consumers.py:1025 msgid "This is only available for the final tournament." msgstr "Cela n'est possible que pour la finale." -#: draw/consumers.py:1028 +#: draw/consumers.py:1029 msgid "" "The draw of the round 2 is starting. The passage order is determined from " "the ranking of the first round, in order to mix the teams between the two " @@ -449,7 +449,7 @@ msgstr "" "partir du classement du premier tour, afin de mélanger les équipes entre les " "deux jours." -#: draw/consumers.py:1038 +#: draw/consumers.py:1039 msgid "The draw of the second round is starting!" msgstr "Le tirage au sort du deuxième tour commence !" @@ -626,7 +626,7 @@ msgid "The current pool where teams select their problems." msgstr "La poule en cours, où les équipes choisissent leurs problèmes" #: draw/models.py:239 -#, python-brace-format +#, python-brace-format msgid "The number of the round must be between 1 and {nb}." msgstr "Le numéro du tour doit être entre 1 et {nb}." @@ -634,7 +634,7 @@ msgstr "Le numéro du tour doit être entre 1 et {nb}." msgid "rounds" msgstr "tours" -#: draw/models.py:268 participation/models.py:1072 +#: draw/models.py:268 participation/models.py:1126 msgid "letter" msgstr "lettre" @@ -672,12 +672,12 @@ msgstr "L'instance complète de la poule." msgid "Pool {letter}{number}" msgstr "Poule {letter}{number}" -#: draw/models.py:447 participation/models.py:1564 +#: draw/models.py:447 participation/models.py:1643 msgid "pools" msgstr "poules" -#: draw/models.py:459 participation/models.py:1050 participation/models.py:1754 -#: participation/models.py:1784 participation/models.py:1826 +#: draw/models.py:459 participation/models.py:1104 participation/models.py:1862 +#: participation/models.py:1892 participation/models.py:1934 msgid "participation" msgstr "participation" @@ -701,8 +701,9 @@ msgid "" msgstr "" "L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1." -#: draw/models.py:496 draw/models.py:519 participation/models.py:1586 -#: participation/models.py:1791 +#: draw/models.py:496 draw/models.py:519 participation/models.py:1234 +#: participation/models.py:1665 participation/models.py:1899 +#: participation/views.py:1487 participation/views.py:1752 #, python-brace-format msgid "Problem #{problem}" msgstr "Problème n°{problem}" @@ -913,31 +914,31 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" msgid "valid" msgstr "valide" -#: participation/admin.py:87 participation/models.py:763 +#: participation/admin.py:87 participation/models.py:817 msgid "selected for final" msgstr "sélectionnée pour la finale" #: participation/admin.py:124 participation/admin.py:188 -#: participation/models.py:1593 participation/tables.py:114 +#: participation/models.py:1672 participation/tables.py:114 msgid "defender" msgstr "défenseur⋅se" -#: participation/admin.py:128 participation/models.py:1600 -#: participation/models.py:1838 +#: participation/admin.py:128 participation/models.py:1679 +#: participation/models.py:1946 msgid "opponent" msgstr "opposant⋅e" -#: participation/admin.py:132 participation/models.py:1607 -#: participation/models.py:1839 +#: participation/admin.py:132 participation/models.py:1686 +#: participation/models.py:1947 msgid "reviewer" msgstr "rapporteur⋅rice" -#: participation/admin.py:136 participation/models.py:1614 -#: participation/models.py:1840 +#: participation/admin.py:136 participation/models.py:1693 +#: participation/models.py:1948 msgid "observer" msgstr "observateur⋅rice" -#: participation/admin.py:192 participation/models.py:1789 +#: participation/admin.py:192 participation/models.py:1897 msgid "problem" msgstr "numéro de problème" @@ -961,7 +962,7 @@ msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." msgid "The team is already validated or the validation is pending." msgstr "La validation de l'équipe est déjà faite ou en cours." -#: participation/forms.py:94 participation/forms.py:366 +#: participation/forms.py:94 participation/forms.py:367 #: registration/forms.py:126 registration/forms.py:148 #: registration/forms.py:170 registration/forms.py:192 #: registration/forms.py:214 registration/forms.py:236 @@ -989,7 +990,7 @@ msgstr "Message à adresser à l'équipe :" msgid "The uploaded file size must be under 5 Mo." msgstr "Le fichier envoyé doit peser moins de 5 Mo." -#: participation/forms.py:178 participation/forms.py:368 +#: participation/forms.py:178 participation/forms.py:369 msgid "The uploaded file must be a PDF file." msgstr "Le fichier envoyé doit être au format PDF." @@ -1017,24 +1018,24 @@ msgstr "" "Ce fichier contient des éléments non-UTF-8 et non-ISO-8859-1. Merci " "d'envoyer votre tableur au format CSV." -#: participation/forms.py:327 +#: participation/forms.py:328 msgid "The following note is higher of the maximum expected value:" msgstr "La note suivante est supérieure au maximum attendu :" -#: participation/forms.py:333 +#: participation/forms.py:334 msgid "The following user was not found:" msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :" -#: participation/forms.py:349 +#: participation/forms.py:350 msgid "The defender, the opponent and the reviewer must be different." msgstr "" "Les équipes défenseuse, opposante et rapportrice doivent être différent⋅es." -#: participation/forms.py:353 +#: participation/forms.py:354 msgid "This defender did not work on this problem." msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème." -#: participation/forms.py:372 +#: participation/forms.py:373 msgid "The PDF file must not have more than 2 pages." msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages." @@ -1287,36 +1288,100 @@ msgstr "finale" msgid "Google Sheet ID" msgstr "ID de la feuille Google Sheets" -#: participation/models.py:728 registration/admin.py:125 +#: participation/models.py:473 participation/models.py:474 +#: participation/models.py:476 participation/models.py:714 +msgid "Final ranking" +msgstr "Classement final" + +#: participation/models.py:481 participation/models.py:551 +#: participation/models.py:1309 participation/views.py:1726 +msgid "Team" +msgstr "Équipe" + +#: participation/models.py:481 +msgid "Scores day 1" +msgstr "Scores jour 1" + +#: participation/models.py:481 +msgid "Tweaks day 1" +msgstr "Ajustements 1" + +#: participation/models.py:481 +msgid "Scores day 2" +msgstr "Scores jour 2" + +#: participation/models.py:481 +msgid "Tweaks day 2" +msgstr "Ajustements 2" + +#: participation/models.py:482 +msgid "Total D1 + D2" +msgstr "Total J1 + J2" + +#: participation/models.py:482 +msgid "Scores day 3" +msgstr "Scores jour 3" + +#: participation/models.py:482 +msgid "Tweaks day 3" +msgstr "Ajustements 3" + +#: participation/models.py:484 participation/models.py:1309 +#: participation/views.py:1733 +msgid "Total" +msgstr "Total" + +#: participation/models.py:484 participation/models.py:551 +#: participation/models.py:1309 +#: participation/templates/participation/tournament_harmonize.html:14 +#: participation/views.py:1736 +msgid "Rank" +msgstr "Rang" + +#: participation/models.py:551 participation/models.py:716 +msgid "Score" +msgstr "Score" + +#: participation/models.py:551 +msgid "Mention" +msgstr "Mention" + +#: participation/models.py:696 participation/models.py:1572 +msgid "Don't update the table structure for a better automated integration." +msgstr "" +"Ne pas mettre à jour la structure de la table pour une meilleure intégration " +"automatisée." + +#: participation/models.py:782 registration/admin.py:125 msgid "tournaments" msgstr "tournois" -#: participation/models.py:757 +#: participation/models.py:811 msgid "valid team" msgstr "équipe valide" -#: participation/models.py:758 +#: participation/models.py:812 msgid "The participation got the validation of the organizers." msgstr "La participation a été validée par les organisateur⋅rices." -#: participation/models.py:764 +#: participation/models.py:818 msgid "The team is selected for the final tournament." msgstr "L'équipe est sélectionnée pour la finale." -#: participation/models.py:768 +#: participation/models.py:822 msgid "mention" msgstr "mention" -#: participation/models.py:775 +#: participation/models.py:829 msgid "mention (final)" msgstr "Mention (pour la finale) :" -#: participation/models.py:785 +#: participation/models.py:839 #, python-brace-format msgid "Participation of the team {name} ({trigram})" msgstr "Participation de l'équipe {name} ({trigram})" -#: participation/models.py:792 +#: participation/models.py:846 #, python-brace-format msgid "" "

The team {trigram} has {nb_missing_payments} missing payments. Each " @@ -1329,11 +1394,11 @@ msgstr "" "notification de bourse) pour participer au tournoi.

Les participant⋅es " "qui n'ont pas encore payé sont : {participants}.

" -#: participation/models.py:800 +#: participation/models.py:854 msgid "Missing payments" msgstr "Paiements manquants" -#: participation/models.py:817 +#: participation/models.py:871 msgid "" "

The solutions for the tournament of {tournament} are due on the {date:%Y-" "%m-%d %H:%M}.

You have currently sent {nb_solutions} " @@ -1348,11 +1413,11 @@ msgstr "" "pouvez envoyer vos solutions sur votre page de " "participation.

" -#: participation/models.py:827 participation/models.py:841 +#: participation/models.py:881 participation/models.py:895 msgid "Solutions due" msgstr "Rendu des solutions" -#: participation/models.py:833 +#: participation/models.py:887 msgid "" "

The solutions for the tournament of {tournament} are due on the {date:%Y-" "%m-%d %H:%M}.

Remember that you can only fix minor changes to your " @@ -1365,7 +1430,7 @@ msgstr "" "parties.

Vous pouvez envoyer vos solutions sur votre " "page de participation.

" -#: participation/models.py:847 registration/models.py:607 +#: participation/models.py:901 registration/models.py:607 msgid "" "

The draw of the solutions for the tournament {tournament} is planned on " "the {date:%Y-%m-%d %H:%M}. You can join it on this link." @@ -1375,11 +1440,11 @@ msgstr "" "{date:%d/%m/%Y %H:%M}. Vous pouvez y participer sur ce lien.

" -#: participation/models.py:853 registration/models.py:614 +#: participation/models.py:907 registration/models.py:614 msgid "Draw of solutions" msgstr "Tirage au sort des solutions" -#: participation/models.py:865 +#: participation/models.py:919 #, python-brace-format msgid "" "

The solutions draw is ended. You can check the result on votre solution du problème " "{problem}.

" -#: participation/models.py:874 participation/models.py:932 -#: participation/models.py:991 +#: participation/models.py:928 participation/models.py:986 +#: participation/models.py:1045 #, python-brace-format msgid "" "

You will oppose the solution of the team {opponent} on the problème {problem}. Vous pouvez envoyer votre note " "de synthèse sur cette page.

" -#: participation/models.py:883 participation/models.py:941 -#: participation/models.py:1000 +#: participation/models.py:937 participation/models.py:995 +#: participation/models.py:1054 #, python-brace-format msgid "" "

You will report the solution of the team {reviewer} on the problème {problem}. Vous pouvez envoyer votre note " "de synthèse sur cette page.

" -#: participation/models.py:893 participation/models.py:951 -#: participation/models.py:1010 +#: participation/models.py:947 participation/models.py:1005 +#: participation/models.py:1064 #, python-brace-format msgid "" "

You will observe the solution of the team {observer} on the problème {problem}. Vous pouvez envoyer votre note " "de synthèse sur cette page.

" -#: participation/models.py:913 registration/models.py:629 +#: participation/models.py:967 registration/models.py:629 msgid "First round" msgstr "Premier tour" -#: participation/models.py:925 +#: participation/models.py:979 #, python-brace-format msgid "" "

For the second round, you will defend your " @@ -1440,12 +1505,12 @@ msgstr "" "

Pour le second tour, vous défendrez votre " "solution du problème {problem}.

" -#: participation/models.py:971 participation/models.py:1030 +#: participation/models.py:1025 participation/models.py:1084 #: registration/models.py:640 msgid "Second round" msgstr "Second tour" -#: participation/models.py:984 +#: participation/models.py:1038 #, python-brace-format msgid "" "

For the third round, you will defend your " @@ -1454,7 +1519,7 @@ msgstr "" "

Pour le troisième tour, vous défendrez votre " "solution du problème {problem}.

" -#: participation/models.py:1036 +#: participation/models.py:1090 #, python-brace-format msgid "" "

The tournament {tournament} is ended. You can check the results on the Le tournoi {tournament} est terminé. Vous pouvez consulter les résultats " "sur la page du tournoi.

" -#: participation/models.py:1041 +#: participation/models.py:1095 msgid "Tournament ended" msgstr "Tournoi terminé" -#: participation/models.py:1051 participation/models.py:1094 +#: participation/models.py:1105 participation/models.py:1148 msgid "participations" msgstr "participations" -#: participation/models.py:1066 participation/models.py:1067 -#: participation/models.py:1068 +#: participation/models.py:1120 participation/models.py:1121 +#: participation/models.py:1122 #, python-brace-format msgid "Round {round}" msgstr "Tour {round}" -#: participation/models.py:1082 +#: participation/models.py:1136 msgid "room" msgstr "salle" -#: participation/models.py:1084 +#: participation/models.py:1138 msgid "Room 1" msgstr "Salle 1" -#: participation/models.py:1085 +#: participation/models.py:1139 msgid "Room 2" msgstr "Salle 2" -#: participation/models.py:1088 +#: participation/models.py:1142 msgid "For 5-teams pools only" msgstr "Pour les poules de 5 équipe uniquement" -#: participation/models.py:1100 +#: participation/models.py:1154 msgid "juries" msgstr "jurys" -#: participation/models.py:1109 +#: participation/models.py:1163 msgid "president of the jury" msgstr "président⋅e du jury" -#: participation/models.py:1116 +#: participation/models.py:1170 msgid "BigBlueButton URL" msgstr "Lien BigBlueButton" -#: participation/models.py:1117 +#: participation/models.py:1171 msgid "The link of the BBB visio for this pool." msgstr "Le lien du salon BBB pour cette poule." -#: participation/models.py:1122 +#: participation/models.py:1176 msgid "results available" msgstr "résultats disponibles" -#: participation/models.py:1123 +#: participation/models.py:1177 msgid "" "Check this case when results become accessible to teams. They stay " "accessible to you. Only averages are given." @@ -1522,33 +1587,61 @@ msgstr "" "Ils restent toujours accessibles pour vous. Seules les moyennes sont " "communiquées." -#: participation/models.py:1155 +#: participation/models.py:1209 msgid "The president of the jury must be part of the jury." msgstr "Læ président⋅e du jury doit faire partie du jury." -#: participation/models.py:1544 +#: participation/models.py:1235 participation/models.py:1309 +#: participation/views.py:1481 participation/views.py:1730 +msgid "Problem" +msgstr "Problème" + +#: participation/models.py:1245 participation/views.py:1530 +#: participation/views.py:1531 +msgid "Juree" +msgstr "Juré⋅e" + +#: participation/models.py:1268 participation/models.py:1588 +#: participation/models.py:1610 participation/views.py:1600 +msgid "Average" +msgstr "Moyenne" + +#: participation/models.py:1274 participation/views.py:1619 +msgid "Coefficient" +msgstr "Coefficien" + +#: participation/models.py:1275 participation/views.py:1662 +msgid "Subtotal" +msgstr "Sous-total" + +#: participation/models.py:1535 +#, python-brace-format +msgid "Input must be a valid integer between {min_note} and {max_note}." +msgstr "L'entrée doit être un entier valide entre {min_note} et {max_note}." + +#: participation/models.py:1623 #, python-brace-format msgid "The jury {jury} is not part of the jury for this pool." msgstr "{jury} ne fait pas partie du jury pour cette poule." -#: participation/models.py:1557 +#: participation/models.py:1636 #, python-brace-format msgid "Pool {code} for tournament {tournament} with teams {teams}" msgstr "Poule {code} du tournoi {tournament} avec les équipes {teams}" -#: participation/models.py:1577 +#: participation/models.py:1656 msgid "position" msgstr "position" -#: participation/models.py:1584 +#: participation/models.py:1663 msgid "defended solution" msgstr "solution défendue" -#: participation/models.py:1622 +#: participation/models.py:1701 msgid "penalties" msgstr "pénalités" -#: participation/models.py:1624 +#: participation/models.py:1703 msgid "" "Number of penalties for the defender. The defender will loose a 0.5 " "coefficient per penalty." @@ -1556,128 +1649,128 @@ msgstr "" "Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 " "sur sa présentation orale par pénalité." -#: participation/models.py:1721 participation/models.py:1724 -#: participation/models.py:1727 participation/models.py:1730 +#: participation/models.py:1829 participation/models.py:1832 +#: participation/models.py:1835 participation/models.py:1838 #, python-brace-format msgid "Team {trigram} is not registered in the pool." msgstr "L'équipe {trigram} n'est pas inscrite dans la poule." -#: participation/models.py:1735 +#: participation/models.py:1843 #, python-brace-format msgid "Passage of {defender} for problem {problem}" msgstr "Passage de {defender} pour le problème {problem}" -#: participation/models.py:1739 participation/models.py:1748 -#: participation/models.py:1833 participation/models.py:1876 +#: participation/models.py:1847 participation/models.py:1856 +#: participation/models.py:1941 participation/models.py:1984 msgid "passage" msgstr "passage" -#: participation/models.py:1740 +#: participation/models.py:1848 msgid "passages" msgstr "passages" -#: participation/models.py:1759 +#: participation/models.py:1867 msgid "difference" msgstr "différence" -#: participation/models.py:1760 +#: participation/models.py:1868 msgid "Score to add/remove on the final score" msgstr "Score à ajouter/retrancher au score final" -#: participation/models.py:1767 +#: participation/models.py:1875 msgid "tweak" msgstr "harmonisation" -#: participation/models.py:1768 +#: participation/models.py:1876 msgid "tweaks" msgstr "harmonisations" -#: participation/models.py:1796 +#: participation/models.py:1904 msgid "solution for the final tournament" msgstr "solution pour la finale" -#: participation/models.py:1801 participation/models.py:1845 +#: participation/models.py:1909 participation/models.py:1953 msgid "file" msgstr "fichier" -#: participation/models.py:1811 +#: participation/models.py:1919 #, python-brace-format msgid "Solution of team {team} for problem {problem}" msgstr "Solution de l'équipe {team} pour le problème {problem}" -#: participation/models.py:1813 +#: participation/models.py:1921 msgid "for final" msgstr "pour la finale" -#: participation/models.py:1816 +#: participation/models.py:1924 msgid "solution" msgstr "solution" -#: participation/models.py:1817 +#: participation/models.py:1925 msgid "solutions" msgstr "solutions" -#: participation/models.py:1851 +#: participation/models.py:1959 #, python-brace-format msgid "Synthesis of {team} as {type} for problem {problem} of {defender}" msgstr "" "Note de synthèse de l'équipe {team} en tant que {type} pour le problème " "{problem} de {defender}" -#: participation/models.py:1859 +#: participation/models.py:1967 msgid "synthesis" msgstr "note de synthèse" -#: participation/models.py:1860 +#: participation/models.py:1968 msgid "syntheses" msgstr "notes de synthèse" -#: participation/models.py:1869 +#: participation/models.py:1977 msgid "jury" msgstr "jury" -#: participation/models.py:1881 +#: participation/models.py:1989 msgid "defender writing note" msgstr "note d'écrit défenseur⋅se" -#: participation/models.py:1887 +#: participation/models.py:1995 msgid "defender oral note" msgstr "note d'oral défenseur⋅se" -#: participation/models.py:1893 +#: participation/models.py:2001 msgid "opponent writing note" msgstr "note d'écrit opposant⋅e" -#: participation/models.py:1899 +#: participation/models.py:2007 msgid "opponent oral note" msgstr "note d'oral opposant⋅e" -#: participation/models.py:1905 +#: participation/models.py:2013 msgid "reviewer writing note" msgstr "note d'écrit rapporteur⋅rice" -#: participation/models.py:1911 +#: participation/models.py:2019 msgid "reviewer oral note" msgstr "note d'oral du rapporteur⋅rice" -#: participation/models.py:1917 +#: participation/models.py:2025 msgid "observer writing note" msgstr "note d'écrit de l'observateur⋅rice" -#: participation/models.py:1923 +#: participation/models.py:2031 msgid "observer oral note" msgstr "note d'oral de l'observateur⋅rice" -#: participation/models.py:1988 +#: participation/models.py:2096 #, python-brace-format msgid "Notes of {jury} for {passage}" msgstr "Notes de {jury} pour le {passage}" -#: participation/models.py:1991 +#: participation/models.py:2099 msgid "note" msgstr "note" -#: participation/models.py:1992 +#: participation/models.py:2100 msgid "notes" msgstr "notes" @@ -1718,8 +1811,8 @@ msgstr "Pas d'équipe définie" #: participation/tables.py:147 #: participation/templates/participation/note_form.html:14 #: participation/templates/participation/passage_detail.html:15 -#: participation/templates/participation/passage_detail.html:168 -#: participation/templates/participation/passage_detail.html:174 +#: participation/templates/participation/passage_detail.html:176 +#: participation/templates/participation/passage_detail.html:182 #: participation/templates/participation/pool_detail.html:13 #: participation/templates/participation/pool_detail.html:152 #: participation/templates/participation/team_detail.html:185 @@ -1807,7 +1900,7 @@ msgid "Upload solution" msgstr "Envoyer une solution" #: participation/templates/participation/participation_detail.html:65 -#: participation/templates/participation/passage_detail.html:180 +#: participation/templates/participation/passage_detail.html:188 #: participation/templates/participation/pool_detail.html:157 #: participation/templates/participation/team_detail.html:245 #: participation/templates/participation/upload_motivation_letter.html:13 @@ -1870,12 +1963,12 @@ msgid "No synthesis was uploaded yet." msgstr "Aucune note de synthèse n'a encore été envoyée." #: participation/templates/participation/passage_detail.html:61 -#: participation/templates/participation/passage_detail.html:173 +#: participation/templates/participation/passage_detail.html:181 msgid "Update notes" msgstr "Modifier les notes" #: participation/templates/participation/passage_detail.html:66 -#: participation/templates/participation/passage_detail.html:179 +#: participation/templates/participation/passage_detail.html:187 msgid "Upload synthesis" msgstr "Envoyer une note de synthèse" @@ -1887,51 +1980,51 @@ msgstr "Détails des notes" msgid "Average points for the defender writing" msgstr "Moyenne de l'écrit de l'équipe défenseuse" -#: participation/templates/participation/passage_detail.html:88 +#: participation/templates/participation/passage_detail.html:90 msgid "Average points for the defender oral" msgstr "Moyenne de l'oral de l'équipe défenseuse" -#: participation/templates/participation/passage_detail.html:94 +#: participation/templates/participation/passage_detail.html:98 msgid "Average points for the opponent writing" msgstr "Moyenne de l'écrit de l'équipe opposante" -#: participation/templates/participation/passage_detail.html:100 +#: participation/templates/participation/passage_detail.html:104 msgid "Average points for the opponent oral" msgstr "Moyenne de l'oral de l'équipe opposante" -#: participation/templates/participation/passage_detail.html:106 +#: participation/templates/participation/passage_detail.html:110 msgid "Average points for the reviewer writing" msgstr "Moyenne de l'écrit de l'équipe rapportrice" -#: participation/templates/participation/passage_detail.html:112 +#: participation/templates/participation/passage_detail.html:116 msgid "Average points for the reviewer oral" msgstr "Moyenne de l'oral de l'équipe rapportrice" -#: participation/templates/participation/passage_detail.html:119 +#: participation/templates/participation/passage_detail.html:123 msgid "Average points for the observer writing" msgstr "Moyenne de l'écrit de l'équipe observatrice" -#: participation/templates/participation/passage_detail.html:125 +#: participation/templates/participation/passage_detail.html:129 msgid "Average points for the observer oral" msgstr "Moyenne de l'oral de l'équipe observatrice" -#: participation/templates/participation/passage_detail.html:136 +#: participation/templates/participation/passage_detail.html:140 msgid "Defender points" msgstr "Points de l'équipe défenseuse" -#: participation/templates/participation/passage_detail.html:142 +#: participation/templates/participation/passage_detail.html:148 msgid "Opponent points" msgstr "Points de l'équipe opposante" -#: participation/templates/participation/passage_detail.html:148 +#: participation/templates/participation/passage_detail.html:156 msgid "reviewer points" msgstr "Points de l'équipe rapportrice" -#: participation/templates/participation/passage_detail.html:155 +#: participation/templates/participation/passage_detail.html:163 msgid "observer points" msgstr "Points de l'équipe observatrice" -#: participation/templates/participation/passage_detail.html:167 +#: participation/templates/participation/passage_detail.html:175 #: participation/templates/participation/passage_form.html:11 msgid "Update passage" msgstr "Modifier le passage" @@ -2355,10 +2448,6 @@ msgstr "Dépublier les notes pour le second tour" msgid "Files available for download" msgstr "Fichiers disponibles au téléchargement" -#: participation/templates/participation/tournament_harmonize.html:14 -msgid "Rank" -msgstr "Rang" - #: participation/templates/participation/tournament_harmonize.html:16 #: registration/models.py:655 msgid "Note" @@ -2615,17 +2704,37 @@ msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e msgid "Notes were successfully uploaded." msgstr "Les notes ont bien été envoyées." -#: participation/views.py:1845 +#: participation/views.py:1496 +msgid "Role" +msgstr "Rôle" + +#: participation/views.py:1502 +msgid "Defender" +msgstr "Défenseur⋅se" + +#: participation/views.py:1508 +msgid "Opponent" +msgstr "Opposant⋅e" + +#: participation/views.py:1515 +msgid "Reviewer" +msgstr "Rapporteur⋅rice" + +#: participation/views.py:1522 +msgid "Observer" +msgstr "Observateur⋅rice" + +#: participation/views.py:1893 #, python-brace-format msgid "Notation sheets of pool {pool} of {tournament}.zip" msgstr "Feuilles de notations pour la poule {pool} du tournoi {tournament}.zip" -#: participation/views.py:1850 +#: participation/views.py:1898 #, python-brace-format msgid "Notation sheets of {tournament}.zip" msgstr "Feuilles de notation de {tournament}.zip" -#: participation/views.py:2017 +#: participation/views.py:2065 msgid "You can't upload a synthesis after the deadline." msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite." diff --git a/participation/forms.py b/participation/forms.py index e01093c..2178cd1 100644 --- a/participation/forms.py +++ b/participation/forms.py @@ -302,25 +302,26 @@ class UploadNotesForm(forms.Form): line = [s for s in line if s == s] # Strip cases line = [str(s).strip() for s in line if str(s)] - if line and line[0] == 'Problème': + if line and line[0] in ["Problème", "Problem"]: pool_size = len(line) - 1 - line_length = 2 + 6 * pool_size + line_length = 2 + (8 if df.iat[1, 8] == "Observer" else 6) * pool_size continue if pool_size == 0 or len(line) < line_length: continue name = line[0] - if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]: + if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe", + "role", "juree", "average", "coefficient", "subtotal", "team"]: continue notes = line[2:line_length] - print(name, notes) if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes): continue notes = list(map(lambda x: int(float(x)), notes)) - print(notes) - max_notes = pool_size * [20, 20, 10, 10, 10, 10] + max_notes = pool_size * [20 if settings.TFJM_APP == "TFJM" else 10, + 20 if settings.TFJM_APP == "TFJM" else 10, + 10, 10, 10, 10, 10, 10] for n, max_n in zip(notes, max_notes): if n > max_n: self.add_error('file', diff --git a/participation/models.py b/participation/models.py index 64dc852..c1f46e7 100644 --- a/participation/models.py +++ b/participation/models.py @@ -458,7 +458,7 @@ class Tournament(models.Model): return self.notes_sheet_id gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) - spreadsheet = gc.create(f"Feuille de notes - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID) + spreadsheet = gc.create(f"{_('Notation sheet')} - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID) spreadsheet.update_locale("fr_FR") spreadsheet.share(None, "anyone", "writer", with_link=True) self.notes_sheet_id = spreadsheet.id @@ -470,17 +470,21 @@ class Tournament(models.Model): gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) spreadsheet = gc.open_by_key(self.notes_sheet_id) worksheets = spreadsheet.worksheets() - if "Classement final" not in [ws.title for ws in worksheets]: - worksheet = spreadsheet.add_worksheet("Classement final", 100, 26) + if _("Final ranking") not in [ws.title for ws in worksheets]: + worksheet = spreadsheet.add_worksheet(_("Final ranking"), 30, 10) else: - worksheet = spreadsheet.worksheet("Classement final") + worksheet = spreadsheet.worksheet(_("Final ranking")) if worksheet.index != self.pools.count(): worksheet.update_index(self.pools.count()) - header = [["Équipe", "Score jour 1", "Harmonisation 1", "Score jour 2", "Harmonisation 2", "Total", "Rang"]] + header = [[_("Team"), _("Scores day 1"), _("Tweaks day 1"), _("Scores day 2"), _("Tweaks day 2")] + + ([_("Total D1 + D2"), _("Scores day 3"), _("Tweaks day 3")] + if settings.NB_ROUNDS >= 3 else []) + + [_("Total"), _("Rank")]] lines = [] participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all() + total_col, rank_col = ("F", "G") if settings.NB_ROUNDS == 2 else ("I", "J") for i, participation in enumerate(participations): line = [f"{participation.team.name} ({participation.team.trigram})"] lines.append(line) @@ -494,7 +498,7 @@ class Tournament(models.Model): tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation) tweak1 = tweak1_qs.get() if tweak1_qs.exists() else None - line.append(f"=SIERREUR('Poule {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)") + line.append(f"=SIERREUR('{_('Pool')} {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)") line.append(tweak1.diff if tweak1 else 0) if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists(): @@ -508,23 +512,49 @@ class Tournament(models.Model): tweak2 = tweak2_qs.get() if tweak2_qs.exists() else None line.append( - f"=SIERREUR('Poule {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)") + f"=SIERREUR('{_('Pool')} {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)") line.append(tweak2.diff if tweak2 else 0) + + if settings.NB_ROUNDS >= 3: + line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}") + + if Passage.objects.filter(pool__tournament=self, pool__round=3, defender=participation).exists(): + passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, defender=participation) + pool3 = passage3.pool + if pool3.participations.count() != 5: + position3 = passage3.position + else: + position3 = (passage3.position - 1) * 2 + pool3.room + tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation) + tweak3 = tweak3_qs.get() if tweak3_qs.exists() else None + + line.append( + f"=SIERREUR('{_('Pool')} {pool3.short_name}'!$D{pool3.juries.count() + 10 + position3}; 0)") + line.append(tweak3.diff if tweak3 else 0) + else: + line.append(0) + line.append(0) else: - # User has no second pool yet + # There is no second pool yet line.append(0) line.append(0) - line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}") - line.append(f"=RANG($F{i + 2}; $F$2:$F${participations.count() + 1})") + if settings.NB_ROUNDS >= 3: + line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}") + line.append(0) + line.append(0) - final_ranking = [["", "", "", ""], ["", "", "", ""], ["Équipe", "Score", "Rang", "Mention"], + line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}" + + (f" + (PI() - 2) * $G{i + 2} + $H{i + 2}" if settings.NB_ROUNDS >= 3 else "")) + line.append(f"=RANG(${total_col}{i + 2}; ${total_col}$2:${total_col}${participations.count() + 1})") + + final_ranking = [["", "", "", ""], ["", "", "", ""], [_("Team"), _("Score"), _("Rank"), _("Mention")], [f"=SORT($A$2:$A${participations.count() + 1}; " - f"$F$2:$F${participations.count() + 1}; FALSE)", - f"=SORT($F$2:$F${participations.count() + 1}; " - f"$F$2:$F${participations.count() + 1}; FALSE)", - f"=SORT($G$2:$G${participations.count() + 1}; " - f"$F$2:$F${participations.count() + 1}; FALSE)", ]] + f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", + f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; " + f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", + f"=SORT(${rank_col}$2:${rank_col}${participations.count() + 1}; " + f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", ]] final_ranking += [["", "", ""] for _i in range(participations.count() - 1)] notes = dict() @@ -538,12 +568,13 @@ class Tournament(models.Model): final_ranking[i + 3].append(participation.mention if not self.final else participation.mention_final) data = header + lines + final_ranking - worksheet.update(data, f"A1:G{2 * participations.count() + 4}", raw=False) + worksheet.update(data, f"A1:{rank_col}{2 * participations.count() + 4}", raw=False) format_requests = [] # Set the width of the columns - column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150)] + column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150), + ("H", 150), ("I", 150), ("J", 150)] for column, width in column_widths: grid_range = a1_range_to_grid_range(column, worksheet.id) format_requests.append({ @@ -563,7 +594,7 @@ class Tournament(models.Model): # Set borders border_ranges = [("A1:Z", "0000"), - (f"A1:G{participations.count() + 1}", "1111"), + (f"A1:{rank_col}{participations.count() + 1}", "1111"), (f"A{participations.count() + 4}:D{2 * participations.count() + 4}", "1111")] sides_names = ['top', 'bottom', 'left', 'right'] styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"] @@ -585,7 +616,7 @@ class Tournament(models.Model): }) # Make titles bold - bold_ranges = [("A1:Z", False), ("A1:G1", True), + bold_ranges = [("A1:Z", False), (f"A1:{rank_col}1", True), (f"A{participations.count() + 4}:D{participations.count() + 4}", True)] for bold_range, bold in bold_ranges: format_requests.append({ @@ -598,14 +629,18 @@ class Tournament(models.Model): # Set background color for headers and footers bg_colors = [("A1:Z", (1, 1, 1)), - ("A1:G1", (0.8, 0.8, 0.8)), + (f"A1:{rank_col}1", (0.8, 0.8, 0.8)), (f"A2:B{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"C2:C{participations.count() + 1}", (1, 1, 1)), (f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"E2:E{participations.count() + 1}", (1, 1, 1)), - (f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"A{participations.count() + 4}:D{participations.count() + 4}", (0.8, 0.8, 0.8)), (f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),] + if settings.NB_ROUNDS >= 3: + bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9))) + bg_colors.append((f"H2:I{participations.count() + 1}", (0.9, 0.9, 0.9))) + else: + bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9))) for bg_range, bg_color in bg_colors: r, g, b = bg_color format_requests.append({ @@ -622,9 +657,15 @@ class Tournament(models.Model): (f"D2:D{participations.count() + 1}", "0.0"), (f"E2:E{participations.count() + 1}", "0"), (f"F2:F{participations.count() + 1}", "0.0"), - (f"G2:G{participations.count() + 1}", "0"), (f"B{participations.count() + 5}:B{2 * participations.count() + 5}", "0.0"), (f"C{participations.count() + 5}:C{2 * participations.count() + 5}", "0"), ] + if settings.NB_ROUNDS >= 3: + number_format_ranges += [(f"G2:G{participations.count() + 1}", "0.0"), + (f"H2:H{participations.count() + 1}", "0"), + (f"I2:I{participations.count() + 1}", "0.0"), + (f"J2:J{participations.count() + 1}", "0"), ] + else: + number_format_ranges.append((f"G2:G{participations.count() + 1}", "0")) for number_format_range, pattern in number_format_ranges: format_requests.append({ "repeatCell": { @@ -643,16 +684,16 @@ class Tournament(models.Model): }) # Protect the header, the juries list, the footer and the ranking - protected_ranges = ["A1:G1", f"A2:B{participations.count() + 1}", + protected_ranges = ["A1:J1", f"A2:B{participations.count() + 1}", f"D2:D{participations.count() + 1}", f"F2:G{participations.count() + 1}", + f"I2:J{participations.count() + 1}", f"A{participations.count() + 4}:C{2 * participations.count() + 4}", ] for protected_range in protected_ranges: format_requests.append({ "addProtectedRange": { "protectedRange": { "range": a1_range_to_grid_range(protected_range, worksheet.id), - "description": "Structure du tableur à ne pas modifier " - "pour une meilleure prise en charge automatisée", + "description": _("Don't update the table structure for a better automated integration."), "warningOnly": True, }, } @@ -666,19 +707,21 @@ class Tournament(models.Model): # Draw has not been done yet return + translation.activate(settings.PREFERRED_LANGUAGE_CODE) + gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) spreadsheet = gc.open_by_key(self.notes_sheet_id) - worksheet = spreadsheet.worksheet("Classement final") + worksheet = spreadsheet.worksheet(_("Final ranking")) - score_cell = worksheet.find("Score") + score_cell = worksheet.find(_("Score")) max_row = score_cell.row - 3 if max_row == 1: # There is no team return - data = worksheet.get_values(f"A2:E{max_row}") + data = worksheet.get_values(f"A2:H{max_row}") for line in data: - trigram = line[0][-4:-1] + trigram = line[0][-settings.TEAM_CODE_LENGTH - 1:-1] participation = self.participations.get(team__trigram=trigram) pool1 = self.pools.get(round=1, participations=participation, room=1) tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation) @@ -701,6 +744,17 @@ class Tournament(models.Model): create_defaults={'diff': tweak2_nb, 'pool': pool2, 'participation': participation}) + if self.pools.filter(round=3, participations=participation).exists(): + pool3 = self.pools.get(round=3, participations=participation, room=1) + tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation) + tweak3_nb = int(line[7]) + if not tweak3_nb: + tweak3_qs.delete() + else: + tweak3_qs.update_or_create(defaults={'diff': tweak3_nb}, + create_defaults={'diff': tweak3_nb, 'pool': pool3, + 'participation': participation}) + nb_participations = self.participations.filter(valid=True).count() mentions = worksheet.get_values(f"A{score_cell.row + 1}:D{score_cell.row + nb_participations}") notes = dict() @@ -1164,26 +1218,31 @@ class Pool(models.Model): gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id) worksheets = spreadsheet.worksheets() - if f"Poule {self.short_name}" not in [ws.title for ws in worksheets]: - worksheet = spreadsheet.add_worksheet(f"Poule {self.short_name}", 100, 26) + if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]: + worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}", 100, 34) else: - worksheet = spreadsheet.worksheet(f"Poule {self.short_name}") + worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}") if any(ws.title == "Sheet1" for ws in worksheets): spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1")) pool_size = self.participations.count() - passage_width = 6 + has_observer = settings.TFJM_APP == "ETEAM" and pool_size >= 4 + passage_width = 6 + (2 if has_observer else 0) passages = self.passages.all() header = [ - sum(([f"Problème {passage.solution_number}"] + (passage_width - 1) * [""] - for passage in passages), start=["Problème", ""]), - sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "", - f"Opposant⋅e ({passage.opponent.team.trigram})", "", - f"Rapporteur⋅rice ({passage.reviewer.team.trigram})", ""] + sum(([_("Problem #{problem}").format(problem=passage.solution_number)] + (passage_width - 1) * [""] + for passage in passages), start=[_("Problem"), ""]), + sum(([f"{_('Defender')} ({passage.defender.team.trigram})", "", + f"{_('Opponent')} ({passage.opponent.team.trigram})", "", + f"{_('Reviewer')} ({passage.reviewer.team.trigram})", ""] + + ([f"{('Observer')} ({passage.observer.team.trigram})", ""] if has_observer else []) for passage in passages), start=["Rôle", ""]), - sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"] - for _passage in passages), start=["Juré⋅e", ""]), + sum(([f"{_('Writing')} (/{20 if settings.TFJM_APP == "TFJM" else 10})", + f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})" + f"{_('Writing')} (/10)", f"{_('Oral')} (/10)", f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"] + + ([f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"] if has_observer else []) + for _passage in passages), start=[_("Juree"), ""]), ] notes = [[]] # Begin with empty hidden line to ensure pretty design @@ -1193,6 +1252,8 @@ class Pool(models.Model): note = passage.notes.filter(jury=jury).first() line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral, note.reviewer_writing, note.reviewer_oral]) + if has_observer: + line.extend([note.observer_writing, note.observer_oral]) notes.append(line) notes.append([]) # Add empty line to ensure pretty design @@ -1204,11 +1265,15 @@ class Pool(models.Model): return '' return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26) - average = ["Moyenne", ""] - coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] for passage in passages), - start=["Coefficient", ""]) - subtotal = ["Sous-total", ""] - footer = [average, coeffs, subtotal, 26 * [""]] + average = [_("Average"), ""] + coeffs = sum(([passage.coeff_defender_writing, passage.coeff_defender_oral, + passage.coeff_opponent_writing, passage.coeff_opponent_oral, + passage.coeff_reviewer_writing, passage.coeff_reviewer_oral] + + ([passage.coeff_observer_writing, passage.coeff_observer_oral] if has_observer else []) + for passage in passages), + start=[_("Coefficient"), ""]) + subtotal = [_("Subtotal"), ""] + footer = [average, coeffs, subtotal, 34 * [""]] min_row = 5 max_row = min_row + self.juries.count() @@ -1234,8 +1299,14 @@ class Pool(models.Model): subtotal.extend([f"={rep_w_col}{max_row + 1} * {rep_w_col}{max_row + 2}" f" + {rep_o_col}{max_row + 1} * {rep_o_col}{max_row + 2}", ""]) + if has_observer: + obs_w_col = getcol(min_column + passage_width * i + 6) + obs_o_col = getcol(min_column + passage_width * i + 7) + subtotal.extend([f"={obs_w_col}{max_row + 1} * {obs_w_col}{max_row + 2}" + f" + {obs_o_col}{max_row + 1} * {obs_o_col}{max_row + 2}", ""]) + ranking = [ - ["Équipe", "", "Problème", "Total", "Rang"], + [_("Team"), "", _("Problem"), _("Total"), _("Rank")], ] all_passages = Passage.objects.filter(pool__tournament=self.tournament, pool__round=self.round, @@ -1258,14 +1329,22 @@ class Pool(models.Model): reviewer_col = reviewer_passage.position - 1 formula = "=" - formula += (f"'Poule {defender_passage.pool.short_name}'" + formula += (f"'{_('Pool')} {defender_passage.pool.short_name}'" f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender - formula += (f" + 'Poule {opponent_passage.pool.short_name}'" + formula += (f" + '{_('Pool')} {opponent_passage.pool.short_name}'" f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent - formula += (f" + 'Poule {reviewer_passage.pool.short_name}'" + formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'" f"!{getcol(min_column + reviewer_col * passage_width + 4)}{reviewer_row + 3}") # reviewer + if has_observer: + observer_passage = Passage.objects.get(observer=participation, + pool__tournament=self.tournament, pool__round=self.round) + observer_row = 5 + observer_passage.pool.juries.count() + observer_col = observer_passage.position - 1 + formula += (f" + '{_('Pool')} {observer_passage.pool.short_name}'" + f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}") + ranking.append([f"{participation.team.name} ({participation.team.trigram})", "", - f"='Poule {defender_passage.pool.short_name}'" + f"='{_('Pool')} {defender_passage.pool.short_name}'" f"!${getcol(3 + defender_col * passage_width)}$1", formula, f"=RANG(D{max_row + 6 + i}; " @@ -1273,8 +1352,8 @@ class Pool(models.Model): all_values = header + notes + footer + ranking - worksheet.batch_clear([f"A1:Z{max_row + 5 + pool_size}"]) - worksheet.update("A1:Z", all_values, raw=False) + worksheet.batch_clear([f"A1:AH{max_row + 5 + pool_size}"]) + worksheet.update("A1:AH", all_values, raw=False) format_requests = [] @@ -1300,13 +1379,13 @@ class Pool(models.Model): for i in range(pool_size + 1): merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}") - format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:Z", worksheet.id)}}) + format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:AH", worksheet.id)}}) for name in merge_cells: grid_range = a1_range_to_grid_range(name, worksheet.id) format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}}) # Make titles bold - bold_ranges = [("A1:Z", False), ("A1:Z3", True), + bold_ranges = [("A1:AH", False), ("A1:AH3", True), (f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)] for bold_range, bold in bold_ranges: format_requests.append({ @@ -1318,7 +1397,7 @@ class Pool(models.Model): }) # Set background color for headers and footers - bg_colors = [("A1:Z", (1, 1, 1)), + bg_colors = [("A1:AH", (1, 1, 1)), (f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)), (f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)), (f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)), @@ -1407,7 +1486,7 @@ class Pool(models.Model): }) # Define borders - border_ranges = [("A1:Z", "0000"), + border_ranges = [("A1:AH", "0000"), (f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"), (f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"), (f"A1:B{max_row + 3}", "1113"), @@ -1443,7 +1522,7 @@ class Pool(models.Model): for j in range(passage_width): column = getcol(min_column + i * passage_width + j) min_note = 0 - max_note = 20 if j < 2 else 10 + max_note = 20 if j < 2 and settings.TFJM_APP == "TFJM" else 10 format_requests.append({ "setDataValidation": { "range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id), @@ -1453,8 +1532,8 @@ class Pool(models.Model): "values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); ' f'{column}4>={min_note}; {column}4<={max_note})'},], }, - "inputMessage": f"La saisie doit être un entier valide " - f"compris entre {min_note} et {max_note}.", + "inputMessage": (_("Input must be a valid integer between {min_note} and {max_note}.") + .format(min_note=min_note, max_note=max_note)), "strict": True, }, } @@ -1482,16 +1561,15 @@ class Pool(models.Model): }) # Protect the header, the juries list, the footer and the ranking - protected_ranges = ["A1:Z4", + protected_ranges = ["A1:AH4", f"A{min_row}:B{max_row}", - f"A{max_row}:Z{max_row + 5 + pool_size}"] + f"A{max_row}:AH{max_row + 5 + pool_size}"] for protected_range in protected_ranges: format_requests.append({ "addProtectedRange": { "protectedRange": { "range": a1_range_to_grid_range(protected_range, worksheet.id), - "description": "Structure du tableur à ne pas modifier " - "pour une meilleure prise en charge automatisée", + "description": _("Don't update the table structure for a better automated integration."), "warningOnly": True, }, } @@ -1505,9 +1583,9 @@ class Pool(models.Model): gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id) - worksheet = spreadsheet.worksheet(f"Poule {self.short_name}") + worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}") - average_cell = worksheet.find("Moyenne") + average_cell = worksheet.find(_("Average")) min_row = 5 max_row = average_cell.row - 1 juries_visible = worksheet.get(f"A{min_row}:B{max_row}") @@ -1527,16 +1605,17 @@ class Pool(models.Model): gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) self.tournament.create_spreadsheet() spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id) - worksheet = spreadsheet.worksheet(f"Poule {self.short_name}") + worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}") - average_cell = worksheet.find("Moyenne") + average_cell = worksheet.find(_("Average")) min_row = 5 max_row = average_cell.row - 2 - data = worksheet.get_values(f"A{min_row}:Z{max_row}") + data = worksheet.get_values(f"A{min_row}:AH{max_row}") if not data or not data[0]: return - passage_width = 6 + has_observer = settings.TFJM_APP == "ETEAM" and self.participations.count() >= 4 + passage_width = 6 + (2 if has_observer else 0) for line in data: jury_name = line[0] jury_id = line[1] @@ -1640,56 +1719,87 @@ class Passage(models.Model): def average_defender_writing(self) -> float: return self.avg(note.defender_writing for note in self.notes.all()) + @property + def coeff_defender_writing(self) -> float: + return 1 if settings.TFJM_APP == "TFJM" else 2 + @property def average_defender_oral(self) -> float: return self.avg(note.defender_oral for note in self.notes.all()) + @property + def coeff_defender_oral(self) -> float: + coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3 + coeff *= 1 - 0.25 * self.defender_penalties + return coeff + @property def average_defender(self) -> float: - writing_coeff = 1 if settings.TFJM_APP == "TFJM" else 2 - oral_coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3 - oral_coeff *= 1 - 0.25 * self.defender_penalties - return writing_coeff * self.average_defender_writing + oral_coeff * self.average_defender_oral + return (self.coeff_defender_writing * self.average_defender_writing + + self.coeff_defender_oral * self.average_defender_oral) @property def average_opponent_writing(self) -> float: return self.avg(note.opponent_writing for note in self.notes.all()) + @property + def coeff_opponent_writing(self) -> float: + return 0.9 if not self.observer else 0.6 + @property def average_opponent_oral(self) -> float: return self.avg(note.opponent_oral for note in self.notes.all()) + @property + def coeff_opponent_oral(self) -> float: + return 2 + @property def average_opponent(self) -> float: - writing_coeff = 0.9 if not self.observer else 0.6 - oral_coeff = 2 - return writing_coeff * self.average_opponent_writing + oral_coeff * self.average_opponent_oral + return (self.coeff_opponent_writing * self.average_opponent_writing + + self.coeff_opponent_oral * self.average_opponent_oral) @property def average_reviewer_writing(self) -> float: return self.avg(note.reviewer_writing for note in self.notes.all()) + @property + def coeff_reviewer_writing(self): + return 0.9 if not self.observer else 0.6 + @property def average_reviewer_oral(self) -> float: return self.avg(note.reviewer_oral for note in self.notes.all()) + @property + def coeff_reviewer_oral(self): + return 1 if settings.TFJM_APP == "TFJM" else 1.2 + @property def average_reviewer(self) -> float: - writing_coeff = 0.9 if not self.observer else 0.6 - oral_coeff = 1 if settings.TFJM_APP == "TFJM" else 1.2 - return writing_coeff * self.average_reviewer_writing + oral_coeff * self.average_reviewer_oral + return (self.coeff_reviewer_writing * self.average_reviewer_writing + + self.coeff_reviewer_oral * self.average_reviewer_oral) @property def average_observer_writing(self) -> float: return self.avg(note.observer_writing for note in self.notes.all()) + @property + def coeff_observer_writing(self): + return 0.6 + @property def average_observer_oral(self) -> float: return self.avg(note.observer_oral for note in self.notes.all()) + @property + def coeff_observer_oral(self): + return 0.5 + @property def average_observer(self) -> float: - return 0.6 * self.average_observer_writing + 0.5 * self.average_observer_oral + return (self.coeff_observer_writing * self.average_observer_writing + + self.coeff_observer_oral * self.average_observer_oral) @property def averages(self): @@ -1707,9 +1817,7 @@ class Passage(models.Model): avg = self.average_defender if participation == self.defender else self.average_opponent \ if participation == self.opponent else self.average_reviewer if participation == self.reviewer \ else self.average_observer if participation == self.observer else 0 - - if self.pool.round == 3 and settings.TFJM_APP == "ETEAM": - avg *= math.pi - 2 + avg *= self.pool.coeff return avg @@ -1957,7 +2065,7 @@ class Note(models.Model): passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk) spreadsheet_id = passage.pool.tournament.notes_sheet_id spreadsheet = gc.open_by_key(spreadsheet_id) - worksheet = spreadsheet.worksheet(f"Poule {passage.pool.short_name}") + worksheet = spreadsheet.worksheet(f"{_('Pool')} {passage.pool.short_name}") jury_id_cell = worksheet.find(str(self.jury_id), in_column=2) if not jury_id_cell: raise ValueError("The jury ID cell was not found in the spreadsheet.") diff --git a/participation/templates/participation/passage_detail.html b/participation/templates/participation/passage_detail.html index 13a18fb..0733ef7 100644 --- a/participation/templates/participation/passage_detail.html +++ b/participation/templates/participation/passage_detail.html @@ -82,13 +82,17 @@ {% trans "Average points for the defender writing" %} ({{ passage.defender.team.trigram }}) : -
{{ passage.average_defender_writing|floatformat }}/20
+
+ {{ passage.average_defender_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %} +
{% trans "Average points for the defender oral" %} ({{ passage.defender.team.trigram }}) :
-
{{ passage.average_defender_oral|floatformat }}/20
+
+ {{ passage.average_defender_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %} +
{% trans "Average points for the opponent writing" %} @@ -113,14 +117,14 @@ ({{ passage.reviewer.team.trigram }}) :
{{ passage.average_reviewer_oral|floatformat }}/10
- + {% if passage.observer %}
{% trans "Average points for the observer writing" %} ({{ passage.observer.team.trigram }}) :
{{ passage.average_observer_writing|floatformat }}/10
- +
{% trans "Average points for the observer oral" %} ({{ passage.observer.team.trigram }}) : @@ -136,27 +140,31 @@ {% trans "Defender points" %} ({{ passage.defender.team.trigram }}) :
-
{{ passage.average_defender|floatformat }}/52
+
+ {{ passage.average_defender|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %} +
{% trans "Opponent points" %} ({{ passage.opponent.team.trigram }}) :
-
{{ passage.average_opponent|floatformat }}/29
+
+ {{ passage.average_opponent|floatformat }}/{% if TFJM_APP == "TFJM" %}29{% else %}{% if passage.observer %}26{% else %}29{% endif %}{% endif %} +
{% trans "reviewer points" %} ({{ passage.reviewer.team.trigram }}) :
-
{{ passage.average_reviewer|floatformat }}/19
- +
{{ passage.average_reviewer|floatformat }}/{% if TFJM_APP == "TFJM" %}19{% else %}{% if passage.observer %}18{% else %}21{% endif %}{% endif %}
+ {% if passage.observer %}
{% trans "observer points" %} ({{ passage.observer.team.trigram }}) :
- -
{{ passage.average_observer|floatformat }}/10
+ +
{{ passage.average_observer|floatformat }}/6
{% endif %} diff --git a/participation/views.py b/participation/views.py index c249d57..ca17546 100644 --- a/participation/views.py +++ b/participation/views.py @@ -24,7 +24,7 @@ from django.http import FileResponse, Http404, HttpResponse from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy -from django.utils import timezone +from django.utils import timezone, translation from django.utils.crypto import get_random_string from django.utils.decorators import method_decorator from django.utils.timezone import localtime @@ -1254,7 +1254,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView): return self.form_invalid(form) for vr, notes in parsed_notes.items(): - notes_count = 6 + notes_count = 6 + (2 if pool.participations.count() >= 4 and settings.TFJM_APP == "ETEAM" else 0) for i, passage in enumerate(pool.passages.all()): note = Note.objects.get_or_create(jury=vr, passage=passage)[0] passage_notes = notes[notes_count * i:notes_count * (i + 1)] @@ -1289,8 +1289,11 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): return self.handle_no_permission() def render_to_response(self, context, **response_kwargs): # noqa: C901 + translation.activate(settings.PREFERRED_LANGUAGE_CODE) + pool_size = self.object.passages.count() - passage_width = 6 + has_observer = self.object.participations.count() >= 4 and settings.TFJM_APP == "ETEAM" + passage_width = 6 + (2 if has_observer else 0) line_length = pool_size * passage_width def getcol(number: int) -> str: @@ -1475,79 +1478,96 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): header_pb = TableRow() table.addElement(header_pb) problems_tc = TableCell(valuetype="string", stylename=title_style_topleft) - problems_tc.addElement(P(text="Problème")) + problems_tc.addElement(P(text=_("Problem"))) problems_tc.setAttribute('numbercolumnsspanned', "2") header_pb.addElement(problems_tc) header_pb.addElement(CoveredTableCell()) for passage in self.object.passages.all(): tc = TableCell(valuetype="string", stylename=title_style_topleftright) - tc.addElement(P(text=f"Problème {passage.solution_number}")) - tc.setAttribute('numbercolumnsspanned', "6") + tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number))) + tc.setAttribute('numbercolumnsspanned', str(passage_width)) header_pb.addElement(tc) - header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=5)) + header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=passage_width - 1)) # Add roles on the second line of the table header_role = TableRow() table.addElement(header_role) role_tc = TableCell(valuetype="string", stylename=title_style_left) - role_tc.addElement(P(text="Rôle")) + role_tc.addElement(P(text=_("Role"))) role_tc.setAttribute('numbercolumnsspanned', "2") header_role.addElement(role_tc) header_role.addElement(CoveredTableCell()) for i in range(pool_size): defender_tc = TableCell(valuetype="string", stylename=title_style_left) - defender_tc.addElement(P(text="Défenseur⋅se")) + defender_tc.addElement(P(text=_("Defender"))) defender_tc.setAttribute('numbercolumnsspanned', "2") header_role.addElement(defender_tc) header_role.addElement(CoveredTableCell()) opponent_tc = TableCell(valuetype="string", stylename=title_style) - opponent_tc.addElement(P(text="Opposant⋅e")) + opponent_tc.addElement(P(text=_("Opponent"))) opponent_tc.setAttribute('numbercolumnsspanned', "2") header_role.addElement(opponent_tc) header_role.addElement(CoveredTableCell()) reviewer_tc = TableCell(valuetype="string", - stylename=title_style_right) - reviewer_tc.addElement(P(text="Rapporteur⋅rice")) + stylename=title_style if has_observer else title_style_right) + reviewer_tc.addElement(P(text=_("Reviewer"))) reviewer_tc.setAttribute('numbercolumnsspanned', "2") header_role.addElement(reviewer_tc) header_role.addElement(CoveredTableCell()) + if has_observer: + observer_tc = TableCell(valuetype="string", stylename=title_style_right) + observer_tc.addElement(P(text=_("Observer"))) + observer_tc.setAttribute('numbercolumnsspanned', "2") + header_role.addElement(observer_tc) + header_role.addElement(CoveredTableCell()) + # Add maximum notes on the third line header_notes = TableRow() table.addElement(header_notes) - jury_tc = TableCell(valuetype="string", value="Juré⋅e", stylename=title_style_botleft) - jury_tc.addElement(P(text="Juré⋅e")) + jury_tc = TableCell(valuetype="string", value=_("Juree"), stylename=title_style_botleft) + jury_tc.addElement(P(text=_("Juree"))) jury_tc.setAttribute('numbercolumnsspanned', "2") header_notes.addElement(jury_tc) header_notes.addElement(CoveredTableCell()) for i in range(pool_size): defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft) - defender_w_tc.addElement(P(text="Écrit (/20)")) + defender_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})")) header_notes.addElement(defender_w_tc) defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot) - defender_o_tc.addElement(P(text="Oral (/20)")) + defender_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})")) header_notes.addElement(defender_o_tc) opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot) - opponent_w_tc.addElement(P(text="Écrit (/10)")) + opponent_w_tc.addElement(P(text=f"{_('Writing')} (/10)")) header_notes.addElement(opponent_w_tc) opponent_o_tc = TableCell(valuetype="string", stylename=title_style_bot) - opponent_o_tc.addElement(P(text="Oral (/10)")) + opponent_o_tc.addElement(P(text=f"{_('Oral')} (/10)")) header_notes.addElement(opponent_o_tc) reviewer_w_tc = TableCell(valuetype="string", stylename=title_style_bot) - reviewer_w_tc.addElement(P(text="Écrit (/10)")) + reviewer_w_tc.addElement(P(text=f"{_('Writing')} (/10)")) header_notes.addElement(reviewer_w_tc) - reviewer_o_tc = TableCell(valuetype="string", stylename=title_style_botright) - reviewer_o_tc.addElement(P(text="Oral (/10)")) + reviewer_o_tc = TableCell(valuetype="string", + stylename=title_style_bot if has_observer else title_style_botright) + reviewer_o_tc.addElement(P(text=f"{_('Oral')} (/10)")) header_notes.addElement(reviewer_o_tc) + if has_observer: + observer_w_tc = TableCell(valuetype="string", stylename=title_style_bot) + observer_w_tc.addElement(P(text=f"{_('Writing')} (/10)")) + header_notes.addElement(observer_w_tc) + + observer_o_tc = TableCell(valuetype="string", stylename=title_style_botright) + observer_o_tc.addElement(P(text=f"{_('Oral')} (/10)")) + header_notes.addElement(observer_o_tc) + # Add a notation line for each jury for jury in self.object.juries.all(): jury_row = TableRow() @@ -1577,7 +1597,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): average_row = TableRow() table.addElement(average_row) average_tc = TableCell(valuetype="string", stylename=title_style_topleftright) - average_tc.addElement(P(text="Moyenne")) + average_tc.addElement(P(text=_("Average"))) average_tc.setAttribute('numbercolumnsspanned', "2") average_row.addElement(average_tc) average_row.addElement(CoveredTableCell()) @@ -1596,40 +1616,50 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): coeff_row = TableRow() table.addElement(coeff_row) coeff_tc = TableCell(valuetype="string", stylename=title_style_leftright) - coeff_tc.addElement(P(text="Coefficient")) + coeff_tc.addElement(P(text=_("Coefficient"))) coeff_tc.setAttribute('numbercolumnsspanned', "2") coeff_row.addElement(coeff_tc) coeff_row.addElement(CoveredTableCell()) for passage in self.object.passages.all(): - defender_w_tc = TableCell(valuetype="float", value=1, stylename=style_left) - defender_w_tc.addElement(P(text="1")) + defender_w_tc = TableCell(valuetype="float", value=passage.coeff_defender_writing, stylename=style_left) + defender_w_tc.addElement(P(text=str(passage.coeff_defender_writing))) coeff_row.addElement(defender_w_tc) - defender_o_tc = TableCell(valuetype="float", value=1.6 - 0.4 * passage.defender_penalties, stylename=style) - defender_o_tc.addElement(P(text=str(2 - 0.4 * passage.defender_penalties))) + defender_o_tc = TableCell(valuetype="float", value=passage.coeff_defender_oral, stylename=style) + defender_o_tc.addElement(P(text=str(passage.coeff_defender_oral))) coeff_row.addElement(defender_o_tc) - opponent_w_tc = TableCell(valuetype="float", value=0.9, stylename=style) - opponent_w_tc.addElement(P(text="1")) + opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style) + opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing))) coeff_row.addElement(opponent_w_tc) - opponent_o_tc = TableCell(valuetype="float", value=2, stylename=style) - opponent_o_tc.addElement(P(text="2")) + opponent_o_tc = TableCell(valuetype="float", value=passage.coeff_opponent_oral, stylename=style) + opponent_o_tc.addElement(P(text=str(passage.coeff_opponent_oral))) coeff_row.addElement(opponent_o_tc) - reviewer_w_tc = TableCell(valuetype="float", value=0.9, stylename=style) - reviewer_w_tc.addElement(P(text="1")) + reviewer_w_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_writing, stylename=style) + reviewer_w_tc.addElement(P(text=str(passage.coeff_reviewer_writing))) coeff_row.addElement(reviewer_w_tc) - reviewer_o_tc = TableCell(valuetype="float", value=1, stylename=style_right) - reviewer_o_tc.addElement(P(text="1")) + reviewer_o_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_oral, + stylename=style if has_observer else style_right) + reviewer_o_tc.addElement(P(text=str(passage.coeff_reviewer_oral))) coeff_row.addElement(reviewer_o_tc) + if has_observer: + observer_w_tc = TableCell(valuetype="float", value=passage.coeff_observer_writing, stylename=style) + observer_w_tc.addElement(P(text=str(passage.coeff_observer_writing))) + coeff_row.addElement(observer_w_tc) + + observer_o_tc = TableCell(valuetype="float", value=passage.coeff_observer_oral, stylename=style_right) + observer_o_tc.addElement(P(text=str(passage.coeff_observer_oral))) + coeff_row.addElement(observer_o_tc) + # Add the subtotal on the next line subtotal_row = TableRow() table.addElement(subtotal_row) subtotal_tc = TableCell(valuetype="string", stylename=title_style_botleft) - subtotal_tc.addElement(P(text="Sous-total")) + subtotal_tc.addElement(P(text=_("Subtotal"))) subtotal_tc.setAttribute('numbercolumnsspanned', "2") subtotal_row.addElement(subtotal_tc) subtotal_row.addElement(CoveredTableCell()) @@ -1656,7 +1686,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): rep_w_col = getcol(min_column + passage_width * i + 4) rep_o_col = getcol(min_column + passage_width * i + 5) - reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer, stylename=style_botright) + reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer, + stylename=style_bot if has_observer else style_botright) reviewer_tc.addElement(P(text=str(passage.average_reviewer))) reviewer_tc.setAttribute('numbercolumnsspanned', "2") reviewer_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]" @@ -1664,6 +1695,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): subtotal_row.addElement(reviewer_tc) subtotal_row.addElement(CoveredTableCell()) + if has_observer: + obs_w_col = getcol(min_column + passage_width * i + 6) + obs_o_col = getcol(min_column + passage_width * i + 7) + observer_tc = TableCell(valuetype="float", value=passage.average_observer, stylename=style_botright) + observer_tc.addElement(P(text=str(passage.average_observer))) + observer_tc.setAttribute('numbercolumnsspanned', "2") + observer_tc.setAttribute("formula", f"of:=[.{obs_w_col}{max_row + 1}] * [.{obs_w_col}{max_row + 2}]" + f" + [.{obs_o_col}{max_row + 1}] * [.{obs_o_col}{max_row + 2}]") + subtotal_row.addElement(observer_tc) + subtotal_row.addElement(CoveredTableCell()) + table.addElement(TableRow()) if self.object.participations.count() == 5: @@ -1681,17 +1723,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): scores_header = TableRow() table.addElement(scores_header) team_tc = TableCell(valuetype="string", stylename=title_style_topbotleft) - team_tc.addElement(P(text="Équipe")) + team_tc.addElement(P(text=_("Team"))) team_tc.setAttribute('numbercolumnsspanned', "2") scores_header.addElement(team_tc) problem_tc = TableCell(valuetype="string", stylename=title_style_topbot) - problem_tc.addElement(P(text="Problème")) + problem_tc.addElement(P(text=_("Problem"))) scores_header.addElement(problem_tc) total_tc = TableCell(valuetype="string", stylename=title_style_topbot) - total_tc.addElement(P(text="Total")) + total_tc.addElement(P(text=_("Total"))) scores_header.addElement(total_tc) rank_tc = TableCell(valuetype="string", stylename=title_style_topbotright) - rank_tc.addElement(P(text="Rang")) + rank_tc.addElement(P(text=_("Rank"))) scores_header.addElement(rank_tc) sorted_participations = sorted(self.object.participations.all(), key=lambda p: -self.object.average(p)) @@ -1707,13 +1749,15 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): problem_tc = TableCell(valuetype="string", stylename=style_bot if passage.position == pool_size else style) - problem_tc.addElement(P(text=f"Problème {passage.solution_number}")) + problem_tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number))) problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]") team_row.addElement(problem_tc) defender_pos = passage.position - 1 opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1 reviewer_pos = self.object.passages.get(reviewer=passage.defender).position - 1 + observer_pos = self.object.passages.get(observer=passage.defender).position - 1 \ + if has_observer else None score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender), stylename=style_bot if passage.position == pool_size else style) @@ -1721,7 +1765,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): formula = "of:=" formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender formula += " + " + getcol(min_column + opponent_pos * passage_width + 2) + str(max_row + 3) # Opponent - formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # reviewer + formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # Reviewer + if has_observer: + # Observer + formula += " + " + getcol(min_column + observer_pos * passage_width + 6) + str(max_row + 3) score_tc.setAttribute("formula", formula) team_row.addElement(score_tc) @@ -1730,7 +1777,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): stylename=style_botright if passage.position == pool_size else style_right) rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1))) rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; " - f"[.{score_col}${max_row + 6}]:[.{score_col}${max_row + 5 + pool_size}])") + f"[.{score_col}${max_row + 6}]:" + f"[.{score_col}${max_row + 5 + pool_size}])") team_row.addElement(rank_tc) table.addElement(TableRow()) @@ -1755,8 +1803,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView): return FileResponse(streaming_content=open("/tmp/notes.ods", "rb"), content_type="application/vnd.oasis.opendocument.spreadsheet", - filename=f"Feuille de notes - {self.object.tournament.name} " - f"- Poule {self.object.short_name}.ods") + filename=f"{_('Notation sheet')} - {self.object.tournament.name} " + f"- {_('Pool')} {self.object.short_name}.ods") class NotationSheetTemplateView(VolunteerMixin, DetailView):