1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2024-11-30 10:53:01 +00:00

Compare commits

..

No commits in common. "620bbe78170815b35f96c01c0b8bf24ceb8e62ad" and "696863f6c365f18ddba8db339aed5d4e0cf6d6ff" have entirely different histories.

26 changed files with 580 additions and 892 deletions

View File

@ -416,7 +416,7 @@ class Pool(models.Model):
passage_pool = pool2
passage_position = 1 + i // 2
reporter = tds[line[0]].participation
defender = tds[line[0]].participation
opponent = tds[line[1]].participation
reviewer = tds[line[2]].participation
observer = tds[line[3]].participation if self.size >= 4 and settings.TFJM_APP == "ETEAM" else None
@ -426,11 +426,11 @@ class Pool(models.Model):
pool=passage_pool,
position=passage_position,
solution_number=tds[line[0]].accepted,
reporter=reporter,
defender=defender,
opponent=opponent,
reviewer=reviewer,
observer=observer,
reporter_penalties=tds[line[0]].penalty_int,
defender_penalties=tds[line[0]].penalty_int,
)
# Update Google Sheets
@ -549,7 +549,7 @@ class TeamDraw(models.Model):
@property
def penalty(self):
"""
The penalty multiplier on the reporter oral, in percentage, which is a malus of 25% for each penalty.
The penalty multiplier on the defender oral, in percentage, which is a malus of 25% for each penalty.
"""
return 25 * self.penalty_int

View File

@ -521,9 +521,9 @@ document.addEventListener('DOMContentLoaded', () => {
teamTd.innerText = team
teamTr.append(teamTd)
let reporterTd = document.createElement('td')
reporterTd.classList.add('text-center')
reporterTd.innerText = 'Déf'
let defenderTd = document.createElement('td')
defenderTd.classList.add('text-center')
defenderTd.innerText = 'Déf'
let opponentTd = document.createElement('td')
opponentTd.classList.add('text-center')
@ -537,29 +537,29 @@ document.addEventListener('DOMContentLoaded', () => {
if (poule.teams.length === 3) {
switch (i) {
case 0:
teamTr.append(reporterTd, reviewerTd, opponentTd)
teamTr.append(defenderTd, reviewerTd, opponentTd)
break
case 1:
teamTr.append(opponentTd, reporterTd, reviewerTd)
teamTr.append(opponentTd, defenderTd, reviewerTd)
break
case 2:
teamTr.append(reviewerTd, opponentTd, reporterTd)
teamTr.append(reviewerTd, opponentTd, defenderTd)
break
}
} else if (poule.teams.length === 4) {
let emptyTd = document.createElement('td')
switch (i) {
case 0:
teamTr.append(reporterTd, emptyTd, reviewerTd, opponentTd)
teamTr.append(defenderTd, emptyTd, reviewerTd, opponentTd)
break
case 1:
teamTr.append(opponentTd, reporterTd, emptyTd, reviewerTd)
teamTr.append(opponentTd, defenderTd, emptyTd, reviewerTd)
break
case 2:
teamTr.append(reviewerTd, opponentTd, reporterTd, emptyTd)
teamTr.append(reviewerTd, opponentTd, defenderTd, emptyTd)
break
case 3:
teamTr.append(emptyTd, reviewerTd, opponentTd, reporterTd)
teamTr.append(emptyTd, reviewerTd, opponentTd, defenderTd)
break
}
} else if (poule.teams.length === 5) {
@ -567,19 +567,19 @@ document.addEventListener('DOMContentLoaded', () => {
let emptyTd2 = document.createElement('td')
switch (i) {
case 0:
teamTr.append(reporterTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
teamTr.append(defenderTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
break
case 1:
teamTr.append(emptyTd, reporterTd, reviewerTd, emptyTd2, opponentTd)
teamTr.append(emptyTd, defenderTd, reviewerTd, emptyTd2, opponentTd)
break
case 2:
teamTr.append(opponentTd, emptyTd, reporterTd, emptyTd2, reviewerTd)
teamTr.append(opponentTd, emptyTd, defenderTd, emptyTd2, reviewerTd)
break
case 3:
teamTr.append(reviewerTd, opponentTd, emptyTd, reporterTd, emptyTd2)
teamTr.append(reviewerTd, opponentTd, emptyTd, defenderTd, emptyTd2)
break
case 4:
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, reporterTd)
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, defenderTd)
break
}
}
@ -662,7 +662,7 @@ document.addEventListener('DOMContentLoaded', () => {
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral reporter
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
// This is P - 6 for the ETEAM
if (penaltyDiv === null) {
penaltyDiv = document.createElement('div')

View File

@ -307,71 +307,71 @@
<td class="text-center">{{ td.participation.team.trigram }}</td>
{% if pool.size == 3 %}
{% if forloop.counter == 1 %}
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">Déf</td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
{% elif forloop.counter == 2 %}
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
<td class="text-center">Rap</td>
{% elif forloop.counter == 3 %}
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
{% endif %}
{% elif pool.size == 4 %}
{% if forloop.counter == 1 %}
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
{% elif forloop.counter == 2 %}
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
{% elif forloop.counter == 3 %}
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
<td></td>
{% elif forloop.counter == 4 %}
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
{% endif %}
{% elif pool.size == 5 %}
{% if forloop.counter == 1 %}
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center"></td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td></td>
{% elif forloop.counter == 2 %}
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center"></td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td></td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
{% elif forloop.counter == 3 %}
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center"></td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">Opp</td>
<td></td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
{% elif forloop.counter == 4 %}
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td class="text-center"></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td></td>
<td class="text-center">Déf</td>
<td></td>
{% elif forloop.counter == 5 %}
<td class="text-center"></td>
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td></td>
<td class="text-center">Déf</td>
{% endif %}
{% endif %}
</tr>

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-06 22:07+0200\n"
"POT-Creation-Date: 2024-07-06 10:16+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"
@ -79,7 +79,7 @@ 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:783
#: participation/models.py:807 participation/models.py:1131
#: participation/models.py:807 participation/models.py:1116
#: 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:1666 participation/models.py:1675
#: participation/models.py:1651 participation/models.py:1660
#: participation/tables.py:84
msgid "pool"
msgstr "poule"
@ -265,7 +265,7 @@ msgid "teams"
msgstr "équipes"
#: draw/admin.py:92 draw/models.py:245 draw/models.py:465
#: participation/models.py:1135
#: participation/models.py:1120
msgid "round"
msgstr "tour"
@ -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:1143
#: draw/models.py:268 participation/models.py:1128
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:1667
#: draw/models.py:447 participation/models.py:1652
msgid "pools"
msgstr "poules"
#: draw/models.py:459 participation/models.py:1121 participation/models.py:1886
#: participation/models.py:1920 participation/models.py:1962
#: draw/models.py:459 participation/models.py:1106 participation/models.py:1871
#: participation/models.py:1901 participation/models.py:1943
msgid "participation"
msgstr "participation"
@ -701,9 +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:1252
#: participation/models.py:1689 participation/models.py:1927
#: participation/views.py:1492 participation/views.py:1757
#: draw/models.py:496 draw/models.py:519 participation/models.py:1237
#: participation/models.py:1674 participation/models.py:1908
#: participation/views.py:1489 participation/views.py:1754
#, python-brace-format
msgid "Problem #{problem}"
msgstr "Problème n°{problem}"
@ -814,67 +814,6 @@ msgstr "Pb."
msgid "Room"
msgstr "Salle"
#: draw/templates/draw/tournament_content.html:310
#: draw/templates/draw/tournament_content.html:315
#: draw/templates/draw/tournament_content.html:320
#: draw/templates/draw/tournament_content.html:324
#: draw/templates/draw/tournament_content.html:330
#: draw/templates/draw/tournament_content.html:336
#: draw/templates/draw/tournament_content.html:342
#: draw/templates/draw/tournament_content.html:346
#: draw/templates/draw/tournament_content.html:353
#: draw/templates/draw/tournament_content.html:360
#: draw/templates/draw/tournament_content.html:367
#: draw/templates/draw/tournament_content.html:374
msgctxt "Role abbreviation"
msgid "Rep"
msgstr "Déf"
#: draw/templates/draw/tournament_content.html:311
#: draw/templates/draw/tournament_content.html:316
#: draw/templates/draw/tournament_content.html:318
#: draw/templates/draw/tournament_content.html:326
#: draw/templates/draw/tournament_content.html:332
#: draw/templates/draw/tournament_content.html:334
#: draw/templates/draw/tournament_content.html:340
#: draw/templates/draw/tournament_content.html:348
#: draw/templates/draw/tournament_content.html:355
#: draw/templates/draw/tournament_content.html:362
#: draw/templates/draw/tournament_content.html:364
#: draw/templates/draw/tournament_content.html:371
msgctxt "Role abbreviation"
msgid "Rev"
msgstr "Rap"
#: draw/templates/draw/tournament_content.html:312
#: draw/templates/draw/tournament_content.html:314
#: draw/templates/draw/tournament_content.html:319
#: draw/templates/draw/tournament_content.html:327
#: draw/templates/draw/tournament_content.html:329
#: draw/templates/draw/tournament_content.html:335
#: draw/templates/draw/tournament_content.html:341
#: draw/templates/draw/tournament_content.html:349
#: draw/templates/draw/tournament_content.html:356
#: draw/templates/draw/tournament_content.html:358
#: draw/templates/draw/tournament_content.html:365
#: draw/templates/draw/tournament_content.html:372
msgctxt "Role abbreviation"
msgid "Opp"
msgstr "Opp"
#: draw/templates/draw/tournament_content.html:325
#: draw/templates/draw/tournament_content.html:331
#: draw/templates/draw/tournament_content.html:337
#: draw/templates/draw/tournament_content.html:339
#: draw/templates/draw/tournament_content.html:350
#: draw/templates/draw/tournament_content.html:352
#: draw/templates/draw/tournament_content.html:359
#: draw/templates/draw/tournament_content.html:366
#: draw/templates/draw/tournament_content.html:373
msgctxt "Role abbreviation"
msgid "Obs"
msgstr "Obs"
#: draw/templates/draw/tournament_content.html:395
#: draw/templates/draw/tournament_content.html:414
msgid "Abort"
@ -980,26 +919,26 @@ msgid "selected for final"
msgstr "sélectionnée pour la finale"
#: participation/admin.py:124 participation/admin.py:188
#: participation/models.py:1696 participation/tables.py:114
msgid "reporter"
#: participation/models.py:1681 participation/tables.py:114
msgid "defender"
msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:1703
#: participation/models.py:1974
#: participation/admin.py:128 participation/models.py:1688
#: participation/models.py:1955
msgid "opponent"
msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:1710
#: participation/models.py:1975
#: participation/admin.py:132 participation/models.py:1695
#: participation/models.py:1956
msgid "reviewer"
msgstr "rapporteur⋅rice"
#: participation/admin.py:136 participation/models.py:1717
#: participation/models.py:1976
#: participation/admin.py:136 participation/models.py:1702
#: participation/models.py:1957
msgid "observer"
msgstr "observateur⋅rice"
#: participation/admin.py:192 participation/models.py:1925
#: participation/admin.py:192 participation/models.py:1906
msgid "problem"
msgstr "numéro de problème"
@ -1088,12 +1027,12 @@ msgid "The following user was not found:"
msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :"
#: participation/forms.py:350
msgid "The reporter, the opponent and the reviewer must be different."
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:354
msgid "This reporter did not work on this problem."
msgid "This defender did not work on this problem."
msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème."
#: participation/forms.py:373
@ -1300,7 +1239,7 @@ msgid "first phase date"
msgstr "date du premier tour"
#: participation/models.py:327
msgid "limit date to upload the written reviews for the first phase"
msgid "limit date to upload the syntheses for the first phase"
msgstr "date limite pour envoyer les notes de synthèses pour la première phase"
#: participation/models.py:332
@ -1313,7 +1252,7 @@ msgstr ""
"cocher la case lorsque les solutions pour le second tour sont accessibles"
#: participation/models.py:342
msgid "limit date to upload the written reviews for the second phase"
msgid "limit date to upload the syntheses for the second phase"
msgstr "date limite d'envoi des notes de synthèse pour la seconde phase"
#: participation/models.py:347
@ -1326,7 +1265,7 @@ msgstr ""
"cocher la case lorsque les solutions pour le second tour sont accessibles"
#: participation/models.py:357
msgid "limit date to upload the written reviews for the third phase"
msgid "limit date to upload the syntheses for the third phase"
msgstr ""
"date limite pour envoyer les notes de synthèses pour la troisième phase"
@ -1355,7 +1294,7 @@ msgid "Final ranking"
msgstr "Classement final"
#: participation/models.py:481 participation/models.py:553
#: participation/models.py:1327 participation/views.py:1731
#: participation/models.py:1312 participation/views.py:1728
msgid "Team"
msgstr "Équipe"
@ -1387,15 +1326,15 @@ msgstr "Scores jour 3"
msgid "Tweaks day 3"
msgstr "Ajustements 3"
#: participation/models.py:485 participation/models.py:1327
#: participation/views.py:1738
#: participation/models.py:485 participation/models.py:1312
#: participation/views.py:1735
msgid "Total"
msgstr "Total"
#: participation/models.py:485 participation/models.py:553
#: participation/models.py:1327
#: participation/models.py:1312
#: participation/templates/participation/tournament_harmonize.html:14
#: participation/views.py:1741
#: participation/views.py:1738
msgid "Rank"
msgstr "Rang"
@ -1407,7 +1346,7 @@ msgstr "Score"
msgid "Mention"
msgstr "Mention"
#: participation/models.py:698 participation/models.py:1596
#: participation/models.py:698 participation/models.py:1581
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 "
@ -1509,7 +1448,7 @@ msgstr "Tirage au sort des solutions"
#, python-brace-format
msgid ""
"<p>The solutions draw is ended. You can check the result on <a "
"href='{draw_url}'>this page</a>.</p><p>For the first round, you will present "
"href='{draw_url}'>this page</a>.</p><p>For the first round, you will defend "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>"
msgstr ""
"<p>Le tirage au sort des solutions est terminé. Vous pouvez consulter les "
@ -1517,70 +1456,70 @@ msgstr ""
"tour, vous défendrez <a href='{solution_url}'>votre solution du problème "
"{problem}</a>.</p>"
#: participation/models.py:930 participation/models.py:993
#: participation/models.py:1057
#: participation/models.py:930 participation/models.py:988
#: participation/models.py:1047
#, python-brace-format
msgid ""
"<p>You will oppose the solution of the team {opponent} on the <a "
"href='{solution_url}'>problem {problem}</a>. You can upload your written "
"review on <a href='{passage_url}'>this page</a>.</p>"
"href='{solution_url}'>problem {problem}</a>. You can upload your synthesis "
"sheet on <a href='{passage_url}'>this page</a>.</p>"
msgstr ""
"<p>Vous opposerez la solution de l'équipe {opponent} sur le <a "
"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:939 participation/models.py:1002
#: participation/models.py:1066
#: participation/models.py:939 participation/models.py:997
#: participation/models.py:1056
#, python-brace-format
msgid ""
"<p>You will report the solution of the team {reviewer} on the <a "
"href='{solution_url}'>problem {problem}</a>. You can upload your written "
"review on <a href='{passage_url}'>this page</a>.</p>"
"href='{solution_url}'>problem {problem}. You can upload your synthesis sheet "
"on <a href='{passage_url}'>this page</a>.</p>"
msgstr ""
"<p>Vous rapporterez la solution de l'équipe {reviewer} sur le <a "
"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:949 participation/models.py:1012
#: participation/models.py:1076
#: participation/models.py:949 participation/models.py:1007
#: participation/models.py:1066
#, python-brace-format
msgid ""
"<p>You will observe the solution of the team {observer} on the <a "
"href='{solution_url}'>problem {problem}</a>. You can upload your written "
"review on <a href='{passage_url}'>this page</a>.</p>"
"href='{solution_url}'>problem {problem}. You can upload your synthesis sheet "
"on <a href='{passage_url}'>this page</a>.</p>"
msgstr ""
"<p>Vous observerez la solution de l'équipe {observer} sur le <a "
"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:974 registration/models.py:629
#: participation/models.py:969 registration/models.py:629
msgid "First round"
msgstr "Premier tour"
#: participation/models.py:986
#: participation/models.py:981
#, python-brace-format
msgid ""
"<p>For the second round, you will present <a href='{solution_url}'>your "
"<p>For the second round, you will defend <a href='{solution_url}'>your "
"solution of the problem {problem}</a>.</p>"
msgstr ""
"<p>Pour le second tour, vous défendrez <a href='{solution_url}'>votre "
"solution du problème {problem}</a>.</p>"
#: participation/models.py:1037 participation/models.py:1101
#: participation/models.py:1027 participation/models.py:1086
#: registration/models.py:640
msgid "Second round"
msgstr "Second tour"
#: participation/models.py:1050
#: participation/models.py:1040
#, python-brace-format
msgid ""
"<p>For the third round, you will present <a href='{solution_url}'>your "
"<p>For the third round, you will defend <a href='{solution_url}'>your "
"solution of the problem {problem}</a>.</p>"
msgstr ""
"<p>Pour le troisième tour, vous défendrez <a href='{solution_url}'>votre "
"solution du problème {problem}</a>.</p>"
#: participation/models.py:1107
#: participation/models.py:1092
#, python-brace-format
msgid ""
"<p>The tournament {tournament} is ended. You can check the results on the <a "
@ -1589,57 +1528,57 @@ msgstr ""
"<p>Le tournoi {tournament} est terminé. Vous pouvez consulter les résultats "
"sur la <a href='{url}'>page du tournoi</a>.</p>"
#: participation/models.py:1112
#: participation/models.py:1097
msgid "Tournament ended"
msgstr "Tournoi terminé"
#: participation/models.py:1122 participation/models.py:1165
#: participation/models.py:1107 participation/models.py:1150
msgid "participations"
msgstr "participations"
#: participation/models.py:1137 participation/models.py:1138
#: participation/models.py:1139
#: participation/models.py:1122 participation/models.py:1123
#: participation/models.py:1124
#, python-brace-format
msgid "Round {round}"
msgstr "Tour {round}"
#: participation/models.py:1153
#: participation/models.py:1138
msgid "room"
msgstr "salle"
#: participation/models.py:1155
#: participation/models.py:1140
msgid "Room 1"
msgstr "Salle 1"
#: participation/models.py:1156
#: participation/models.py:1141
msgid "Room 2"
msgstr "Salle 2"
#: participation/models.py:1159
#: participation/models.py:1144
msgid "For 5-teams pools only"
msgstr "Pour les poules de 5 équipe uniquement"
#: participation/models.py:1171
#: participation/models.py:1156
msgid "juries"
msgstr "jurys"
#: participation/models.py:1180
#: participation/models.py:1165
msgid "president of the jury"
msgstr "président⋅e du jury"
#: participation/models.py:1187
#: participation/models.py:1172
msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton"
#: participation/models.py:1188
#: participation/models.py:1173
msgid "The link of the BBB visio for this pool."
msgstr "Le lien du salon BBB pour cette poule."
#: participation/models.py:1193
#: participation/models.py:1178
msgid "results available"
msgstr "résultats disponibles"
#: participation/models.py:1194
#: participation/models.py:1179
msgid ""
"Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given."
@ -1648,194 +1587,194 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées."
#: participation/models.py:1226
#: participation/models.py:1211
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:1253 participation/models.py:1327
#: participation/views.py:1486 participation/views.py:1735
#: participation/models.py:1238 participation/models.py:1312
#: participation/views.py:1483 participation/views.py:1732
msgid "Problem"
msgstr "Problème"
#: participation/models.py:1258 participation/views.py:1501
#: participation/models.py:1243 participation/views.py:1498
msgid "Role"
msgstr "Rôle"
#: participation/models.py:1263 participation/views.py:1535
#: participation/views.py:1536
#: participation/models.py:1248 participation/views.py:1532
#: participation/views.py:1533
msgid "Juree"
msgstr "Juré⋅e"
#: participation/models.py:1286 participation/models.py:1612
#: participation/models.py:1634 participation/views.py:1605
#: participation/models.py:1271 participation/models.py:1597
#: participation/models.py:1619 participation/views.py:1602
msgid "Average"
msgstr "Moyenne"
#: participation/models.py:1292 participation/views.py:1624
#: participation/models.py:1277 participation/views.py:1621
msgid "Coefficient"
msgstr "Coefficien"
#: participation/models.py:1293 participation/views.py:1667
#: participation/models.py:1278 participation/views.py:1664
msgid "Subtotal"
msgstr "Sous-total"
#: participation/models.py:1559
#: participation/models.py:1544
#, 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:1647
#: participation/models.py:1632
#, 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:1660
#: participation/models.py:1645
#, 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:1680
#: participation/models.py:1665
msgid "position"
msgstr "position"
#: participation/models.py:1687
msgid "reported solution"
#: participation/models.py:1672
msgid "defended solution"
msgstr "solution défendue"
#: participation/models.py:1725
#: participation/models.py:1710
msgid "penalties"
msgstr "pénalités"
#: participation/models.py:1727
#: participation/models.py:1712
msgid ""
"Number of penalties for the reporter. The reporter will loose a 0.5 "
"Number of penalties for the defender. The defender will loose a 0.5 "
"coefficient per penalty."
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:1853 participation/models.py:1856
#: participation/models.py:1859 participation/models.py:1862
#: participation/models.py:1838 participation/models.py:1841
#: participation/models.py:1844 participation/models.py:1847
#, 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:1867
#: participation/models.py:1852
#, python-brace-format
msgid "Passage of {reporter} for problem {problem}"
msgstr "Passage de {reporter} pour le problème {problem}"
msgid "Passage of {defender} for problem {problem}"
msgstr "Passage de {defender} pour le problème {problem}"
#: participation/models.py:1871 participation/models.py:1880
#: participation/models.py:1969 participation/models.py:2012
#: participation/models.py:1856 participation/models.py:1865
#: participation/models.py:1950 participation/models.py:1993
msgid "passage"
msgstr "passage"
#: participation/models.py:1872
#: participation/models.py:1857
msgid "passages"
msgstr "passages"
#: participation/models.py:1891
#: participation/models.py:1876
msgid "difference"
msgstr "différence"
#: participation/models.py:1892
#: participation/models.py:1877
msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final"
#: participation/models.py:1899
#: participation/models.py:1884
msgid "tweak"
msgstr "harmonisation"
#: participation/models.py:1900
#: participation/models.py:1885
msgid "tweaks"
msgstr "harmonisations"
#: participation/models.py:1932
#: participation/models.py:1913
msgid "solution for the final tournament"
msgstr "solution pour la finale"
#: participation/models.py:1937 participation/models.py:1981
#: participation/models.py:1918 participation/models.py:1962
msgid "file"
msgstr "fichier"
#: participation/models.py:1947
#: participation/models.py:1928
#, 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:1949
#: participation/models.py:1930
msgid "for final"
msgstr "pour la finale"
#: participation/models.py:1952
#: participation/models.py:1933
msgid "solution"
msgstr "solution"
#: participation/models.py:1953
#: participation/models.py:1934
msgid "solutions"
msgstr "solutions"
#: participation/models.py:1987
#: participation/models.py:1968
#, python-brace-format
msgid "Written review of {team} as {type} for problem {problem} of {reporter}"
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 {reporter}"
"{problem} de {defender}"
#: participation/models.py:1995
msgid "written review"
#: participation/models.py:1976
msgid "synthesis"
msgstr "note de synthèse"
#: participation/models.py:1996
msgid "written reviews"
#: participation/models.py:1977
msgid "syntheses"
msgstr "notes de synthèse"
#: participation/models.py:2005
#: participation/models.py:1986
msgid "jury"
msgstr "jury"
#: participation/models.py:2017
msgid "reporter writing note"
#: participation/models.py:1998
msgid "defender writing note"
msgstr "note d'écrit défenseur⋅se"
#: participation/models.py:2023
msgid "reporter oral note"
#: participation/models.py:2004
msgid "defender oral note"
msgstr "note d'oral défenseur⋅se"
#: participation/models.py:2029
#: participation/models.py:2010
msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e"
#: participation/models.py:2035
#: participation/models.py:2016
msgid "opponent oral note"
msgstr "note d'oral opposant⋅e"
#: participation/models.py:2041
#: participation/models.py:2022
msgid "reviewer writing note"
msgstr "note d'écrit rapporteur⋅rice"
#: participation/models.py:2047
#: participation/models.py:2028
msgid "reviewer oral note"
msgstr "note d'oral du rapporteur⋅rice"
#: participation/models.py:2053
#: participation/models.py:2034
msgid "observer writing note"
msgstr "note d'écrit de l'observateur⋅rice"
#: participation/models.py:2059
#: participation/models.py:2040
msgid "observer oral note"
msgstr "note d'oral de l'observateur⋅rice"
#: participation/models.py:2124
#: participation/models.py:2105
#, python-brace-format
msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}"
#: participation/models.py:2127
#: participation/models.py:2108
msgid "note"
msgstr "note"
#: participation/models.py:2128
#: participation/models.py:2109
msgid "notes"
msgstr "notes"
@ -1971,7 +1910,7 @@ msgstr "Envoyer une solution"
#: participation/templates/participation/upload_motivation_letter.html:13
#: participation/templates/participation/upload_notes.html:24
#: participation/templates/participation/upload_solution.html:11
#: participation/templates/participation/upload_written_review.html:23
#: participation/templates/participation/upload_synthesis.html:18
#: registration/templates/registration/upload_health_sheet.html:17
#: registration/templates/registration/upload_parental_authorization.html:17
#: registration/templates/registration/upload_photo_authorization.html:18
@ -1994,7 +1933,7 @@ msgid "Position:"
msgstr "Position :"
#: participation/templates/participation/passage_detail.html:28
msgid "Reporter:"
msgid "Defender:"
msgstr "Défenseur⋅se :"
#: participation/templates/participation/passage_detail.html:31
@ -2010,11 +1949,11 @@ msgid "Observer:"
msgstr "Observateur⋅rice :"
#: participation/templates/participation/passage_detail.html:42
msgid "Reported solution:"
msgid "Defended solution:"
msgstr "Solution défendue"
#: participation/templates/participation/passage_detail.html:45
msgid "Reporter penalties count:"
msgid "Defender penalties count:"
msgstr "Nombre de pénalités :"
#: participation/templates/participation/passage_detail.html:48
@ -2024,7 +1963,7 @@ msgstr "Notes de synthèse :"
#: participation/templates/participation/passage_detail.html:53
#: participation/templates/participation/pool_detail.html:68
msgid "No review was uploaded yet."
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
@ -2034,19 +1973,19 @@ msgstr "Modifier les notes"
#: participation/templates/participation/passage_detail.html:66
#: participation/templates/participation/passage_detail.html:187
msgid "Upload review"
msgstr "Envoyer la note de synthèse"
msgid "Upload synthesis"
msgstr "Envoyer une note de synthèse"
#: participation/templates/participation/passage_detail.html:74
msgid "Notes detail"
msgstr "Détails des notes"
#: participation/templates/participation/passage_detail.html:82
msgid "Average points for the reporter writing"
msgid "Average points for the defender writing"
msgstr "Moyenne de l'écrit de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:90
msgid "Average points for the reporter oral"
msgid "Average points for the defender oral"
msgstr "Moyenne de l'oral de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:98
@ -2074,7 +2013,7 @@ msgid "Average points for the observer oral"
msgstr "Moyenne de l'oral de l'équipe observatrice"
#: participation/templates/participation/passage_detail.html:140
msgid "Reporter points"
msgid "Defender points"
msgstr "Points de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:148
@ -2119,7 +2058,7 @@ msgid "Edit jury"
msgstr "Modifier le jury"
#: participation/templates/participation/pool_detail.html:49
msgid "Reported solutions:"
msgid "Defended solutions:"
msgstr "Solutions défendues :"
#: participation/templates/participation/pool_detail.html:55
@ -2435,15 +2374,15 @@ msgid "date of the random draw"
msgstr "date du tirage au sort"
#: participation/templates/participation/tournament_detail.html:41
msgid "date of maximal written reviews submission for the first round"
msgid "date of maximal syntheses submission for the first round"
msgstr "date limite de soumission des notes de synthèse pour le premier tour"
#: participation/templates/participation/tournament_detail.html:44
msgid "date of maximal written reviews submission for the second round"
msgid "date of maximal syntheses submission for the second round"
msgstr "date limite de soumission des notes de synthèse pour le second tour"
#: participation/templates/participation/tournament_detail.html:48
msgid "date of maximal written reviews submission for the third round"
msgid "date of maximal syntheses submission for the third round"
msgstr "date limite de soumission des notes de synthèse pour le troisième tour"
#: participation/templates/participation/tournament_detail.html:56
@ -2523,42 +2462,6 @@ msgstr "Dépublier les notes pour le troisième tour"
msgid "Files available for download"
msgstr "Fichiers disponibles au téléchargement"
#: participation/templates/participation/tournament_detail.html:236
msgid "Validated team participant data spreadsheet"
msgstr "Tableur des données des équipes validées"
#: participation/templates/participation/tournament_detail.html:241
msgid "All teams participant data spreadsheet"
msgstr "Tableur des données de toutes les équipes"
#: participation/templates/participation/tournament_detail.html:246
msgid "Archive of all authorisations sorted by team and person"
msgstr "Archive de toutes les autorisations triées par équipe et personne"
#: participation/templates/participation/tournament_detail.html:251
msgid "Archive of all submitted solutions sorted by team"
msgstr "Archive de toutes les solutions envoyées triées par équipe"
#: participation/templates/participation/tournament_detail.html:256
msgid "Archive of all sent solutions sorted by problem"
msgstr "Archive de toutes les solutions envoyées triées par problème"
#: participation/templates/participation/tournament_detail.html:261
msgid "Archive of all sent solutions sorted by pool"
msgstr "Archive de toutes les solutions envoyées triées par poule"
#: participation/templates/participation/tournament_detail.html:266
msgid "Archive of all summary notes sorted by pool and passage"
msgstr "Archive de toutes les notes de synthèse triées par poule et passage"
#: participation/templates/participation/tournament_detail.html:272
msgid "Note spreadsheet on Google Sheets"
msgstr "Tableur de notes sur Google Sheets"
#: participation/templates/participation/tournament_detail.html:277
msgid "Archive of all printable note sheets sorted by pool"
msgstr "Archive de toutes les fiches de notes imprimables triées par poule"
#: participation/templates/participation/tournament_harmonize.html:16
#: registration/models.py:655
msgid "Note"
@ -2601,11 +2504,11 @@ msgstr ""
msgid "Download empty notation sheet"
msgstr "Télécharger la fiche de notation vierge"
#: participation/templates/participation/upload_written_review.html:9
#: participation/templates/participation/upload_synthesis.html:9
msgid "Templates:"
msgstr "Modèles :"
#: participation/templates/participation/upload_written_review.html:14
#: participation/templates/participation/upload_synthesis.html:13
msgid "Warning: non-free format"
msgstr "Attention : format non libre"
@ -2753,96 +2656,96 @@ msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
msgid "Solutions of team {trigram}.zip"
msgstr "Solutions de l'équipe {trigram}.zip"
#: participation/views.py:1023
#: participation/views.py:1022
#, python-brace-format
msgid "Written reviews of team {trigram}.zip"
msgid "Syntheses of team {trigram}.zip"
msgstr "Notes de synthèse de l'équipe {trigram}.zip"
#: participation/views.py:1040 participation/views.py:1056
#: participation/views.py:1039 participation/views.py:1054
#, python-brace-format
msgid "Solutions of {tournament}.zip"
msgstr "Solutions de {tournament}.zip"
#: participation/views.py:1041 participation/views.py:1057
#: participation/views.py:1039 participation/views.py:1054
#, python-brace-format
msgid "Written reviews of {tournament}.zip"
msgid "Syntheses of {tournament}.zip"
msgstr "Notes de synthèse de {tournament}.zip"
#: participation/views.py:1066
#: participation/views.py:1063
#, 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:1067
#: participation/views.py:1064
#, python-brace-format
msgid "Written reviews for pool {pool} of tournament {tournament}.zip"
msgid "Syntheses for pool {pool} of tournament {tournament}.zip"
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:1109
#: participation/views.py:1106
#, 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:1125
#: participation/views.py:1122
#, python-brace-format
msgid "The jury {name} is already in the pool!"
msgstr "{name} est déjà dans la poule !"
#: participation/views.py:1145
#: participation/views.py:1142
msgid "New jury account"
msgstr "Nouveau compte de juré⋅e"
#: participation/views.py:1166
#: participation/views.py:1163
#, 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:1202
#: participation/views.py:1199
#, 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:1228
#: participation/views.py:1225
#, 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:1256
#: participation/views.py:1253
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:1272
#: participation/views.py:1269
msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées."
#: participation/views.py:1507
msgid "Reporter"
#: participation/views.py:1504
msgid "Defender"
msgstr "Défenseur⋅se"
#: participation/views.py:1513
#: participation/views.py:1510
msgid "Opponent"
msgstr "Opposant⋅e"
#: participation/views.py:1520
#: participation/views.py:1517
msgid "Reviewer"
msgstr "Rapporteur⋅rice"
#: participation/views.py:1527
#: participation/views.py:1524
msgid "Observer"
msgstr "Observateur⋅rice"
#: participation/views.py:1898
#: participation/views.py:1895
#, 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:1903
#: participation/views.py:1900
#, python-brace-format
msgid "Notation sheets of {tournament}.zip"
msgstr "Feuilles de notation de {tournament}.zip"
#: participation/views.py:2070
msgid "You can't upload a written review after the deadline."
#: participation/views.py:2067
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."
#: registration/admin.py:53 registration/admin.py:69 registration/admin.py:85

View File

@ -4,7 +4,7 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
class ParticipationInline(admin.StackedInline):
@ -32,8 +32,8 @@ class SolutionInline(admin.TabularInline):
show_change_link = True
class WrittenReviewInline(admin.TabularInline):
model = WrittenReview
class SynthesisInline(admin.TabularInline):
model = Synthesis
extra = 0
ordering = ('passage__solution_number', 'type',)
autocomplete_fields = ('passage',)
@ -51,7 +51,7 @@ class PassageInline(admin.TabularInline):
model = Passage
extra = 0
ordering = ('position',)
autocomplete_fields = ('reporter', 'opponent', 'reviewer', 'observer',)
autocomplete_fields = ('defender', 'opponent', 'reviewer', 'observer',)
show_change_link = True
@ -95,7 +95,7 @@ class ParticipationAdmin(admin.ModelAdmin):
search_fields = ('team__name', 'team__trigram',)
list_filter = ('valid', 'tournament',)
autocomplete_fields = ('team', 'tournament',)
inlines = (SolutionInline, WrittenReviewInline,)
inlines = (SolutionInline, SynthesisInline,)
@admin.register(Pool)
@ -113,17 +113,17 @@ class PoolAdmin(admin.ModelAdmin):
@admin.register(Passage)
class PassageAdmin(admin.ModelAdmin):
list_display = ('__str__', 'reporter_trigram', 'solution_number', 'opponent_trigram', 'reviewer_trigram',
list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reviewer_trigram',
'observer_trigram', 'pool_abbr', 'position', 'tournament')
list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',)
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',)
autocomplete_fields = ('pool', 'reporter', 'opponent', 'reviewer', 'observer',)
autocomplete_fields = ('pool', 'defender', 'opponent', 'reviewer', 'observer',)
inlines = (NoteInline,)
@admin.display(description=_("reporter"), ordering='reporter__team__trigram')
def reporter_trigram(self, record: Passage):
return record.reporter.team.trigram
@admin.display(description=_("defender"), ordering='defender__team__trigram')
def defender_trigram(self, record: Passage):
return record.defender.team.trigram
@admin.display(description=_("opponent"), ordering='opponent__team__trigram')
def opponent_trigram(self, record: Passage):
@ -148,13 +148,13 @@ class PassageAdmin(admin.ModelAdmin):
@admin.register(Note)
class NoteAdmin(admin.ModelAdmin):
list_display = ('passage', 'pool', 'jury', 'reporter_writing', 'reporter_oral',
list_display = ('passage', 'pool', 'jury', 'defender_writing', 'defender_oral',
'opponent_writing', 'opponent_oral', 'reviewer_writing', 'reviewer_oral',
'observer_writing', 'observer_oral',)
list_filter = ('passage__pool__letter', 'passage__solution_number', 'jury',
'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral')
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__reporter__team__trigram',)
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__defender__team__trigram',)
autocomplete_fields = ('jury', 'passage',)
@admin.display(description=_("pool"))
@ -178,19 +178,19 @@ class SolutionAdmin(admin.ModelAdmin):
return Tournament.final_tournament() if record.final_solution else record.participation.tournament
@admin.register(WrittenReview)
class WrittenReviewAdmin(admin.ModelAdmin):
list_display = ('participation', 'type', 'reporter', 'passage',)
@admin.register(Synthesis)
class SynthesisAdmin(admin.ModelAdmin):
list_display = ('participation', 'type', 'defender', 'passage',)
list_filter = ('participation__tournament', 'type', 'passage__solution_number',)
search_fields = ('participation__team__name', 'participation__team__trigram',)
autocomplete_fields = ('participation', 'passage',)
@admin.display(description=_("reporter"))
def reporter(self, record: WrittenReview):
return record.passage.reporter
@admin.display(description=_("defender"))
def defender(self, record: Synthesis):
return record.passage.defender
@admin.display(description=_("problem"))
def problem(self, record: WrittenReview):
def problem(self, record: Synthesis):
return record.passage.solution_number

View File

@ -3,7 +3,7 @@
from rest_framework import serializers
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
class NoteSerializer(serializers.ModelSerializer):
@ -38,9 +38,9 @@ class SolutionSerializer(serializers.ModelSerializer):
fields = '__all__'
class WrittenReviewSerializer(serializers.ModelSerializer):
class SynthesisSerializer(serializers.ModelSerializer):
class Meta:
model = WrittenReview
model = Synthesis
fields = '__all__'
@ -58,9 +58,9 @@ class TournamentSerializer(serializers.ModelSerializer):
class Meta:
model = Tournament
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
'inscription_limit', 'solution_limit', 'solutions_draw', 'reviews_first_phase_limit',
'solutions_available_second_phase', 'reviews_second_phase_limit',
'solutions_available_third_phase', 'reviews_third_phase_limit',
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'description', 'organizers', 'final', 'participations',)

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from .views import NoteViewSet, ParticipationViewSet, PassageViewSet, PoolViewSet, \
SolutionViewSet, TeamViewSet, TournamentViewSet, TweakViewSet, WrittenReviewViewSet
SolutionViewSet, SynthesisViewSet, TeamViewSet, TournamentViewSet, TweakViewSet
def register_participation_urls(router, path):
@ -13,8 +13,8 @@ def register_participation_urls(router, path):
router.register(path + "/participation", ParticipationViewSet)
router.register(path + "/passage", PassageViewSet)
router.register(path + "/pool", PoolViewSet)
router.register(path + "/review", WrittenReviewViewSet)
router.register(path + "/solution", SolutionViewSet)
router.register(path + "/synthesis", SynthesisViewSet)
router.register(path + "/team", TeamViewSet)
router.register(path + "/tournament", TournamentViewSet)
router.register(path + "/tweak", TweakViewSet)

View File

@ -4,15 +4,15 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.viewsets import ModelViewSet
from .serializers import NoteSerializer, ParticipationSerializer, PassageSerializer, PoolSerializer, \
SolutionSerializer, TeamSerializer, TournamentSerializer, TweakSerializer, WrittenReviewSerializer
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
SolutionSerializer, SynthesisSerializer, TeamSerializer, TournamentSerializer, TweakSerializer
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
class NoteViewSet(ModelViewSet):
queryset = Note.objects.all()
serializer_class = NoteSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['jury', 'passage', 'reporter_writing', 'reporter_oral', 'opponent_writing',
filterset_fields = ['jury', 'passage', 'defender_writing', 'defender_oral', 'opponent_writing',
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', ]
@ -27,7 +27,7 @@ class PassageViewSet(ModelViewSet):
queryset = Passage.objects.all()
serializer_class = PassageSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['pool', 'solution_number', 'reporter', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
filterset_fields = ['pool', 'solution_number', 'defender', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
class PoolViewSet(ModelViewSet):
@ -44,9 +44,9 @@ class SolutionViewSet(ModelViewSet):
filterset_fields = ['participation', 'number', 'problem', 'final_solution', ]
class WrittenReviewViewSet(ModelViewSet):
queryset = WrittenReview.objects.all()
serializer_class = WrittenReviewSerializer
class SynthesisViewSet(ModelViewSet):
queryset = Synthesis.objects.all()
serializer_class = SynthesisSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['participation', 'number', 'passage', 'type', ]
@ -64,9 +64,9 @@ class TournamentViewSet(ModelViewSet):
serializer_class = TournamentSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
'inscription_limit', 'solution_limit', 'solutions_draw', 'reviews_first_phase_limit',
'solutions_available_second_phase', 'reviews_second_phase_limit',
'solutions_available_third_phase', 'reviews_third_phase_limit',
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'description', 'organizers', 'final', ]

View File

@ -16,7 +16,7 @@ from pypdf import PdfReader
from registration.models import VolunteerRegistration
from tfjm import settings
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
class TeamForm(forms.ModelForm):
@ -137,7 +137,7 @@ class TournamentForm(forms.ModelForm):
if settings.NB_ROUNDS < 3:
del self.fields['date_third_phase']
del self.fields['solutions_available_third_phase']
del self.fields['reviews_third_phase_limit']
del self.fields['syntheses_third_phase_limit']
if not settings.PAYMENT_MANAGEMENT:
del self.fields['price']
@ -151,14 +151,14 @@ class TournamentForm(forms.ModelForm):
'solution_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'date_first_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
'reviews_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'date_second_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
'reviews_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'date_third_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
'reviews_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'organizers': forms.SelectMultiple(attrs={
'class': 'selectpicker',
'data-live-search': 'true',
@ -345,21 +345,21 @@ class UploadNotesForm(forms.Form):
class PassageForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if "reporter" in cleaned_data and "opponent" in cleaned_data and "reviewer" in cleaned_data \
and len({cleaned_data["reporter"], cleaned_data["opponent"], cleaned_data["reviewer"]}) < 3:
self.add_error(None, _("The reporter, the opponent and the reviewer must be different."))
if "reporter" in self.cleaned_data and "solution_number" in self.cleaned_data \
and not Solution.objects.filter(participation=cleaned_data["reporter"],
if "defender" in cleaned_data and "opponent" in cleaned_data and "reviewer" in cleaned_data \
and len({cleaned_data["defender"], cleaned_data["opponent"], cleaned_data["reviewer"]}) < 3:
self.add_error(None, _("The defender, the opponent and the reviewer must be different."))
if "defender" in self.cleaned_data and "solution_number" in self.cleaned_data \
and not Solution.objects.filter(participation=cleaned_data["defender"],
problem=cleaned_data["solution_number"]).exists():
self.add_error("solution_number", _("This reporter did not work on this problem."))
self.add_error("solution_number", _("This defender did not work on this problem."))
return cleaned_data
class Meta:
model = Passage
fields = ('position', 'solution_number', 'reporter', 'opponent', 'reviewer', 'opponent', 'reporter_penalties',)
fields = ('position', 'solution_number', 'defender', 'opponent', 'reviewer', 'opponent', 'defender_penalties',)
class WrittenReviewForm(forms.ModelForm):
class SynthesisForm(forms.ModelForm):
def clean_file(self):
if "file" in self.files:
file = self.files["file"]
@ -375,16 +375,16 @@ class WrittenReviewForm(forms.ModelForm):
def save(self, commit=True):
"""
Don't save a written review with this way. Use a view instead
Don't save a synthesis with this way. Use a view instead
"""
class Meta:
model = WrittenReview
model = Synthesis
fields = ('file',)
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ('reporter_writing', 'reporter_oral', 'opponent_writing',
fields = ('defender_writing', 'defender_oral', 'opponent_writing',
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', )

View File

@ -51,23 +51,23 @@ class Command(BaseCommand):
team3, score3 = sorted_notes[2]
pool1 = tournament.pools.filter(round=1, participations=team2).first()
reporter_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reporter=team2)
defender_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, defender=team2)
opponent_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, opponent=team2)
reviewer_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reviewer=team2)
pool2 = tournament.pools.filter(round=2, participations=team2).first()
reporter_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reporter=team2)
defender_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, defender=team2)
opponent_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, opponent=team2)
reviewer_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reviewer=team2)
line.append(team2.team.trigram)
line.append(str(pool1.jury_president or ""))
line.append(f"Pb. {reporter_passage_1.solution_number}")
line.extend([reporter_passage_1.average_reporter_writing, reporter_passage_1.average_reporter_oral,
line.append(f"Pb. {defender_passage_1.solution_number}")
line.extend([defender_passage_1.average_defender_writing, defender_passage_1.average_defender_oral,
opponent_passage_1.average_opponent_writing, opponent_passage_1.average_opponent_oral,
reviewer_passage_1.average_reviewer_writing, reviewer_passage_1.average_reviewer_oral])
line.append(str(pool2.jury_president or ""))
line.append(f"Pb. {reporter_passage_2.solution_number}")
line.extend([reporter_passage_2.average_reporter_writing, reporter_passage_2.average_reporter_oral,
line.append(f"Pb. {defender_passage_2.solution_number}")
line.extend([defender_passage_2.average_defender_writing, defender_passage_2.average_defender_oral,
opponent_passage_2.average_opponent_writing, opponent_passage_2.average_opponent_oral,
reviewer_passage_2.average_reviewer_writing, reviewer_passage_2.average_reviewer_oral])
line.extend([score2, f"{score1:.1f} ({team1.team.trigram})",

View File

@ -1,75 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-06 19:19
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("participation", "0019_note_observer_oral_note_observer_writing_and_more"),
]
operations = [
migrations.RenameModel(
old_name="Synthesis",
new_name="WrittenReview",
),
migrations.AlterModelOptions(
name="writtenreview",
options={
"ordering": ("passage__pool__round", "type"),
"verbose_name": "written review",
"verbose_name_plural": "written reviews",
},
),
migrations.RenameField(
model_name="tournament",
old_name="syntheses_first_phase_limit",
new_name="reviews_first_phase_limit",
),
migrations.RenameField(
model_name="tournament",
old_name="syntheses_second_phase_limit",
new_name="reviews_second_phase_limit",
),
migrations.RenameField(
model_name="tournament",
old_name="syntheses_third_phase_limit",
new_name="reviews_third_phase_limit",
),
migrations.AlterField(
model_name="tournament",
name="reviews_first_phase_limit",
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name="limit date to upload the written reviews for the first phase",
),
),
migrations.AlterField(
model_name="tournament",
name="reviews_second_phase_limit",
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name="limit date to upload the written reviews for the second phase",
),
),
migrations.AlterField(
model_name="tournament",
name="reviews_third_phase_limit",
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name="limit date to upload the written reviews for the third phase",
),
),
migrations.AlterField(
model_name="writtenreview",
name="passage",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="written_reviews",
to="participation.passage",
verbose_name="passage",
),
),
]

View File

@ -1,133 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-06 20:00
import django
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("participation", "0020_rename_synthesis_writtenreview_and_more"),
]
operations = [
migrations.RenameField(
model_name="note",
old_name="defender_oral",
new_name="reporter_oral",
),
migrations.RenameField(
model_name="note",
old_name="defender_writing",
new_name="reporter_writing",
),
migrations.RenameField(
model_name="passage",
old_name="defender",
new_name="reporter",
),
migrations.RenameField(
model_name="passage",
old_name="defender_penalties",
new_name="reporter_penalties",
),
migrations.AlterField(
model_name="passage",
name="solution_number",
field=models.PositiveSmallIntegerField(
choices=[
(1, "Problem #1"),
(2, "Problem #2"),
(3, "Problem #3"),
(4, "Problem #4"),
(5, "Problem #5"),
(6, "Problem #6"),
(7, "Problem #7"),
(8, "Problem #8"),
(9, "Problem #9"),
(10, "Problem #10"),
],
verbose_name="reported solution",
),
),
migrations.AlterField(
model_name="note",
name="reporter_oral",
field=models.PositiveSmallIntegerField(
choices=[
(0, 0),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8),
(9, 9),
(10, 10),
(11, 11),
(12, 12),
(13, 13),
(14, 14),
(15, 15),
(16, 16),
(17, 17),
(18, 18),
(19, 19),
(20, 20),
],
default=0,
verbose_name="reporter oral note",
),
),
migrations.AlterField(
model_name="note",
name="reporter_writing",
field=models.PositiveSmallIntegerField(
choices=[
(0, 0),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8),
(9, 9),
(10, 10),
(11, 11),
(12, 12),
(13, 13),
(14, 14),
(15, 15),
(16, 16),
(17, 17),
(18, 18),
(19, 19),
(20, 20),
],
default=0,
verbose_name="reporter writing note",
),
),
migrations.AlterField(
model_name="passage",
name="reporter",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="participation.participation",
verbose_name="reporter",
),
),
migrations.AlterField(
model_name="passage",
name="reporter_penalties",
field=models.PositiveSmallIntegerField(
default=0,
help_text="Number of penalties for the reporter. The reporter will loose a 0.5 coefficient per penalty.",
verbose_name="penalties",
),
),
]

View File

@ -323,8 +323,8 @@ class Tournament(models.Model):
default=date.today,
)
reviews_first_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the written reviews for the first phase"),
syntheses_first_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the first phase"),
default=timezone.now,
)
@ -338,8 +338,8 @@ class Tournament(models.Model):
default=False,
)
reviews_second_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the written reviews for the second phase"),
syntheses_second_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the second phase"),
default=timezone.now,
)
@ -353,8 +353,8 @@ class Tournament(models.Model):
default=False,
)
reviews_third_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the written reviews for the third phase"),
syntheses_third_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the third phase"),
default=timezone.now,
)
@ -442,10 +442,10 @@ class Tournament(models.Model):
return Solution.objects.filter(participation__tournament=self)
@property
def written_reviews(self):
def syntheses(self):
if self.final:
return WrittenReview.objects.filter(final_solution=True)
return WrittenReview.objects.filter(participation__tournament=self)
return Synthesis.objects.filter(final_solution=True)
return Synthesis.objects.filter(participation__tournament=self)
@property
def best_format(self):
@ -490,7 +490,7 @@ class Tournament(models.Model):
line = [f"{participation.team.name} ({participation.team.trigram})"]
lines.append(line)
passage1 = Passage.objects.get(pool__tournament=self, pool__round=1, reporter=participation)
passage1 = Passage.objects.get(pool__tournament=self, pool__round=1, defender=participation)
pool1 = passage1.pool
if pool1.participations.count() != 5:
position1 = passage1.position
@ -502,8 +502,8 @@ class Tournament(models.Model):
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, reporter=participation).exists():
passage2 = Passage.objects.get(pool__tournament=self, pool__round=2, reporter=participation)
if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists():
passage2 = Passage.objects.get(pool__tournament=self, pool__round=2, defender=participation)
pool2 = passage2.pool
if pool2.participations.count() != 5:
position2 = passage2.position
@ -519,8 +519,8 @@ class Tournament(models.Model):
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, reporter=participation).exists():
passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, reporter=participation)
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
@ -911,128 +911,128 @@ class Participation(models.Model):
'priority': 1,
'content': content,
})
elif timezone.now() <= tournament.reviews_first_phase_limit + timedelta(hours=2):
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reporter=self)
elif timezone.now() <= tournament.syntheses_first_phase_limit + timedelta(hours=2):
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, defender=self)
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, opponent=self)
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reviewer=self)
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=1, observer=self)
observer_passage = observer_passage.get() if observer_passage.exists() else None
reporter_text = _("<p>The solutions draw is ended. You can check the result on "
defender_text = _("<p>The solutions draw is ended. You can check the result on "
"<a href='{draw_url}'>this page</a>.</p>"
"<p>For the first round, you will present "
"<p>For the first round, you will defend "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
solution_url = reporter_passage.reported_solution.file.url
reporter_content = format_lazy(reporter_text, draw_url=draw_url,
solution_url=solution_url, problem=reporter_passage.solution_number)
solution_url = defender_passage.defended_solution.file.url
defender_content = format_lazy(defender_text, draw_url=draw_url,
solution_url=solution_url, problem=defender_passage.solution_number)
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.team.trigram,
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
solution_url=solution_url,
problem=opponent_passage.solution_number, passage_url=passage_url)
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.team.trigram,
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
solution_url=solution_url,
problem=reviewer_passage.solution_number, passage_url=passage_url)
if observer_passage:
observer_text = _("<p>You will observe the solution of the team {observer} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
observer_content = format_lazy(observer_text,
observer=observer_passage.reporter.team.trigram,
observer=observer_passage.defender.team.trigram,
solution_url=solution_url,
problem=observer_passage.solution_number, passage_url=passage_url)
else:
observer_content = ""
if settings.TFJM_APP == "TFJM":
reviews_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
reviews_templates = "".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
syntheses_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
else:
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
reviews_templates = "".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex"])
reviews_templates_content = f"<p>{_('Templates:')} {reviews_templates}</p>"
syntheses_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
content = reporter_content + opponent_content + reviewer_content + observer_content \
+ reviews_templates_content
content = defender_content + opponent_content + reviewer_content + observer_content \
+ syntheses_templates_content
informations.append({
'title': _("First round"),
'type': "info",
'priority': 1,
'content': content,
})
elif timezone.now() <= tournament.reviews_second_phase_limit + timedelta(hours=2):
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reporter=self)
elif timezone.now() <= tournament.syntheses_second_phase_limit + timedelta(hours=2):
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, defender=self)
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, opponent=self)
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reviewer=self)
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=2, observer=self)
observer_passage = observer_passage.get() if observer_passage.exists() else None
reporter_text = _("<p>For the second round, you will present "
defender_text = _("<p>For the second round, you will defend "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
solution_url = reporter_passage.reported_solution.file.url
reporter_content = format_lazy(reporter_text, draw_url=draw_url,
solution_url=solution_url, problem=reporter_passage.solution_number)
solution_url = defender_passage.defended_solution.file.url
defender_content = format_lazy(defender_text, draw_url=draw_url,
solution_url=solution_url, problem=defender_passage.solution_number)
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.team.trigram,
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
solution_url=solution_url,
problem=opponent_passage.solution_number, passage_url=passage_url)
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.team.trigram,
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
solution_url=solution_url,
problem=reviewer_passage.solution_number, passage_url=passage_url)
if observer_passage:
observer_text = _("<p>You will observe the solution of the team {observer} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
observer_content = format_lazy(observer_text,
observer=observer_passage.reporter.team.trigram,
observer=observer_passage.defender.team.trigram,
solution_url=solution_url,
problem=observer_passage.solution_number, passage_url=passage_url)
else:
observer_content = ""
if settings.TFJM_APP == "TFJM":
reviews_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
reviews_templates = "".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
syntheses_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
else:
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
reviews_templates = "".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex"])
reviews_templates_content = f"<p>{_('Templates:')} {reviews_templates}</p>"
syntheses_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
content = reporter_content + opponent_content + reviewer_content + observer_content \
+ reviews_templates_content
content = defender_content + opponent_content + reviewer_content + observer_content \
+ syntheses_templates_content
informations.append({
'title': _("Second round"),
'type': "info",
@ -1040,63 +1040,63 @@ class Participation(models.Model):
'content': content,
})
elif settings.TFJM_APP == "ETEAM" \
and timezone.now() <= tournament.reviews_third_phase_limit + timedelta(hours=2):
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reporter=self)
and timezone.now() <= tournament.syntheses_third_phase_limit + timedelta(hours=2):
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, defender=self)
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, opponent=self)
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reviewer=self)
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=3, observer=self)
observer_passage = observer_passage.get() if observer_passage.exists() else None
reporter_text = _("<p>For the third round, you will present "
defender_text = _("<p>For the third round, you will defend "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
solution_url = reporter_passage.reported_solution.file.url
reporter_content = format_lazy(reporter_text, draw_url=draw_url,
solution_url=solution_url, problem=reporter_passage.solution_number)
solution_url = defender_passage.defended_solution.file.url
defender_content = format_lazy(defender_text, draw_url=draw_url,
solution_url=solution_url, problem=defender_passage.solution_number)
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.team.trigram,
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
solution_url=solution_url,
problem=opponent_passage.solution_number, passage_url=passage_url)
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.team.trigram,
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
solution_url=solution_url,
problem=reviewer_passage.solution_number, passage_url=passage_url)
if observer_passage:
observer_text = _("<p>You will observe the solution of the team {observer} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.reported_solution.file.url
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
observer_content = format_lazy(observer_text,
observer=observer_passage.reporter.team.trigram,
observer=observer_passage.defender.team.trigram,
solution_url=solution_url,
problem=observer_passage.solution_number, passage_url=passage_url)
else:
observer_content = ""
if settings.TFJM_APP == "TFJM":
reviews_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
reviews_templates = "".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
syntheses_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
else:
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
reviews_templates = "".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex"])
reviews_templates_content = f"<p>{_('Templates:')} {reviews_templates}</p>"
syntheses_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
content = reporter_content + opponent_content + reviewer_content + observer_content \
+ reviews_templates_content
content = defender_content + opponent_content + reviewer_content + observer_content \
+ syntheses_templates_content
informations.append({
'title': _("Second round"),
'type': "info",
@ -1204,7 +1204,7 @@ class Pool(models.Model):
@property
def solutions(self):
return [passage.reported_solution for passage in self.passages.all()]
return [passage.defended_solution for passage in self.passages.all()]
@property
def coeff(self):
@ -1251,12 +1251,12 @@ class Pool(models.Model):
header = [
sum(([str(_("Problem #{problem}").format(problem=passage.solution_number))] + (passage_width - 1) * [""]
for passage in passages), start=[str(_("Problem")), ""]),
sum(([f"{_('Reporter')} ({passage.reporter.team.trigram})", "",
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=[str(_("Role")), ""]),
sum(([f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})",
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 [])
@ -1268,7 +1268,7 @@ class Pool(models.Model):
line = [str(jury), jury.id]
for passage in passages:
note = passage.notes.filter(jury=jury).first()
line.extend([note.reporter_writing, note.reporter_oral, note.opponent_writing, note.opponent_oral,
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
note.reviewer_writing, note.reviewer_oral])
if has_observer:
line.extend([note.observer_writing, note.observer_oral])
@ -1284,7 +1284,7 @@ class Pool(models.Model):
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
average = [str(_("Average")), ""]
coeffs = sum(([passage.coeff_reporter_writing, passage.coeff_reporter_oral,
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 [])
@ -1330,11 +1330,11 @@ class Pool(models.Model):
pool__round=self.round,
pool__letter=self.letter).order_by('position', 'pool__room')
for i, passage in enumerate(all_passages):
participation = passage.reporter
reporter_passage = Passage.objects.get(reporter=participation,
participation = passage.defender
defender_passage = Passage.objects.get(defender=participation,
pool__tournament=self.tournament, pool__round=self.round)
reporter_row = 5 + reporter_passage.pool.juries.count()
reporter_col = reporter_passage.position - 1
defender_row = 5 + defender_passage.pool.juries.count()
defender_col = defender_passage.position - 1
opponent_passage = Passage.objects.get(opponent=participation,
pool__tournament=self.tournament, pool__round=self.round)
@ -1347,8 +1347,8 @@ class Pool(models.Model):
reviewer_col = reviewer_passage.position - 1
formula = "="
formula += (f"'{_('Pool')} {reporter_passage.pool.short_name}'"
f"!{getcol(min_column + reporter_col * passage_width)}{reporter_row + 3}") # Reporter
formula += (f"'{_('Pool')} {defender_passage.pool.short_name}'"
f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender
formula += (f" + '{_('Pool')} {opponent_passage.pool.short_name}'"
f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent
formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'"
@ -1362,8 +1362,8 @@ class Pool(models.Model):
f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}")
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
f"='{_('Pool')} {reporter_passage.pool.short_name}'"
f"!${getcol(3 + reporter_col * passage_width)}$1",
f"='{_('Pool')} {defender_passage.pool.short_name}'"
f"!${getcol(3 + defender_col * passage_width)}$1",
formula,
f"=RANG(D{max_row + 6 + i}; "
f"D${max_row + 6}:D${max_row + 5 + pool_size})"])
@ -1430,7 +1430,7 @@ class Pool(models.Model):
(f"A{max_row + 6}:E{max_row + 5 + pool_size}", (0.9, 0.9, 0.9)),]
# Display penalties in red
bg_colors += [(f"{getcol(2 + (passage.position - 1) * passage_width + 2)}{max_row + 2}", (1.0, 0.7, 0.7))
for passage in self.passages.filter(reporter_penalties__gte=1).all()]
for passage in self.passages.filter(defender_penalties__gte=1).all()]
for bg_range, bg_color in bg_colors:
r, g, b = bg_color
format_requests.append({
@ -1684,16 +1684,16 @@ class Passage(models.Model):
)
solution_number = models.PositiveSmallIntegerField(
verbose_name=_("reported solution"),
verbose_name=_("defended solution"),
choices=[
(i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, len(settings.PROBLEMS) + 1)
],
)
reporter = models.ForeignKey(
defender = models.ForeignKey(
Participation,
on_delete=models.PROTECT,
verbose_name=_("reporter"),
verbose_name=_("defender"),
related_name="+",
)
@ -1721,17 +1721,17 @@ class Passage(models.Model):
default=None,
)
reporter_penalties = models.PositiveSmallIntegerField(
defender_penalties = models.PositiveSmallIntegerField(
verbose_name=_("penalties"),
default=0,
help_text=_("Number of penalties for the reporter. "
"The reporter will loose a 0.5 coefficient per penalty."),
help_text=_("Number of penalties for the defender. "
"The defender will loose a 0.5 coefficient per penalty."),
)
@property
def reported_solution(self) -> "Solution":
def defended_solution(self) -> "Solution":
return Solution.objects.get(
participation=self.reporter,
participation=self.defender,
problem=self.solution_number,
final_solution=self.pool.tournament.final)
@ -1740,27 +1740,27 @@ class Passage(models.Model):
return sum(items) / len(items) if items else 0
@property
def average_reporter_writing(self) -> float:
return self.avg(note.reporter_writing for note in self.notes.all())
def average_defender_writing(self) -> float:
return self.avg(note.defender_writing for note in self.notes.all())
@property
def coeff_reporter_writing(self) -> float:
def coeff_defender_writing(self) -> float:
return 1 if settings.TFJM_APP == "TFJM" else 2
@property
def average_reporter_oral(self) -> float:
return self.avg(note.reporter_oral for note in self.notes.all())
def average_defender_oral(self) -> float:
return self.avg(note.defender_oral for note in self.notes.all())
@property
def coeff_reporter_oral(self) -> float:
def coeff_defender_oral(self) -> float:
coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
coeff *= 1 - 0.25 * self.reporter_penalties
coeff *= 1 - 0.25 * self.defender_penalties
return coeff
@property
def average_reporter(self) -> float:
return (self.coeff_reporter_writing * self.average_reporter_writing
+ self.coeff_reporter_oral * self.average_reporter_oral)
def average_defender(self) -> float:
return (self.coeff_defender_writing * self.average_defender_writing
+ self.coeff_defender_oral * self.average_defender_oral)
@property
def average_opponent_writing(self) -> float:
@ -1827,8 +1827,8 @@ class Passage(models.Model):
@property
def averages(self):
yield self.average_reporter_writing
yield self.average_reporter_oral
yield self.average_defender_writing
yield self.average_defender_oral
yield self.average_opponent_writing
yield self.average_opponent_oral
yield self.average_reviewer_writing
@ -1838,7 +1838,7 @@ class Passage(models.Model):
yield self.average_observer_oral
def average(self, participation):
avg = self.average_reporter if participation == self.reporter else self.average_opponent \
avg = self.average_defender if participation == self.defender else self.average_opponent \
if participation == self.opponent else self.average_reviewer if participation == self.reviewer \
else self.average_observer if participation == self.observer else 0
avg *= self.pool.coeff
@ -1849,9 +1849,9 @@ class Passage(models.Model):
return reverse_lazy("participation:passage_detail", args=(self.pk,))
def clean(self):
if self.reporter not in self.pool.participations.all():
if self.defender not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.")
.format(trigram=self.reporter.team.trigram))
.format(trigram=self.defender.team.trigram))
if self.opponent not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.")
.format(trigram=self.opponent.team.trigram))
@ -1864,8 +1864,8 @@ class Passage(models.Model):
return super().clean()
def __str__(self):
return _("Passage of {reporter} for problem {problem}")\
.format(reporter=self.reporter.team, problem=self.solution_number)
return _("Passage of {defender} for problem {problem}")\
.format(defender=self.defender.team, problem=self.solution_number)
class Meta:
verbose_name = _("passage")
@ -1905,12 +1905,8 @@ def get_solution_filename(instance, filename):
+ ("_final" if instance.final_solution else "")
def get_review_filename(instance, filename):
return f"reviews/{instance.participation.team.trigram}_{instance.type}_{instance.passage.pk}"
def get_synthesis_filename(instance, filename):
return get_review_filename(instance, filename)
return f"syntheses/{instance.participation.team.trigram}_{instance.type}_{instance.passage.pk}"
class Solution(models.Model):
@ -1955,7 +1951,7 @@ class Solution(models.Model):
ordering = ('participation__team__trigram', 'final_solution', 'problem',)
class WrittenReview(models.Model):
class Synthesis(models.Model):
participation = models.ForeignKey(
Participation,
on_delete=models.CASCADE,
@ -1965,7 +1961,7 @@ class WrittenReview(models.Model):
passage = models.ForeignKey(
Passage,
on_delete=models.CASCADE,
related_name="written_reviews",
related_name="syntheses",
verbose_name=_("passage"),
)
@ -1984,16 +1980,16 @@ class WrittenReview(models.Model):
)
def __str__(self):
return _("Written review of {team} as {type} for problem {problem} of {reporter}").format(
return _("Synthesis of {team} as {type} for problem {problem} of {defender}").format(
team=self.participation.team.trigram,
type=self.get_type_display(),
problem=self.passage.solution_number,
reporter=self.passage.reporter.team.trigram,
defender=self.passage.defender.team.trigram,
)
class Meta:
verbose_name = _("written review")
verbose_name_plural = _("written reviews")
verbose_name = _("synthesis")
verbose_name_plural = _("syntheses")
unique_together = (('participation', 'passage', 'type', ), )
ordering = ('passage__pool__round', 'type',)
@ -2013,14 +2009,14 @@ class Note(models.Model):
related_name="notes",
)
reporter_writing = models.PositiveSmallIntegerField(
verbose_name=_("reporter writing note"),
defender_writing = models.PositiveSmallIntegerField(
verbose_name=_("defender writing note"),
choices=[(i, i) for i in range(0, 21)],
default=0,
)
reporter_oral = models.PositiveSmallIntegerField(
verbose_name=_("reporter oral note"),
defender_oral = models.PositiveSmallIntegerField(
verbose_name=_("defender oral note"),
choices=[(i, i) for i in range(0, 21)],
default=0,
)
@ -2062,8 +2058,8 @@ class Note(models.Model):
)
def get_all(self):
yield self.reporter_writing
yield self.reporter_oral
yield self.defender_writing
yield self.defender_oral
yield self.opponent_writing
yield self.opponent_oral
yield self.reviewer_writing
@ -2072,10 +2068,10 @@ class Note(models.Model):
yield self.observer_writing
yield self.observer_oral
def set_all(self, reporter_writing: int, reporter_oral: int, opponent_writing: int, opponent_oral: int,
def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int,
reviewer_writing: int, reviewer_oral: int, observer_writing: int = 0, observer_oral: int = 0):
self.reporter_writing = reporter_writing
self.reporter_oral = reporter_oral
self.defender_writing = defender_writing
self.defender_oral = defender_oral
self.opponent_writing = opponent_writing
self.opponent_oral = opponent_oral
self.reviewer_writing = reviewer_writing

View File

@ -108,13 +108,13 @@ class PoolTable(tables.Table):
class PassageTable(tables.Table):
# FIXME Ne pas afficher l'équipe observatrice si non nécessaire
reporter = tables.LinkColumn(
defender = tables.LinkColumn(
"participation:passage_detail",
args=[tables.A("id")],
verbose_name=_("reporter").capitalize,
verbose_name=_("defender").capitalize,
)
def render_reporter(self, value):
def render_defender(self, value):
return value.team.trigram
def render_opponent(self, value):
@ -131,7 +131,7 @@ class PassageTable(tables.Table):
'class': 'table table-condensed table-striped text-center',
}
model = Passage
fields = ('reporter', 'opponent', 'reviewer', 'observer', 'solution_number', )
fields = ('defender', 'opponent', 'reviewer', 'observer', 'solution_number', )
class NoteTable(tables.Table):
@ -159,5 +159,5 @@ class NoteTable(tables.Table):
'class': 'table table-condensed table-striped text-center',
}
model = Note
fields = ('jury', 'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
fields = ('jury', 'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', 'update',)

View File

@ -6,7 +6,7 @@
<form method="post">
<div id="form-content">
<h4>{% trans "Notes of" %} {{ note.jury }}</h4>
<h5>{% trans "Defense of" %} {{ note.passage.reporter.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
<h5>{% trans "Defense of" %} {{ note.passage.defender.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
<hr>
{% csrf_token %}
{{ form|crispy }}

View File

@ -25,8 +25,8 @@
<dt class="col-sm-3">{% trans "Position:" %}</dt>
<dd class="col-sm-9">{{ passage.position }}</dd>
<dt class="col-sm-3">{% trans "Reporter:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd>
<dt class="col-sm-3">{% trans "Defender:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.defender.get_absolute_url }}">{{ passage.defender.team }}</a></dd>
<dt class="col-sm-3">{% trans "Opponent:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.opponent.get_absolute_url }}">{{ passage.opponent.team }}</a></dd>
@ -39,18 +39,18 @@
<dd class="col-sm-9"><a href="{{ passage.observer.get_absolute_url }}">{{ passage.observer.team }}</a></dd>
{% endif %}
<dt class="col-sm-3">{% trans "Reported solution:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.reported_solution.file.url }}">{{ passage.reported_solution }}</a></dd>
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}">{{ passage.defended_solution }}</a></dd>
<dt class="col-sm-3">{% trans "Reporter penalties count:" %}</dt>
<dd class="col-sm-9">{{ passage.reporter_penalties }}</dd>
<dt class="col-sm-3">{% trans "Defender penalties count:" %}</dt>
<dd class="col-sm-9">{{ passage.defender_penalties }}</dd>
<dt class="col-sm-3">{% trans "Syntheses:" %}</dt>
<dd class="col-sm-9">
{% for review in passage.written_reviews.all %}
<a href="{{ review.file.url }}">{{ review }}{% if not forloop.last %}, {% endif %}</a>
{% for synthesis in passage.syntheses.all %}
<a href="{{ synthesis.file.url }}">{{ synthesis }}{% if not forloop.last %}, {% endif %}</a>
{% empty %}
{% trans "No review was uploaded yet." %}
{% trans "No synthesis was uploaded yet." %}
{% endfor %}
</dd>
</dl>
@ -63,7 +63,7 @@
</div>
{% elif user.registration.participates %}
<div class="card-footer text-center">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadWrittenReviewModal">{% trans "Upload review" %}</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadSynthesisModal">{% trans "Upload synthesis" %}</button>
</div>
{% endif %}
</div>
@ -79,19 +79,19 @@
<div class="card-body">
<dl class="row">
<dt class="col-sm-8">
{% trans "Average points for the reporter writing" %}
({{ passage.reporter.team.trigram }}) :
{% trans "Average points for the defender writing" %}
({{ passage.defender.team.trigram }}) :
</dt>
<dd class="col-sm-4">
{{ passage.average_reporter_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
{{ passage.average_defender_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
</dd>
<dt class="col-sm-8">
{% trans "Average points for the reporter oral" %}
({{ passage.reporter.team.trigram }}) :
{% trans "Average points for the defender oral" %}
({{ passage.defender.team.trigram }}) :
</dt>
<dd class="col-sm-4">
{{ passage.average_reporter_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
{{ passage.average_defender_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
</dd>
<dt class="col-sm-8">
@ -137,11 +137,11 @@
<dl class="row">
<dt class="col-sm-8">
{% trans "Reporter points" %}
({{ passage.reporter.team.trigram }}) :
{% trans "Defender points" %}
({{ passage.defender.team.trigram }}) :
</dt>
<dd class="col-sm-4">
{{ passage.average_reporter|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
{{ passage.average_defender|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
</dd>
<dt class="col-sm-8">
@ -184,10 +184,10 @@
{% include "base_modal.html" with modal_id=note.modal_name %}
{% endfor %}
{% elif user.registration.participates %}
{% trans "Upload review" as modal_title %}
{% trans "Upload synthesis" as modal_title %}
{% trans "Upload" as modal_button %}
{% url "participation:upload_review" pk=passage.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadWrittenReview" modal_enctype="multipart/form-data" %}
{% url "participation:upload_synthesis" pk=passage.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadSynthesis" modal_enctype="multipart/form-data" %}
{% endif %}
{% endblock %}
@ -201,8 +201,8 @@
initModal("{{ note.modal_name }}", "{% url "participation:update_notes" pk=note.pk %}")
{% endfor %}
{% elif user.registration.participates %}
initModal("uploadWrittenReview", "{% url "participation:upload_review" pk=passage.pk %}")
initModal("uploadSynthesis", "{% url "participation:upload_synthesis" pk=passage.pk %}")
{% endif %}
})
});
</script>
{% endblock %}

View File

@ -46,10 +46,10 @@
</a>
</dd>
<dt class="col-sm-3">{% trans "Reported solutions:" %}</dt>
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
<dd class="col-sm-9">
{% for passage in pool.passages.all %}
<a href="{{ passage.reported_solution.file.url }}">{{ passage.reporter.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
<a href="{{ passage.defended_solution.file.url }}">{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
<a href="{% url 'participation:pool_download_solutions' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
<i class="fas fa-download"></i> {% trans "Download all" %}
@ -61,16 +61,16 @@
<ul class="list-group list-group-flush">
{% for passage in pool.passages.all %}
<li class="list-group-item">
{{ passage.reporter.team.trigram }} — {{ passage.get_solution_number_display }} :
{% for review in passage.written_reviews.all %}
<a href="{{ review.file.url }}">{{ review.participation.team.trigram }} ({{ review.get_type_display }})</a>{% if not forloop.last %}, {% endif %}
{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }} :
{% for synthesis in passage.syntheses.all %}
<a href="{{ synthesis.file.url }}">{{ synthesis.participation.team.trigram }} ({{ synthesis.get_type_display }})</a>{% if not forloop.last %}, {% endif %}
{% empty %}
{% trans "No review was uploaded yet." %}
{% trans "No synthesis was uploaded yet." %}
{% endfor %}
</li>
{% endfor %}
</ul>
<a href="{% url 'participation:pool_download_written_reviews' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
<a href="{% url 'participation:pool_download_syntheses' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
<i class="fas fa-download"></i> {% trans "Download all" %}
</a>
</dd>

View File

@ -41,7 +41,7 @@
\begin{center}
\begin{itemize}
{% for passage in passages.all %}
\item D\'efenseur\textperiodcentered{}se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.reporter.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
\item D\'efenseur\textperiodcentered{}se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.defender.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
{% endfor %}
\end{itemize}
\end{center}
@ -50,7 +50,7 @@
%%%%%%%%%%%%%%%%%%%%%DEFENSEUR
\begin{tabular}{|c|p{24mm}|p{11cm}|c|{% for passage in passages.all %}p{2cm}|{% endfor %}}\hline
\multicolumn{4}{|l|}{{\bf D\'efenseur\textperiodcentered{}se} \normalsize pr\'esente les id\'ees et r\'esultats principaux pour la solution du probl\`eme.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.reporter.team.trigram }} {% endfor %}\\ \hline \hline
\multicolumn{4}{|l|}{{\bf D\'efenseur\textperiodcentered{}se} \normalsize pr\'esente les id\'ees et r\'esultats principaux pour la solution du probl\`eme.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.defender.team.trigram }} {% endfor %}\\ \hline \hline
%ECRIT
\multirow{6}{3mm}{\centering \bf\'E\\ C\\ R\\ I\\ T} & \multirow{3}{20mm}{Partie scientifique} & Profondeur et difficulté des éléments présentés & [0,6] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}

View File

@ -46,7 +46,7 @@ Tour {{ pool.round }} \;-- Poule {{ pool.get_letter_display }}{% if pool.partici
\begin{tabular}{|p{40mm}{% for passage in passages.all %}{% if passages.count == 3 %}|p{3cm}|p{3cm}{% else %}|p{2.5cm}|p{2.5cm}{% endif %}{% endfor %}|}\hline
\multirow{2}{40mm}{\LARGE R\^ole} {% for passage in passages.all %}& \multicolumn{2}{c|}{ \Large Probl\`eme {{ passage.solution_number }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
{% for passage in passages.all %}& \hspace{4mm} {\Large \'ECRIT} & \hspace{4mm} {\Large ORAL}{% endfor %} \\ \hline
\multirow{2}{35mm}{\LARGE D\'efenseur\textperiodcentered{}se} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.reporter.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
\multirow{2}{35mm}{\LARGE D\'efenseur\textperiodcentered{}se} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.defender.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
{% for passage in passages.all %}
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 20$
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 20$

View File

@ -38,15 +38,15 @@
<dt class="col-sm-6 text-sm-end">{% trans 'date of the random draw'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.solutions_draw }}</dd>
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal written reviews submission for the first round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.reviews_first_phase_limit }}</dd>
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_first_phase_limit }}</dd>
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal written reviews submission for the second round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.reviews_second_phase_limit }}</dd>
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the second round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_second_phase_limit }}</dd>
{% if TFJM.APP == "ETEAM" %}
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal written reviews submission for the third round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.reviews_third_phase_limit }}</dd>
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the third round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_third_phase_limit }}</dd>
{% endif %}
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
@ -233,48 +233,48 @@
<ul>
<li>
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}">
{% trans "Validated team participant data spreadsheet" %}
Validated team participant data spreadsheet
</a>
</li>
<li>
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}?all">
{% trans "All teams participant data spreadsheet" %}
All teams participant data spreadsheet
</a>
</li>
<li>
<a href="{% url "participation:tournament_authorizations" tournament_id=tournament.id %}">
{% trans "Archive of all authorisations sorted by team and person" %}
Archive of all authorisations sorted by team and person
</a>
</li>
<li>
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}">
{% trans "Archive of all submitted solutions sorted by team" %}
Archive of all submitted solutions sorted by team
</a>
</li>
<li>
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=problem">
{% trans "Archive of all sent solutions sorted by problem" %}
Archive of all sent solutions sorted by problem
</a>
</li>
<li>
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=pool">
{% trans "Archive of all sent solutions sorted by pool" %}
Archive of all sent solutions sorted by pool
</a>
</li>
<li>
<a href="{% url "participation:tournament_written_reviews" tournament_id=tournament.id %}?sort_by=pool">
{% trans "Archive of all summary notes sorted by pool and passage" %}
<a href="{% url "participation:tournament_syntheses" tournament_id=tournament.id %}?sort_by=pool">
Archive of all summary notes sorted by pool and passage
</a>
</li>
<li>
<a href="https://docs.google.com/spreadsheets/d/{{ tournament.notes_sheet_id }}/edit">
<i class="fas fa-table"></i>
{% trans "Note spreadsheet on Google Sheets" %}
Note spreadsheet on Google Sheets
</a>
</li>
<li>
<a href="{% url "participation:tournament_notation_sheets" tournament_id=tournament.id %}">
{% trans "Archive of all printable note sheets sorted by pool" %}
Archive of all printable note sheets sorted by pool
</a>
</li>
</ul>

View File

@ -8,11 +8,11 @@ from .views import CreateTeamView, FinalNotationSheetTemplateView, GSheetNotific
PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, PoolJuryView, PoolNotesTemplateView, \
PoolPresideJuryView, PoolRemoveJuryView, PoolUpdateView, PoolUploadNotesView, \
ScaleNotationSheetTemplateView, SelectTeamFinalView, \
SolutionsDownloadView, SolutionUploadView, \
SolutionsDownloadView, SolutionUploadView, SynthesisUploadView, \
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
TournamentHarmonizeNoteView, TournamentHarmonizeView, TournamentListView, TournamentPaymentsView, \
TournamentPublishNotesView, TournamentUpdateView, WrittenReviewUploadView
TournamentPublishNotesView, TournamentUpdateView
app_name = "participation"
@ -42,8 +42,8 @@ urlpatterns = [
name="tournament_authorizations"),
path("tournament/<int:tournament_id>/solutions/", SolutionsDownloadView.as_view(),
name="tournament_solutions"),
path("tournament/<int:tournament_id>/written_reviews/", SolutionsDownloadView.as_view(),
name="tournament_written_reviews"),
path("tournament/<int:tournament_id>/syntheses/", SolutionsDownloadView.as_view(),
name="tournament_syntheses"),
path("tournament/<int:tournament_id>/notation/sheets/", NotationSheetsArchiveView.as_view(),
name="tournament_notation_sheets"),
path("tournament/<int:pk>/notation/notifications/", GSheetNotificationsView.as_view(),
@ -60,7 +60,7 @@ urlpatterns = [
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
path("pools/<int:pool_id>/solutions/", SolutionsDownloadView.as_view(), name="pool_download_solutions"),
path("pools/<int:pool_id>/written_reviews/", SolutionsDownloadView.as_view(), name="pool_download_written_reviews"),
path("pools/<int:pool_id>/syntheses/", SolutionsDownloadView.as_view(), name="pool_download_syntheses"),
path("pools/<int:pk>/notation/scale/", ScaleNotationSheetTemplateView.as_view(), name="pool_scale_note_sheet"),
path("pools/<int:pk>/notation/final/", FinalNotationSheetTemplateView.as_view(), name="pool_final_note_sheet"),
path("pools/<int:pool_id>/notation/sheets/", NotationSheetsArchiveView.as_view(), name="pool_notation_sheets"),
@ -71,6 +71,6 @@ urlpatterns = [
path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"),
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
path("pools/passages/<int:pk>/written_review/", WrittenReviewUploadView.as_view(), name="upload_written_review"),
path("pools/passages/<int:pk>/solution/", SynthesisUploadView.as_view(), name="upload_synthesis"),
path("pools/passages/notes/<int:pk>/", NoteUpdateView.as_view(), name="update_notes"),
]

View File

@ -46,9 +46,9 @@ from tfjm.lists import get_sympa_client
from tfjm.views import AdminMixin, VolunteerMixin
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
PoolForm, RequestValidationForm, SolutionForm, TeamForm, TournamentForm, UploadNotesForm, \
ValidateParticipationForm, WrittenReviewForm
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
PoolForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
UploadNotesForm, ValidateParticipationForm
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
@ -977,7 +977,7 @@ class PoolUpdateView(VolunteerMixin, UpdateView):
class SolutionsDownloadView(VolunteerMixin, View):
"""
Download all solutions or written reviews as a ZIP archive.
Download all solutions or syntheses as a ZIP archive.
"""
def dispatch(self, request, *args, **kwargs):
@ -1018,12 +1018,11 @@ class SolutionsDownloadView(VolunteerMixin, View):
if 'team_id' in kwargs:
team = Team.objects.get(pk=kwargs["team_id"])
solutions = Solution.objects.filter(participation=team.participation).all()
written_reviews = WrittenReview.objects.filter(participation=team.participation).all()
filename = _("Solutions of team {trigram}.zip") if is_solution \
else _("Written reviews of team {trigram}.zip")
syntheses = Synthesis.objects.filter(participation=team.participation).all()
filename = _("Solutions of team {trigram}.zip") if is_solution else _("Syntheses of team {trigram}.zip")
filename = filename.format(trigram=team.trigram)
def prefix(s: Solution | WrittenReview) -> str:
def prefix(s: Solution | Synthesis) -> str:
return ""
elif 'tournament_id' in kwargs:
tournament = Tournament.objects.get(pk=kwargs["tournament_id"])
@ -1036,12 +1035,11 @@ class SolutionsDownloadView(VolunteerMixin, View):
for sol in pool.solutions:
sol.pool = pool
solutions.append(sol)
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution \
else _("Written reviews of {tournament}.zip")
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
filename = filename.format(tournament=tournament.name)
def prefix(s: Solution | WrittenReview) -> str:
def prefix(s: Solution | Synthesis) -> str:
pool = s.pool if is_solution else s.passage.pool
p = f"Poule {pool.short_name}/"
if not is_solution:
@ -1052,28 +1050,27 @@ class SolutionsDownloadView(VolunteerMixin, View):
solutions = Solution.objects.filter(participation__tournament=tournament).all()
else:
solutions = Solution.objects.filter(final_solution=True).all()
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution \
else _("Written reviews of {tournament}.zip")
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
filename = filename.format(tournament=tournament.name)
def prefix(s: Solution | WrittenReview) -> str:
def prefix(s: Solution | Synthesis) -> str:
return f"{s.participation.team.trigram}/" if sort_by == "team" else f"Problème {s.problem}/"
else:
pool = Pool.objects.get(pk=kwargs["pool_id"])
solutions = pool.solutions
written_reviews = WrittenReview.objects.filter(passage__pool=pool).all()
syntheses = Synthesis.objects.filter(passage__pool=pool).all()
filename = _("Solutions for pool {pool} of tournament {tournament}.zip") \
if is_solution else _("Written reviews for pool {pool} of tournament {tournament}.zip")
if is_solution else _("Syntheses for pool {pool} of tournament {tournament}.zip")
filename = filename.format(pool=pool.short_name,
tournament=pool.tournament.name)
def prefix(s: Solution | WrittenReview) -> str:
def prefix(s: Solution | Synthesis) -> str:
return ""
output = BytesIO()
zf = ZipFile(output, "w")
for s in (solutions if is_solution else written_reviews):
for s in (solutions if is_solution else syntheses):
if s.file.storage.exists(s.file.path):
zf.write("media/" + s.file.name, prefix(s) + f"{s}.pdf")
@ -1503,10 +1500,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_role.addElement(role_tc)
header_role.addElement(CoveredTableCell())
for i in range(pool_size):
reporter_tc = TableCell(valuetype="string", stylename=title_style_left)
reporter_tc.addElement(P(text=_("Reporter")))
reporter_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(reporter_tc)
defender_tc = TableCell(valuetype="string", stylename=title_style_left)
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)
@ -1539,13 +1536,13 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_notes.addElement(CoveredTableCell())
for i in range(pool_size):
reporter_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
reporter_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
header_notes.addElement(reporter_w_tc)
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
defender_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
header_notes.addElement(defender_w_tc)
reporter_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
reporter_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
header_notes.addElement(reporter_o_tc)
defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
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=f"{_('Writing')} (/10)"))
@ -1626,13 +1623,13 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
coeff_row.addElement(coeff_tc)
coeff_row.addElement(CoveredTableCell())
for passage in self.object.passages.all():
reporter_w_tc = TableCell(valuetype="float", value=passage.coeff_reporter_writing, stylename=style_left)
reporter_w_tc.addElement(P(text=str(passage.coeff_reporter_writing)))
coeff_row.addElement(reporter_w_tc)
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)
reporter_o_tc = TableCell(valuetype="float", value=passage.coeff_reporter_oral, stylename=style)
reporter_o_tc.addElement(P(text=str(passage.coeff_reporter_oral)))
coeff_row.addElement(reporter_o_tc)
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=passage.coeff_opponent_writing, stylename=style)
opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
@ -1671,12 +1668,12 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
for i, passage in enumerate(self.object.passages.all()):
def_w_col = getcol(min_column + passage_width * i)
def_o_col = getcol(min_column + passage_width * i + 1)
reporter_tc = TableCell(valuetype="float", value=passage.average_reporter, stylename=style_botleft)
reporter_tc.addElement(P(text=str(passage.average_reporter)))
reporter_tc.setAttribute('numbercolumnsspanned', "2")
reporter_tc.setAttribute("formula", f"of:=[.{def_w_col}{max_row + 1}] * [.{def_w_col}{max_row + 2}]"
defender_tc = TableCell(valuetype="float", value=passage.average_defender, stylename=style_botleft)
defender_tc.addElement(P(text=str(passage.average_defender)))
defender_tc.setAttribute('numbercolumnsspanned', "2")
defender_tc.setAttribute("formula", f"of:=[.{def_w_col}{max_row + 1}] * [.{def_w_col}{max_row + 2}]"
f" + [.{def_o_col}{max_row + 1}] * [.{def_o_col}{max_row + 2}]")
subtotal_row.addElement(reporter_tc)
subtotal_row.addElement(defender_tc)
subtotal_row.addElement(CoveredTableCell())
opp_w_col = getcol(min_column + passage_width * i + 2)
@ -1748,7 +1745,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
team_tc = TableCell(valuetype="string",
stylename=style_botleft if passage.position == pool_size else style_left)
team_tc.addElement(P(text=f"{passage.reporter.team.name} ({passage.reporter.team.trigram})"))
team_tc.addElement(P(text=f"{passage.defender.team.name} ({passage.defender.team.trigram})"))
team_tc.setAttribute('numbercolumnsspanned', "2")
team_row.addElement(team_tc)
@ -1758,17 +1755,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
team_row.addElement(problem_tc)
reporter_pos = passage.position - 1
opponent_pos = self.object.passages.get(opponent=passage.reporter).position - 1
reviewer_pos = self.object.passages.get(reviewer=passage.reporter).position - 1
observer_pos = self.object.passages.get(observer=passage.reporter).position - 1 \
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.reporter),
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
stylename=style_bot if passage.position == pool_size else style)
score_tc.addElement(P(text=self.object.average(passage.reporter)))
score_tc.addElement(P(text=self.object.average(passage.defender)))
formula = "of:="
formula += getcol(min_column + reporter_pos * passage_width) + str(max_row + 3) # Reporter
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
if has_observer:
@ -1778,9 +1775,9 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
team_row.addElement(score_tc)
score_col = 'C'
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.reporter) + 1,
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.defender) + 1,
stylename=style_botright if passage.position == pool_size else style_right)
rank_tc.addElement(P(text=str(sorted_participations.index(passage.reporter) + 1)))
rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1)))
rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; "
f"[.{score_col}${max_row + 6}]:"
f"[.{score_col}${max_row + 5 + pool_size}])")
@ -1984,7 +1981,7 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
or reg in passage.pool.juries.all()
or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \
or reg.participates and reg.team \
and reg.team.participation in [passage.reporter, passage.opponent, passage.reviewer, passage.observer]:
and reg.team.participation in [passage.defender, passage.opponent, passage.reviewer, passage.observer]:
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
@ -2005,8 +2002,8 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
if 'notes' in context and not self.request.user.registration.is_admin:
context['notes']._sequence.remove('update')
context['notes'].columns['reporter_writing'].column.verbose_name += f" ({passage.reporter.team.trigram})"
context['notes'].columns['reporter_oral'].column.verbose_name += f" ({passage.reporter.team.trigram})"
context['notes'].columns['defender_writing'].column.verbose_name += f" ({passage.defender.team.trigram})"
context['notes'].columns['defender_oral'].column.verbose_name += f" ({passage.defender.team.trigram})"
context['notes'].columns['opponent_writing'].column.verbose_name += f" ({passage.opponent.team.trigram})"
context['notes'].columns['opponent_oral'].column.verbose_name += f" ({passage.opponent.team.trigram})"
context['notes'].columns['reviewer_writing'].column.verbose_name += f" ({passage.reviewer.team.trigram})"
@ -2031,9 +2028,9 @@ class PassageUpdateView(VolunteerMixin, UpdateView):
return self.handle_no_permission()
class WrittenReviewUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_written_review.html"
form_class = WrittenReviewForm
class SynthesisUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_synthesis.html"
form_class = SynthesisForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.registration.participates:
@ -2060,14 +2057,14 @@ class WrittenReviewUploadView(LoginRequiredMixin, FormView):
form_syn = form.instance
form_syn.type = 1 if self.participation == self.passage.opponent \
else 2 if self.participation == self.passage.reviewer else 3
syn_qs = WrittenReview.objects.filter(participation=self.participation,
passage=self.passage,
type=form_syn.type).all()
syn_qs = Synthesis.objects.filter(participation=self.participation,
passage=self.passage,
type=form_syn.type).all()
deadline = self.passage.pool.tournament.reviews_first_phase_limit if self.passage.pool.round == 1 \
else self.passage.pool.tournament.reviews_second_phase_limit
deadline = self.passage.pool.tournament.syntheses_first_phase_limit if self.passage.pool.round == 1 \
else self.passage.pool.tournament.syntheses_second_phase_limit
if syn_qs.exists() and timezone.now() > deadline:
form.add_error(None, _("You can't upload a written review after the deadline."))
form.add_error(None, _("You can't upload a synthesis after the deadline."))
return self.form_invalid(form)
# Drop previous solution if existing
@ -2101,8 +2098,8 @@ class NoteUpdateView(VolunteerMixin, UpdateView):
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})"
form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.team.trigram})"
form.fields['defender_writing'].label += f" ({self.object.passage.defender.team.trigram})"
form.fields['defender_oral'].label += f" ({self.object.passage.defender.team.trigram})"
form.fields['opponent_writing'].label += f" ({self.object.passage.opponent.team.trigram})"
form.fields['opponent_oral'].label += f" ({self.object.passage.opponent.team.trigram})"
form.fields['reviewer_writing'].label += f" ({self.object.passage.reviewer.team.trigram})"

View File

@ -26,7 +26,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View
from django_tables2 import SingleTableView
from magic import Magic
from participation.models import Passage, Solution, Tournament, WrittenReview
from participation.models import Passage, Solution, Synthesis, Tournament
from tfjm.tokens import email_validation_token
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
@ -837,11 +837,11 @@ class SolutionView(LoginRequiredMixin, View):
solution = Solution.objects.get(file__endswith=filename)
user = request.user
if user.registration.participates and user.registration.team.participation:
passage_participant_qs = Passage.objects.filter(Q(reporter=user.registration.team.participation)
passage_participant_qs = Passage.objects.filter(Q(defender=user.registration.team.participation)
| Q(opponent=user.registration.team.participation)
| Q(reviewer=user.registration.team.participation)
| Q(observer=user.registration.team.participation),
reporter=solution.participation,
defender=solution.participation,
solution_number=solution.problem)
else:
passage_participant_qs = Passage.objects.none()
@ -853,7 +853,7 @@ class SolutionView(LoginRequiredMixin, View):
or user.registration.is_volunteer
and Passage.objects.filter(Q(pool__juries=user.registration)
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
reporter=solution.participation,
defender=solution.participation,
solution_number=solution.problem).exists()
or user.registration.participates and user.registration.team
and (solution.participation.team == user.registration.team or
@ -871,30 +871,30 @@ class SolutionView(LoginRequiredMixin, View):
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
class WrittenReviewView(LoginRequiredMixin, View):
class SynthesisView(LoginRequiredMixin, View):
"""
Display the sent written reviews.
Display the sent synthesis.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/reviews/{filename}"
path = f"media/syntheses/{filename}"
if not os.path.exists(path):
raise Http404
review = WrittenReview.objects.get(file__endswith=filename)
synthesis = Synthesis.objects.get(file__endswith=filename)
user = request.user
if not (user.registration.is_admin or user.registration.is_volunteer
and (user.registration in review.passage.pool.juries.all()
or user.registration in review.passage.pool.tournament.organizers.all()
or user.registration.pools_presided.filter(tournament=review.passage.pool.tournament).exists())
or user.registration.participates and user.registration.team == review.participation.team):
and (user.registration in synthesis.passage.pool.juries.all()
or user.registration in synthesis.passage.pool.tournament.organizers.all()
or user.registration.pools_presided.filter(tournament=synthesis.passage.pool.tournament).exists())
or user.registration.participates and user.registration.team == synthesis.participation.team):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = str(review) + f".{ext}"
true_file_name = str(synthesis) + f".{ext}"
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)

View File

@ -32,7 +32,7 @@ Round \underline{~~~~} pool \underline{~~~~}
\medskip
Problem \underline{~~~~} reported by team \underline{~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Problem \underline{~~~~} defended by team \underline{~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
\medskip

View File

@ -24,7 +24,7 @@ from django.views.defaults import bad_request, page_not_found, permission_denied
from django.views.generic import TemplateView
from participation.views import MotivationLetterView
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
ReceiptView, SolutionView, VaccineSheetView, WrittenReviewView
ReceiptView, SolutionView, SynthesisView, VaccineSheetView
from .views import AdminSearchView
@ -61,8 +61,8 @@ urlpatterns = [
path('media/solutions/<str:filename>/', SolutionView.as_view(),
name='solution'),
path('media/reviews/<str:filename>/', WrittenReviewView.as_view(),
name='reviews'),
path('media/syntheses/<str:filename>/', SynthesisView.as_view(),
name='synthesis'),
]
if settings.DEBUG: