1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2024-11-27 02:13:06 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Emmy D'Anello
620bbe7817
Defender => Reporter
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-07-06 22:12:07 +02:00
Emmy D'Anello
12205f953b
Rename synthesis to written review
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-07-06 21:29:16 +02:00
26 changed files with 892 additions and 580 deletions

View File

@ -416,7 +416,7 @@ class Pool(models.Model):
passage_pool = pool2
passage_position = 1 + i // 2
defender = tds[line[0]].participation
reporter = 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,
defender=defender,
reporter=reporter,
opponent=opponent,
reviewer=reviewer,
observer=observer,
defender_penalties=tds[line[0]].penalty_int,
reporter_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 defender oral, in percentage, which is a malus of 25% for each penalty.
The penalty multiplier on the reporter 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 defenderTd = document.createElement('td')
defenderTd.classList.add('text-center')
defenderTd.innerText = 'Déf'
let reporterTd = document.createElement('td')
reporterTd.classList.add('text-center')
reporterTd.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(defenderTd, reviewerTd, opponentTd)
teamTr.append(reporterTd, reviewerTd, opponentTd)
break
case 1:
teamTr.append(opponentTd, defenderTd, reviewerTd)
teamTr.append(opponentTd, reporterTd, reviewerTd)
break
case 2:
teamTr.append(reviewerTd, opponentTd, defenderTd)
teamTr.append(reviewerTd, opponentTd, reporterTd)
break
}
} else if (poule.teams.length === 4) {
let emptyTd = document.createElement('td')
switch (i) {
case 0:
teamTr.append(defenderTd, emptyTd, reviewerTd, opponentTd)
teamTr.append(reporterTd, emptyTd, reviewerTd, opponentTd)
break
case 1:
teamTr.append(opponentTd, defenderTd, emptyTd, reviewerTd)
teamTr.append(opponentTd, reporterTd, emptyTd, reviewerTd)
break
case 2:
teamTr.append(reviewerTd, opponentTd, defenderTd, emptyTd)
teamTr.append(reviewerTd, opponentTd, reporterTd, emptyTd)
break
case 3:
teamTr.append(emptyTd, reviewerTd, opponentTd, defenderTd)
teamTr.append(emptyTd, reviewerTd, opponentTd, reporterTd)
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(defenderTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
teamTr.append(reporterTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
break
case 1:
teamTr.append(emptyTd, defenderTd, reviewerTd, emptyTd2, opponentTd)
teamTr.append(emptyTd, reporterTd, reviewerTd, emptyTd2, opponentTd)
break
case 2:
teamTr.append(opponentTd, emptyTd, defenderTd, emptyTd2, reviewerTd)
teamTr.append(opponentTd, emptyTd, reporterTd, emptyTd2, reviewerTd)
break
case 3:
teamTr.append(reviewerTd, opponentTd, emptyTd, defenderTd, emptyTd2)
teamTr.append(reviewerTd, opponentTd, emptyTd, reporterTd, emptyTd2)
break
case 4:
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, defenderTd)
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, reporterTd)
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 defender
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral reporter
// 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">Déf</td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</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">{% trans "Opp" context "Role abbreviation" %}</td>
{% elif forloop.counter == 2 %}
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
<td class="text-center">Rap</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">{% trans "Rev" context "Role abbreviation" %}</td>
{% elif forloop.counter == 3 %}
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</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>
{% endif %}
{% elif pool.size == 4 %}
{% if forloop.counter == 1 %}
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</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">{% trans "Opp" context "Role abbreviation" %}</td>
{% elif forloop.counter == 2 %}
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</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">{% trans "Rev" context "Role abbreviation" %}</td>
{% elif forloop.counter == 3 %}
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</td>
<td></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 class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
{% elif forloop.counter == 4 %}
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td class="text-center">Déf</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">{% trans "Rep" context "Role abbreviation" %}</td>
{% endif %}
{% elif pool.size == 5 %}
{% if forloop.counter == 1 %}
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td></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 class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
{% elif forloop.counter == 2 %}
<td></td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</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">{% trans "Opp" context "Role abbreviation" %}</td>
{% elif forloop.counter == 3 %}
<td class="text-center">Opp</td>
<td></td>
<td class="text-center">Déf</td>
<td></td>
<td class="text-center">Rap</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">{% trans "Rev" context "Role abbreviation" %}</td>
{% elif forloop.counter == 4 %}
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td></td>
<td class="text-center">Déf</td>
<td></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 class="text-center"></td>
{% elif forloop.counter == 5 %}
<td></td>
<td class="text-center">Rap</td>
<td class="text-center">Opp</td>
<td></td>
<td class="text-center">Déf</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">{% trans "Rep" context "Role abbreviation" %}</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 10:16+0200\n"
"POT-Creation-Date: 2024-07-06 22:07+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:1116
#: participation/models.py:807 participation/models.py:1131
#: 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:1651 participation/models.py:1660
#: participation/models.py:1666 participation/models.py:1675
#: 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:1120
#: participation/models.py:1135
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:1128
#: draw/models.py:268 participation/models.py:1143
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:1652
#: draw/models.py:447 participation/models.py:1667
msgid "pools"
msgstr "poules"
#: draw/models.py:459 participation/models.py:1106 participation/models.py:1871
#: participation/models.py:1901 participation/models.py:1943
#: draw/models.py:459 participation/models.py:1121 participation/models.py:1886
#: participation/models.py:1920 participation/models.py:1962
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:1237
#: participation/models.py:1674 participation/models.py:1908
#: participation/views.py:1489 participation/views.py:1754
#: 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
#, python-brace-format
msgid "Problem #{problem}"
msgstr "Problème n°{problem}"
@ -814,6 +814,67 @@ 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"
@ -919,26 +980,26 @@ msgid "selected for final"
msgstr "sélectionnée pour la finale"
#: participation/admin.py:124 participation/admin.py:188
#: participation/models.py:1681 participation/tables.py:114
msgid "defender"
#: participation/models.py:1696 participation/tables.py:114
msgid "reporter"
msgstr "défenseur⋅se"
#: participation/admin.py:128 participation/models.py:1688
#: participation/models.py:1955
#: participation/admin.py:128 participation/models.py:1703
#: participation/models.py:1974
msgid "opponent"
msgstr "opposant⋅e"
#: participation/admin.py:132 participation/models.py:1695
#: participation/models.py:1956
#: participation/admin.py:132 participation/models.py:1710
#: participation/models.py:1975
msgid "reviewer"
msgstr "rapporteur⋅rice"
#: participation/admin.py:136 participation/models.py:1702
#: participation/models.py:1957
#: participation/admin.py:136 participation/models.py:1717
#: participation/models.py:1976
msgid "observer"
msgstr "observateur⋅rice"
#: participation/admin.py:192 participation/models.py:1906
#: participation/admin.py:192 participation/models.py:1925
msgid "problem"
msgstr "numéro de problème"
@ -1027,12 +1088,12 @@ msgid "The following user was not found:"
msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :"
#: participation/forms.py:350
msgid "The defender, the opponent and the reviewer must be different."
msgid "The reporter, 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 defender did not work on this problem."
msgid "This reporter did not work on this problem."
msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème."
#: participation/forms.py:373
@ -1239,7 +1300,7 @@ msgid "first phase date"
msgstr "date du premier tour"
#: participation/models.py:327
msgid "limit date to upload the syntheses for the first phase"
msgid "limit date to upload the written reviews for the first phase"
msgstr "date limite pour envoyer les notes de synthèses pour la première phase"
#: participation/models.py:332
@ -1252,7 +1313,7 @@ msgstr ""
"cocher la case lorsque les solutions pour le second tour sont accessibles"
#: participation/models.py:342
msgid "limit date to upload the syntheses for the second phase"
msgid "limit date to upload the written reviews for the second phase"
msgstr "date limite d'envoi des notes de synthèse pour la seconde phase"
#: participation/models.py:347
@ -1265,7 +1326,7 @@ msgstr ""
"cocher la case lorsque les solutions pour le second tour sont accessibles"
#: participation/models.py:357
msgid "limit date to upload the syntheses for the third phase"
msgid "limit date to upload the written reviews for the third phase"
msgstr ""
"date limite pour envoyer les notes de synthèses pour la troisième phase"
@ -1294,7 +1355,7 @@ msgid "Final ranking"
msgstr "Classement final"
#: participation/models.py:481 participation/models.py:553
#: participation/models.py:1312 participation/views.py:1728
#: participation/models.py:1327 participation/views.py:1731
msgid "Team"
msgstr "Équipe"
@ -1326,15 +1387,15 @@ msgstr "Scores jour 3"
msgid "Tweaks day 3"
msgstr "Ajustements 3"
#: participation/models.py:485 participation/models.py:1312
#: participation/views.py:1735
#: participation/models.py:485 participation/models.py:1327
#: participation/views.py:1738
msgid "Total"
msgstr "Total"
#: participation/models.py:485 participation/models.py:553
#: participation/models.py:1312
#: participation/models.py:1327
#: participation/templates/participation/tournament_harmonize.html:14
#: participation/views.py:1738
#: participation/views.py:1741
msgid "Rank"
msgstr "Rang"
@ -1346,7 +1407,7 @@ msgstr "Score"
msgid "Mention"
msgstr "Mention"
#: participation/models.py:698 participation/models.py:1581
#: participation/models.py:698 participation/models.py:1596
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 "
@ -1448,7 +1509,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 defend "
"href='{draw_url}'>this page</a>.</p><p>For the first round, you will present "
"<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 "
@ -1456,70 +1517,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:988
#: participation/models.py:1047
#: participation/models.py:930 participation/models.py:993
#: participation/models.py:1057
#, 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 synthesis "
"sheet on <a href='{passage_url}'>this page</a>.</p>"
"href='{solution_url}'>problem {problem}</a>. You can upload your written "
"review 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:997
#: participation/models.py:1056
#: participation/models.py:939 participation/models.py:1002
#: participation/models.py:1066
#, python-brace-format
msgid ""
"<p>You will report the solution of the team {reviewer} on the <a "
"href='{solution_url}'>problem {problem}. You can upload your synthesis sheet "
"on <a href='{passage_url}'>this page</a>.</p>"
"href='{solution_url}'>problem {problem}</a>. You can upload your written "
"review 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:1007
#: participation/models.py:1066
#: participation/models.py:949 participation/models.py:1012
#: participation/models.py:1076
#, python-brace-format
msgid ""
"<p>You will observe the solution of the team {observer} on the <a "
"href='{solution_url}'>problem {problem}. You can upload your synthesis sheet "
"on <a href='{passage_url}'>this page</a>.</p>"
"href='{solution_url}'>problem {problem}</a>. You can upload your written "
"review 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:969 registration/models.py:629
#: participation/models.py:974 registration/models.py:629
msgid "First round"
msgstr "Premier tour"
#: participation/models.py:981
#: participation/models.py:986
#, python-brace-format
msgid ""
"<p>For the second round, you will defend <a href='{solution_url}'>your "
"<p>For the second round, you will present <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:1027 participation/models.py:1086
#: participation/models.py:1037 participation/models.py:1101
#: registration/models.py:640
msgid "Second round"
msgstr "Second tour"
#: participation/models.py:1040
#: participation/models.py:1050
#, python-brace-format
msgid ""
"<p>For the third round, you will defend <a href='{solution_url}'>your "
"<p>For the third round, you will present <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:1092
#: participation/models.py:1107
#, python-brace-format
msgid ""
"<p>The tournament {tournament} is ended. You can check the results on the <a "
@ -1528,57 +1589,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:1097
#: participation/models.py:1112
msgid "Tournament ended"
msgstr "Tournoi terminé"
#: participation/models.py:1107 participation/models.py:1150
#: participation/models.py:1122 participation/models.py:1165
msgid "participations"
msgstr "participations"
#: participation/models.py:1122 participation/models.py:1123
#: participation/models.py:1124
#: participation/models.py:1137 participation/models.py:1138
#: participation/models.py:1139
#, python-brace-format
msgid "Round {round}"
msgstr "Tour {round}"
#: participation/models.py:1138
#: participation/models.py:1153
msgid "room"
msgstr "salle"
#: participation/models.py:1140
#: participation/models.py:1155
msgid "Room 1"
msgstr "Salle 1"
#: participation/models.py:1141
#: participation/models.py:1156
msgid "Room 2"
msgstr "Salle 2"
#: participation/models.py:1144
#: participation/models.py:1159
msgid "For 5-teams pools only"
msgstr "Pour les poules de 5 équipe uniquement"
#: participation/models.py:1156
#: participation/models.py:1171
msgid "juries"
msgstr "jurys"
#: participation/models.py:1165
#: participation/models.py:1180
msgid "president of the jury"
msgstr "président⋅e du jury"
#: participation/models.py:1172
#: participation/models.py:1187
msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton"
#: participation/models.py:1173
#: participation/models.py:1188
msgid "The link of the BBB visio for this pool."
msgstr "Le lien du salon BBB pour cette poule."
#: participation/models.py:1178
#: participation/models.py:1193
msgid "results available"
msgstr "résultats disponibles"
#: participation/models.py:1179
#: participation/models.py:1194
msgid ""
"Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given."
@ -1587,194 +1648,194 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées."
#: participation/models.py:1211
#: participation/models.py:1226
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:1238 participation/models.py:1312
#: participation/views.py:1483 participation/views.py:1732
#: participation/models.py:1253 participation/models.py:1327
#: participation/views.py:1486 participation/views.py:1735
msgid "Problem"
msgstr "Problème"
#: participation/models.py:1243 participation/views.py:1498
#: participation/models.py:1258 participation/views.py:1501
msgid "Role"
msgstr "Rôle"
#: participation/models.py:1248 participation/views.py:1532
#: participation/views.py:1533
#: participation/models.py:1263 participation/views.py:1535
#: participation/views.py:1536
msgid "Juree"
msgstr "Juré⋅e"
#: participation/models.py:1271 participation/models.py:1597
#: participation/models.py:1619 participation/views.py:1602
#: participation/models.py:1286 participation/models.py:1612
#: participation/models.py:1634 participation/views.py:1605
msgid "Average"
msgstr "Moyenne"
#: participation/models.py:1277 participation/views.py:1621
#: participation/models.py:1292 participation/views.py:1624
msgid "Coefficient"
msgstr "Coefficien"
#: participation/models.py:1278 participation/views.py:1664
#: participation/models.py:1293 participation/views.py:1667
msgid "Subtotal"
msgstr "Sous-total"
#: participation/models.py:1544
#: participation/models.py:1559
#, 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:1632
#: participation/models.py:1647
#, 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:1645
#: participation/models.py:1660
#, 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:1665
#: participation/models.py:1680
msgid "position"
msgstr "position"
#: participation/models.py:1672
msgid "defended solution"
#: participation/models.py:1687
msgid "reported solution"
msgstr "solution défendue"
#: participation/models.py:1710
#: participation/models.py:1725
msgid "penalties"
msgstr "pénalités"
#: participation/models.py:1712
#: participation/models.py:1727
msgid ""
"Number of penalties for the defender. The defender will loose a 0.5 "
"Number of penalties for the reporter. The reporter 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:1838 participation/models.py:1841
#: participation/models.py:1844 participation/models.py:1847
#: participation/models.py:1853 participation/models.py:1856
#: participation/models.py:1859 participation/models.py:1862
#, 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:1852
#: participation/models.py:1867
#, python-brace-format
msgid "Passage of {defender} for problem {problem}"
msgstr "Passage de {defender} pour le problème {problem}"
msgid "Passage of {reporter} for problem {problem}"
msgstr "Passage de {reporter} pour le problème {problem}"
#: participation/models.py:1856 participation/models.py:1865
#: participation/models.py:1950 participation/models.py:1993
#: participation/models.py:1871 participation/models.py:1880
#: participation/models.py:1969 participation/models.py:2012
msgid "passage"
msgstr "passage"
#: participation/models.py:1857
#: participation/models.py:1872
msgid "passages"
msgstr "passages"
#: participation/models.py:1876
#: participation/models.py:1891
msgid "difference"
msgstr "différence"
#: participation/models.py:1877
#: participation/models.py:1892
msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final"
#: participation/models.py:1884
#: participation/models.py:1899
msgid "tweak"
msgstr "harmonisation"
#: participation/models.py:1885
#: participation/models.py:1900
msgid "tweaks"
msgstr "harmonisations"
#: participation/models.py:1913
#: participation/models.py:1932
msgid "solution for the final tournament"
msgstr "solution pour la finale"
#: participation/models.py:1918 participation/models.py:1962
#: participation/models.py:1937 participation/models.py:1981
msgid "file"
msgstr "fichier"
#: participation/models.py:1928
#: participation/models.py:1947
#, 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:1930
#: participation/models.py:1949
msgid "for final"
msgstr "pour la finale"
#: participation/models.py:1933
#: participation/models.py:1952
msgid "solution"
msgstr "solution"
#: participation/models.py:1934
#: participation/models.py:1953
msgid "solutions"
msgstr "solutions"
#: participation/models.py:1968
#: participation/models.py:1987
#, python-brace-format
msgid "Synthesis of {team} as {type} for problem {problem} of {defender}"
msgid "Written review of {team} as {type} for problem {problem} of {reporter}"
msgstr ""
"Note de synthèse de l'équipe {team} en tant que {type} pour le problème "
"{problem} de {defender}"
"{problem} de {reporter}"
#: participation/models.py:1976
msgid "synthesis"
#: participation/models.py:1995
msgid "written review"
msgstr "note de synthèse"
#: participation/models.py:1977
msgid "syntheses"
#: participation/models.py:1996
msgid "written reviews"
msgstr "notes de synthèse"
#: participation/models.py:1986
#: participation/models.py:2005
msgid "jury"
msgstr "jury"
#: participation/models.py:1998
msgid "defender writing note"
#: participation/models.py:2017
msgid "reporter writing note"
msgstr "note d'écrit défenseur⋅se"
#: participation/models.py:2004
msgid "defender oral note"
#: participation/models.py:2023
msgid "reporter oral note"
msgstr "note d'oral défenseur⋅se"
#: participation/models.py:2010
#: participation/models.py:2029
msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e"
#: participation/models.py:2016
#: participation/models.py:2035
msgid "opponent oral note"
msgstr "note d'oral opposant⋅e"
#: participation/models.py:2022
#: participation/models.py:2041
msgid "reviewer writing note"
msgstr "note d'écrit rapporteur⋅rice"
#: participation/models.py:2028
#: participation/models.py:2047
msgid "reviewer oral note"
msgstr "note d'oral du rapporteur⋅rice"
#: participation/models.py:2034
#: participation/models.py:2053
msgid "observer writing note"
msgstr "note d'écrit de l'observateur⋅rice"
#: participation/models.py:2040
#: participation/models.py:2059
msgid "observer oral note"
msgstr "note d'oral de l'observateur⋅rice"
#: participation/models.py:2105
#: participation/models.py:2124
#, python-brace-format
msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}"
#: participation/models.py:2108
#: participation/models.py:2127
msgid "note"
msgstr "note"
#: participation/models.py:2109
#: participation/models.py:2128
msgid "notes"
msgstr "notes"
@ -1910,7 +1971,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_synthesis.html:18
#: participation/templates/participation/upload_written_review.html:23
#: registration/templates/registration/upload_health_sheet.html:17
#: registration/templates/registration/upload_parental_authorization.html:17
#: registration/templates/registration/upload_photo_authorization.html:18
@ -1933,7 +1994,7 @@ msgid "Position:"
msgstr "Position :"
#: participation/templates/participation/passage_detail.html:28
msgid "Defender:"
msgid "Reporter:"
msgstr "Défenseur⋅se :"
#: participation/templates/participation/passage_detail.html:31
@ -1949,11 +2010,11 @@ msgid "Observer:"
msgstr "Observateur⋅rice :"
#: participation/templates/participation/passage_detail.html:42
msgid "Defended solution:"
msgid "Reported solution:"
msgstr "Solution défendue"
#: participation/templates/participation/passage_detail.html:45
msgid "Defender penalties count:"
msgid "Reporter penalties count:"
msgstr "Nombre de pénalités :"
#: participation/templates/participation/passage_detail.html:48
@ -1963,7 +2024,7 @@ msgstr "Notes de synthèse :"
#: participation/templates/participation/passage_detail.html:53
#: participation/templates/participation/pool_detail.html:68
msgid "No synthesis was uploaded yet."
msgid "No review was uploaded yet."
msgstr "Aucune note de synthèse n'a encore été envoyée."
#: participation/templates/participation/passage_detail.html:61
@ -1973,19 +2034,19 @@ msgstr "Modifier les notes"
#: participation/templates/participation/passage_detail.html:66
#: participation/templates/participation/passage_detail.html:187
msgid "Upload synthesis"
msgstr "Envoyer une note de synthèse"
msgid "Upload review"
msgstr "Envoyer la 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 defender writing"
msgid "Average points for the reporter writing"
msgstr "Moyenne de l'écrit de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:90
msgid "Average points for the defender oral"
msgid "Average points for the reporter oral"
msgstr "Moyenne de l'oral de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:98
@ -2013,7 +2074,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 "Defender points"
msgid "Reporter points"
msgstr "Points de l'équipe défenseuse"
#: participation/templates/participation/passage_detail.html:148
@ -2058,7 +2119,7 @@ msgid "Edit jury"
msgstr "Modifier le jury"
#: participation/templates/participation/pool_detail.html:49
msgid "Defended solutions:"
msgid "Reported solutions:"
msgstr "Solutions défendues :"
#: participation/templates/participation/pool_detail.html:55
@ -2374,15 +2435,15 @@ msgid "date of the random draw"
msgstr "date du tirage au sort"
#: participation/templates/participation/tournament_detail.html:41
msgid "date of maximal syntheses submission for the first round"
msgid "date of maximal written reviews 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 syntheses submission for the second round"
msgid "date of maximal written reviews 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 syntheses submission for the third round"
msgid "date of maximal written reviews 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
@ -2462,6 +2523,42 @@ 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"
@ -2504,11 +2601,11 @@ msgstr ""
msgid "Download empty notation sheet"
msgstr "Télécharger la fiche de notation vierge"
#: participation/templates/participation/upload_synthesis.html:9
#: participation/templates/participation/upload_written_review.html:9
msgid "Templates:"
msgstr "Modèles :"
#: participation/templates/participation/upload_synthesis.html:13
#: participation/templates/participation/upload_written_review.html:14
msgid "Warning: non-free format"
msgstr "Attention : format non libre"
@ -2656,96 +2753,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:1022
#: participation/views.py:1023
#, python-brace-format
msgid "Syntheses of team {trigram}.zip"
msgid "Written reviews of team {trigram}.zip"
msgstr "Notes de synthèse de l'équipe {trigram}.zip"
#: participation/views.py:1039 participation/views.py:1054
#: participation/views.py:1040 participation/views.py:1056
#, python-brace-format
msgid "Solutions of {tournament}.zip"
msgstr "Solutions de {tournament}.zip"
#: participation/views.py:1039 participation/views.py:1054
#: participation/views.py:1041 participation/views.py:1057
#, python-brace-format
msgid "Syntheses of {tournament}.zip"
msgid "Written reviews of {tournament}.zip"
msgstr "Notes de synthèse de {tournament}.zip"
#: participation/views.py:1063
#: participation/views.py:1066
#, 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:1064
#: participation/views.py:1067
#, python-brace-format
msgid "Syntheses for pool {pool} of tournament {tournament}.zip"
msgid "Written reviews for pool {pool} of tournament {tournament}.zip"
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:1106
#: participation/views.py:1109
#, 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:1122
#: participation/views.py:1125
#, python-brace-format
msgid "The jury {name} is already in the pool!"
msgstr "{name} est déjà dans la poule !"
#: participation/views.py:1142
#: participation/views.py:1145
msgid "New jury account"
msgstr "Nouveau compte de juré⋅e"
#: participation/views.py:1163
#: participation/views.py:1166
#, 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:1199
#: participation/views.py:1202
#, 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:1225
#: participation/views.py:1228
#, 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:1253
#: participation/views.py:1256
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:1269
#: participation/views.py:1272
msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées."
#: participation/views.py:1504
msgid "Defender"
#: participation/views.py:1507
msgid "Reporter"
msgstr "Défenseur⋅se"
#: participation/views.py:1510
#: participation/views.py:1513
msgid "Opponent"
msgstr "Opposant⋅e"
#: participation/views.py:1517
#: participation/views.py:1520
msgid "Reviewer"
msgstr "Rapporteur⋅rice"
#: participation/views.py:1524
#: participation/views.py:1527
msgid "Observer"
msgstr "Observateur⋅rice"
#: participation/views.py:1895
#: participation/views.py:1898
#, 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:1900
#: participation/views.py:1903
#, python-brace-format
msgid "Notation sheets of {tournament}.zip"
msgstr "Feuilles de notation de {tournament}.zip"
#: participation/views.py:2067
msgid "You can't upload a synthesis after the deadline."
#: participation/views.py:2070
msgid "You can't upload a written review 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, Synthesis, Team, Tournament, Tweak
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
class ParticipationInline(admin.StackedInline):
@ -32,8 +32,8 @@ class SolutionInline(admin.TabularInline):
show_change_link = True
class SynthesisInline(admin.TabularInline):
model = Synthesis
class WrittenReviewInline(admin.TabularInline):
model = WrittenReview
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 = ('defender', 'opponent', 'reviewer', 'observer',)
autocomplete_fields = ('reporter', '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, SynthesisInline,)
inlines = (SolutionInline, WrittenReviewInline,)
@admin.register(Pool)
@ -113,17 +113,17 @@ class PoolAdmin(admin.ModelAdmin):
@admin.register(Passage)
class PassageAdmin(admin.ModelAdmin):
list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reviewer_trigram',
list_display = ('__str__', 'reporter_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', 'defender', 'opponent', 'reviewer', 'observer',)
autocomplete_fields = ('pool', 'reporter', 'opponent', 'reviewer', 'observer',)
inlines = (NoteInline,)
@admin.display(description=_("defender"), ordering='defender__team__trigram')
def defender_trigram(self, record: Passage):
return record.defender.team.trigram
@admin.display(description=_("reporter"), ordering='reporter__team__trigram')
def reporter_trigram(self, record: Passage):
return record.reporter.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', 'defender_writing', 'defender_oral',
list_display = ('passage', 'pool', 'jury', 'reporter_writing', 'reporter_oral',
'opponent_writing', 'opponent_oral', 'reviewer_writing', 'reviewer_oral',
'observer_writing', 'observer_oral',)
list_filter = ('passage__pool__letter', 'passage__solution_number', 'jury',
'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral')
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__defender__team__trigram',)
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__reporter__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(Synthesis)
class SynthesisAdmin(admin.ModelAdmin):
list_display = ('participation', 'type', 'defender', 'passage',)
@admin.register(WrittenReview)
class WrittenReviewAdmin(admin.ModelAdmin):
list_display = ('participation', 'type', 'reporter', 'passage',)
list_filter = ('participation__tournament', 'type', 'passage__solution_number',)
search_fields = ('participation__team__name', 'participation__team__trigram',)
autocomplete_fields = ('participation', 'passage',)
@admin.display(description=_("defender"))
def defender(self, record: Synthesis):
return record.passage.defender
@admin.display(description=_("reporter"))
def reporter(self, record: WrittenReview):
return record.passage.reporter
@admin.display(description=_("problem"))
def problem(self, record: Synthesis):
def problem(self, record: WrittenReview):
return record.passage.solution_number

View File

@ -3,7 +3,7 @@
from rest_framework import serializers
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
class NoteSerializer(serializers.ModelSerializer):
@ -38,9 +38,9 @@ class SolutionSerializer(serializers.ModelSerializer):
fields = '__all__'
class SynthesisSerializer(serializers.ModelSerializer):
class WrittenReviewSerializer(serializers.ModelSerializer):
class Meta:
model = Synthesis
model = WrittenReview
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', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'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',
'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, SynthesisViewSet, TeamViewSet, TournamentViewSet, TweakViewSet
SolutionViewSet, TeamViewSet, TournamentViewSet, TweakViewSet, WrittenReviewViewSet
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, SynthesisSerializer, TeamSerializer, TournamentSerializer, TweakSerializer
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
SolutionSerializer, TeamSerializer, TournamentSerializer, TweakSerializer, WrittenReviewSerializer
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
class NoteViewSet(ModelViewSet):
queryset = Note.objects.all()
serializer_class = NoteSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['jury', 'passage', 'defender_writing', 'defender_oral', 'opponent_writing',
filterset_fields = ['jury', 'passage', 'reporter_writing', 'reporter_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', 'defender', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
filterset_fields = ['pool', 'solution_number', 'reporter', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
class PoolViewSet(ModelViewSet):
@ -44,9 +44,9 @@ class SolutionViewSet(ModelViewSet):
filterset_fields = ['participation', 'number', 'problem', 'final_solution', ]
class SynthesisViewSet(ModelViewSet):
queryset = Synthesis.objects.all()
serializer_class = SynthesisSerializer
class WrittenReviewViewSet(ModelViewSet):
queryset = WrittenReview.objects.all()
serializer_class = WrittenReviewSerializer
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', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'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',
'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, Synthesis, Team, Tournament
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
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['syntheses_third_phase_limit']
del self.fields['reviews_third_phase_limit']
if not settings.PAYMENT_MANAGEMENT:
del self.fields['price']
@ -151,13 +151,13 @@ 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'),
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
'reviews_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'),
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
'reviews_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'),
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
'reviews_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'organizers': forms.SelectMultiple(attrs={
'class': 'selectpicker',
@ -345,21 +345,21 @@ class UploadNotesForm(forms.Form):
class PassageForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
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"],
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"],
problem=cleaned_data["solution_number"]).exists():
self.add_error("solution_number", _("This defender did not work on this problem."))
self.add_error("solution_number", _("This reporter did not work on this problem."))
return cleaned_data
class Meta:
model = Passage
fields = ('position', 'solution_number', 'defender', 'opponent', 'reviewer', 'opponent', 'defender_penalties',)
fields = ('position', 'solution_number', 'reporter', 'opponent', 'reviewer', 'opponent', 'reporter_penalties',)
class SynthesisForm(forms.ModelForm):
class WrittenReviewForm(forms.ModelForm):
def clean_file(self):
if "file" in self.files:
file = self.files["file"]
@ -375,16 +375,16 @@ class SynthesisForm(forms.ModelForm):
def save(self, commit=True):
"""
Don't save a synthesis with this way. Use a view instead
Don't save a written review with this way. Use a view instead
"""
class Meta:
model = Synthesis
model = WrittenReview
fields = ('file',)
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ('defender_writing', 'defender_oral', 'opponent_writing',
fields = ('reporter_writing', 'reporter_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()
defender_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, defender=team2)
reporter_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reporter=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()
defender_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, defender=team2)
reporter_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reporter=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. {defender_passage_1.solution_number}")
line.extend([defender_passage_1.average_defender_writing, defender_passage_1.average_defender_oral,
line.append(f"Pb. {reporter_passage_1.solution_number}")
line.extend([reporter_passage_1.average_reporter_writing, reporter_passage_1.average_reporter_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. {defender_passage_2.solution_number}")
line.extend([defender_passage_2.average_defender_writing, defender_passage_2.average_defender_oral,
line.append(f"Pb. {reporter_passage_2.solution_number}")
line.extend([reporter_passage_2.average_reporter_writing, reporter_passage_2.average_reporter_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

@ -0,0 +1,75 @@
# 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

@ -0,0 +1,133 @@
# 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,
)
syntheses_first_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the first phase"),
reviews_first_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the written reviews for the first phase"),
default=timezone.now,
)
@ -338,8 +338,8 @@ class Tournament(models.Model):
default=False,
)
syntheses_second_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the second phase"),
reviews_second_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the written reviews for the second phase"),
default=timezone.now,
)
@ -353,8 +353,8 @@ class Tournament(models.Model):
default=False,
)
syntheses_third_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the third phase"),
reviews_third_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the written reviews for the third phase"),
default=timezone.now,
)
@ -442,10 +442,10 @@ class Tournament(models.Model):
return Solution.objects.filter(participation__tournament=self)
@property
def syntheses(self):
def written_reviews(self):
if self.final:
return Synthesis.objects.filter(final_solution=True)
return Synthesis.objects.filter(participation__tournament=self)
return WrittenReview.objects.filter(final_solution=True)
return WrittenReview.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, defender=participation)
passage1 = Passage.objects.get(pool__tournament=self, pool__round=1, reporter=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, defender=participation).exists():
passage2 = Passage.objects.get(pool__tournament=self, pool__round=2, defender=participation)
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)
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, defender=participation).exists():
passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, defender=participation)
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)
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.syntheses_first_phase_limit + timedelta(hours=2):
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, defender=self)
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)
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
defender_text = _("<p>The solutions draw is ended. You can check the result on "
reporter_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 defend "
"<p>For the first round, you will present "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
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)
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)
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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
observer_content = format_lazy(observer_text,
observer=observer_passage.defender.team.trigram,
observer=observer_passage.reporter.team.trigram,
solution_url=solution_url,
problem=observer_passage.solution_number, passage_url=passage_url)
else:
observer_content = ""
if settings.TFJM_APP == "TFJM":
syntheses_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
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"])
else:
syntheses_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
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"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
reviews_templates_content = f"<p>{_('Templates:')} {reviews_templates}</p>"
content = defender_content + opponent_content + reviewer_content + observer_content \
+ syntheses_templates_content
content = reporter_content + opponent_content + reviewer_content + observer_content \
+ reviews_templates_content
informations.append({
'title': _("First round"),
'type': "info",
'priority': 1,
'content': content,
})
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)
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)
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
defender_text = _("<p>For the second round, you will defend "
reporter_text = _("<p>For the second round, you will present "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
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)
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)
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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
observer_content = format_lazy(observer_text,
observer=observer_passage.defender.team.trigram,
observer=observer_passage.reporter.team.trigram,
solution_url=solution_url,
problem=observer_passage.solution_number, passage_url=passage_url)
else:
observer_content = ""
if settings.TFJM_APP == "TFJM":
syntheses_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
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"])
else:
syntheses_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
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"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
reviews_templates_content = f"<p>{_('Templates:')} {reviews_templates}</p>"
content = defender_content + opponent_content + reviewer_content + observer_content \
+ syntheses_templates_content
content = reporter_content + opponent_content + reviewer_content + observer_content \
+ reviews_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.syntheses_third_phase_limit + timedelta(hours=2):
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, defender=self)
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)
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
defender_text = _("<p>For the third round, you will defend "
reporter_text = _("<p>For the third round, you will present "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
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)
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)
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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reviewer_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.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 synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.defended_solution.file.url
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
solution_url = observer_passage.reported_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
observer_content = format_lazy(observer_text,
observer=observer_passage.defender.team.trigram,
observer=observer_passage.reporter.team.trigram,
solution_url=solution_url,
problem=observer_passage.solution_number, passage_url=passage_url)
else:
observer_content = ""
if settings.TFJM_APP == "TFJM":
syntheses_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
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"])
else:
syntheses_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
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"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
reviews_templates_content = f"<p>{_('Templates:')} {reviews_templates}</p>"
content = defender_content + opponent_content + reviewer_content + observer_content \
+ syntheses_templates_content
content = reporter_content + opponent_content + reviewer_content + observer_content \
+ reviews_templates_content
informations.append({
'title': _("Second round"),
'type': "info",
@ -1204,7 +1204,7 @@ class Pool(models.Model):
@property
def solutions(self):
return [passage.defended_solution for passage in self.passages.all()]
return [passage.reported_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"{_('Defender')} ({passage.defender.team.trigram})", "",
sum(([f"{_('Reporter')} ({passage.reporter.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.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
line.extend([note.reporter_writing, note.reporter_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_defender_writing, passage.coeff_defender_oral,
coeffs = sum(([passage.coeff_reporter_writing, passage.coeff_reporter_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.defender
defender_passage = Passage.objects.get(defender=participation,
participation = passage.reporter
reporter_passage = Passage.objects.get(reporter=participation,
pool__tournament=self.tournament, pool__round=self.round)
defender_row = 5 + defender_passage.pool.juries.count()
defender_col = defender_passage.position - 1
reporter_row = 5 + reporter_passage.pool.juries.count()
reporter_col = reporter_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')} {defender_passage.pool.short_name}'"
f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender
formula += (f"'{_('Pool')} {reporter_passage.pool.short_name}'"
f"!{getcol(min_column + reporter_col * passage_width)}{reporter_row + 3}") # Reporter
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')} {defender_passage.pool.short_name}'"
f"!${getcol(3 + defender_col * passage_width)}$1",
f"='{_('Pool')} {reporter_passage.pool.short_name}'"
f"!${getcol(3 + reporter_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(defender_penalties__gte=1).all()]
for passage in self.passages.filter(reporter_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=_("defended solution"),
verbose_name=_("reported solution"),
choices=[
(i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, len(settings.PROBLEMS) + 1)
],
)
defender = models.ForeignKey(
reporter = models.ForeignKey(
Participation,
on_delete=models.PROTECT,
verbose_name=_("defender"),
verbose_name=_("reporter"),
related_name="+",
)
@ -1721,17 +1721,17 @@ class Passage(models.Model):
default=None,
)
defender_penalties = models.PositiveSmallIntegerField(
reporter_penalties = models.PositiveSmallIntegerField(
verbose_name=_("penalties"),
default=0,
help_text=_("Number of penalties for the defender. "
"The defender will loose a 0.5 coefficient per penalty."),
help_text=_("Number of penalties for the reporter. "
"The reporter will loose a 0.5 coefficient per penalty."),
)
@property
def defended_solution(self) -> "Solution":
def reported_solution(self) -> "Solution":
return Solution.objects.get(
participation=self.defender,
participation=self.reporter,
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_defender_writing(self) -> float:
return self.avg(note.defender_writing for note in self.notes.all())
def average_reporter_writing(self) -> float:
return self.avg(note.reporter_writing for note in self.notes.all())
@property
def coeff_defender_writing(self) -> float:
def coeff_reporter_writing(self) -> float:
return 1 if settings.TFJM_APP == "TFJM" else 2
@property
def average_defender_oral(self) -> float:
return self.avg(note.defender_oral for note in self.notes.all())
def average_reporter_oral(self) -> float:
return self.avg(note.reporter_oral for note in self.notes.all())
@property
def coeff_defender_oral(self) -> float:
def coeff_reporter_oral(self) -> float:
coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
coeff *= 1 - 0.25 * self.defender_penalties
coeff *= 1 - 0.25 * self.reporter_penalties
return coeff
@property
def average_defender(self) -> float:
return (self.coeff_defender_writing * self.average_defender_writing
+ self.coeff_defender_oral * self.average_defender_oral)
def average_reporter(self) -> float:
return (self.coeff_reporter_writing * self.average_reporter_writing
+ self.coeff_reporter_oral * self.average_reporter_oral)
@property
def average_opponent_writing(self) -> float:
@ -1827,8 +1827,8 @@ class Passage(models.Model):
@property
def averages(self):
yield self.average_defender_writing
yield self.average_defender_oral
yield self.average_reporter_writing
yield self.average_reporter_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_defender if participation == self.defender else self.average_opponent \
avg = self.average_reporter if participation == self.reporter 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.defender not in self.pool.participations.all():
if self.reporter not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.")
.format(trigram=self.defender.team.trigram))
.format(trigram=self.reporter.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 {defender} for problem {problem}")\
.format(defender=self.defender.team, problem=self.solution_number)
return _("Passage of {reporter} for problem {problem}")\
.format(reporter=self.reporter.team, problem=self.solution_number)
class Meta:
verbose_name = _("passage")
@ -1905,8 +1905,12 @@ 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 f"syntheses/{instance.participation.team.trigram}_{instance.type}_{instance.passage.pk}"
return get_review_filename(instance, filename)
class Solution(models.Model):
@ -1951,7 +1955,7 @@ class Solution(models.Model):
ordering = ('participation__team__trigram', 'final_solution', 'problem',)
class Synthesis(models.Model):
class WrittenReview(models.Model):
participation = models.ForeignKey(
Participation,
on_delete=models.CASCADE,
@ -1961,7 +1965,7 @@ class Synthesis(models.Model):
passage = models.ForeignKey(
Passage,
on_delete=models.CASCADE,
related_name="syntheses",
related_name="written_reviews",
verbose_name=_("passage"),
)
@ -1980,16 +1984,16 @@ class Synthesis(models.Model):
)
def __str__(self):
return _("Synthesis of {team} as {type} for problem {problem} of {defender}").format(
return _("Written review of {team} as {type} for problem {problem} of {reporter}").format(
team=self.participation.team.trigram,
type=self.get_type_display(),
problem=self.passage.solution_number,
defender=self.passage.defender.team.trigram,
reporter=self.passage.reporter.team.trigram,
)
class Meta:
verbose_name = _("synthesis")
verbose_name_plural = _("syntheses")
verbose_name = _("written review")
verbose_name_plural = _("written reviews")
unique_together = (('participation', 'passage', 'type', ), )
ordering = ('passage__pool__round', 'type',)
@ -2009,14 +2013,14 @@ class Note(models.Model):
related_name="notes",
)
defender_writing = models.PositiveSmallIntegerField(
verbose_name=_("defender writing note"),
reporter_writing = models.PositiveSmallIntegerField(
verbose_name=_("reporter writing note"),
choices=[(i, i) for i in range(0, 21)],
default=0,
)
defender_oral = models.PositiveSmallIntegerField(
verbose_name=_("defender oral note"),
reporter_oral = models.PositiveSmallIntegerField(
verbose_name=_("reporter oral note"),
choices=[(i, i) for i in range(0, 21)],
default=0,
)
@ -2058,8 +2062,8 @@ class Note(models.Model):
)
def get_all(self):
yield self.defender_writing
yield self.defender_oral
yield self.reporter_writing
yield self.reporter_oral
yield self.opponent_writing
yield self.opponent_oral
yield self.reviewer_writing
@ -2068,10 +2072,10 @@ class Note(models.Model):
yield self.observer_writing
yield self.observer_oral
def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int,
def set_all(self, reporter_writing: int, reporter_oral: int, opponent_writing: int, opponent_oral: int,
reviewer_writing: int, reviewer_oral: int, observer_writing: int = 0, observer_oral: int = 0):
self.defender_writing = defender_writing
self.defender_oral = defender_oral
self.reporter_writing = reporter_writing
self.reporter_oral = reporter_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
defender = tables.LinkColumn(
reporter = tables.LinkColumn(
"participation:passage_detail",
args=[tables.A("id")],
verbose_name=_("defender").capitalize,
verbose_name=_("reporter").capitalize,
)
def render_defender(self, value):
def render_reporter(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 = ('defender', 'opponent', 'reviewer', 'observer', 'solution_number', )
fields = ('reporter', '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', 'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
fields = ('jury', 'reporter_writing', 'reporter_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.defender.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
<h5>{% trans "Defense of" %} {{ note.passage.reporter.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 "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 "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 "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 "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 "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 "Defender penalties count:" %}</dt>
<dd class="col-sm-9">{{ passage.defender_penalties }}</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 "Syntheses:" %}</dt>
<dd class="col-sm-9">
{% for synthesis in passage.syntheses.all %}
<a href="{{ synthesis.file.url }}">{{ synthesis }}{% if not forloop.last %}, {% endif %}</a>
{% for review in passage.written_reviews.all %}
<a href="{{ review.file.url }}">{{ review }}{% if not forloop.last %}, {% endif %}</a>
{% empty %}
{% trans "No synthesis was uploaded yet." %}
{% trans "No review 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="#uploadSynthesisModal">{% trans "Upload synthesis" %}</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadWrittenReviewModal">{% trans "Upload review" %}</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 defender writing" %}
({{ passage.defender.team.trigram }}) :
{% trans "Average points for the reporter writing" %}
({{ passage.reporter.team.trigram }}) :
</dt>
<dd class="col-sm-4">
{{ passage.average_defender_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
{{ passage.average_reporter_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
</dd>
<dt class="col-sm-8">
{% trans "Average points for the defender oral" %}
({{ passage.defender.team.trigram }}) :
{% trans "Average points for the reporter oral" %}
({{ passage.reporter.team.trigram }}) :
</dt>
<dd class="col-sm-4">
{{ passage.average_defender_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
{{ passage.average_reporter_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 "Defender points" %}
({{ passage.defender.team.trigram }}) :
{% trans "Reporter points" %}
({{ passage.reporter.team.trigram }}) :
</dt>
<dd class="col-sm-4">
{{ passage.average_defender|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
{{ passage.average_reporter|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 synthesis" as modal_title %}
{% trans "Upload review" as modal_title %}
{% trans "Upload" as modal_button %}
{% url "participation:upload_synthesis" pk=passage.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadSynthesis" modal_enctype="multipart/form-data" %}
{% url "participation:upload_review" pk=passage.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadWrittenReview" 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("uploadSynthesis", "{% url "participation:upload_synthesis" pk=passage.pk %}")
initModal("uploadWrittenReview", "{% url "participation:upload_review" pk=passage.pk %}")
{% endif %}
});
})
</script>
{% endblock %}

View File

@ -46,10 +46,10 @@
</a>
</dd>
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
<dt class="col-sm-3">{% trans "Reported solutions:" %}</dt>
<dd class="col-sm-9">
{% for passage in pool.passages.all %}
<a href="{{ passage.defended_solution.file.url }}">{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
<a href="{{ passage.reported_solution.file.url }}">{{ passage.reporter.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.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 %}
{{ 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 %}
{% empty %}
{% trans "No synthesis was uploaded yet." %}
{% trans "No review was uploaded yet." %}
{% endfor %}
</li>
{% endfor %}
</ul>
<a href="{% url 'participation:pool_download_syntheses' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
<a href="{% url 'participation:pool_download_written_reviews' 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.defender.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
\item D\'efenseur\textperiodcentered{}se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.reporter.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.defender.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.reporter.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.defender.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.reporter.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 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 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 second round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_second_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>
{% if TFJM.APP == "ETEAM" %}
<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>
<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>
{% 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 %}">
Validated team participant data spreadsheet
{% trans "Validated team participant data spreadsheet" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}?all">
All teams participant data spreadsheet
{% trans "All teams participant data spreadsheet" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_authorizations" tournament_id=tournament.id %}">
Archive of all authorisations sorted by team and person
{% trans "Archive of all authorisations sorted by team and person" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}">
Archive of all submitted solutions sorted by team
{% trans "Archive of all submitted solutions sorted by team" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=problem">
Archive of all sent solutions sorted by problem
{% trans "Archive of all sent solutions sorted by problem" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=pool">
Archive of all sent solutions sorted by pool
{% trans "Archive of all sent solutions sorted by pool" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_syntheses" tournament_id=tournament.id %}?sort_by=pool">
Archive of all summary notes sorted by pool and passage
<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>
</li>
<li>
<a href="https://docs.google.com/spreadsheets/d/{{ tournament.notes_sheet_id }}/edit">
<i class="fas fa-table"></i>
Note spreadsheet on Google Sheets
{% trans "Note spreadsheet on Google Sheets" %}
</a>
</li>
<li>
<a href="{% url "participation:tournament_notation_sheets" tournament_id=tournament.id %}">
Archive of all printable note sheets sorted by pool
{% trans "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, SynthesisUploadView, \
SolutionsDownloadView, SolutionUploadView, \
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
TournamentHarmonizeNoteView, TournamentHarmonizeView, TournamentListView, TournamentPaymentsView, \
TournamentPublishNotesView, TournamentUpdateView
TournamentPublishNotesView, TournamentUpdateView, WrittenReviewUploadView
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>/syntheses/", SolutionsDownloadView.as_view(),
name="tournament_syntheses"),
path("tournament/<int:tournament_id>/written_reviews/", SolutionsDownloadView.as_view(),
name="tournament_written_reviews"),
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>/syntheses/", SolutionsDownloadView.as_view(), name="pool_download_syntheses"),
path("pools/<int:pool_id>/written_reviews/", SolutionsDownloadView.as_view(), name="pool_download_written_reviews"),
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>/solution/", SynthesisUploadView.as_view(), name="upload_synthesis"),
path("pools/passages/<int:pk>/written_review/", WrittenReviewUploadView.as_view(), name="upload_written_review"),
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, SynthesisForm, TeamForm, TournamentForm, \
UploadNotesForm, ValidateParticipationForm
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
PoolForm, RequestValidationForm, SolutionForm, TeamForm, TournamentForm, UploadNotesForm, \
ValidateParticipationForm, WrittenReviewForm
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
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 syntheses as a ZIP archive.
Download all solutions or written reviews as a ZIP archive.
"""
def dispatch(self, request, *args, **kwargs):
@ -1018,11 +1018,12 @@ 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()
syntheses = Synthesis.objects.filter(participation=team.participation).all()
filename = _("Solutions of team {trigram}.zip") if is_solution else _("Syntheses of team {trigram}.zip")
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")
filename = filename.format(trigram=team.trigram)
def prefix(s: Solution | Synthesis) -> str:
def prefix(s: Solution | WrittenReview) -> str:
return ""
elif 'tournament_id' in kwargs:
tournament = Tournament.objects.get(pk=kwargs["tournament_id"])
@ -1035,11 +1036,12 @@ class SolutionsDownloadView(VolunteerMixin, View):
for sol in pool.solutions:
sol.pool = pool
solutions.append(sol)
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution \
else _("Written reviews of {tournament}.zip")
filename = filename.format(tournament=tournament.name)
def prefix(s: Solution | Synthesis) -> str:
def prefix(s: Solution | WrittenReview) -> str:
pool = s.pool if is_solution else s.passage.pool
p = f"Poule {pool.short_name}/"
if not is_solution:
@ -1050,27 +1052,28 @@ class SolutionsDownloadView(VolunteerMixin, View):
solutions = Solution.objects.filter(participation__tournament=tournament).all()
else:
solutions = Solution.objects.filter(final_solution=True).all()
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
filename = _("Solutions of {tournament}.zip") if is_solution \
else _("Written reviews of {tournament}.zip")
filename = filename.format(tournament=tournament.name)
def prefix(s: Solution | Synthesis) -> str:
def prefix(s: Solution | WrittenReview) -> 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
syntheses = Synthesis.objects.filter(passage__pool=pool).all()
written_reviews = WrittenReview.objects.filter(passage__pool=pool).all()
filename = _("Solutions for pool {pool} of tournament {tournament}.zip") \
if is_solution else _("Syntheses for pool {pool} of tournament {tournament}.zip")
if is_solution else _("Written reviews for pool {pool} of tournament {tournament}.zip")
filename = filename.format(pool=pool.short_name,
tournament=pool.tournament.name)
def prefix(s: Solution | Synthesis) -> str:
def prefix(s: Solution | WrittenReview) -> str:
return ""
output = BytesIO()
zf = ZipFile(output, "w")
for s in (solutions if is_solution else syntheses):
for s in (solutions if is_solution else written_reviews):
if s.file.storage.exists(s.file.path):
zf.write("media/" + s.file.name, prefix(s) + f"{s}.pdf")
@ -1500,10 +1503,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_role.addElement(role_tc)
header_role.addElement(CoveredTableCell())
for i in range(pool_size):
defender_tc = TableCell(valuetype="string", stylename=title_style_left)
defender_tc.addElement(P(text=_("Defender")))
defender_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(defender_tc)
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)
header_role.addElement(CoveredTableCell())
opponent_tc = TableCell(valuetype="string", stylename=title_style)
@ -1536,13 +1539,13 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_notes.addElement(CoveredTableCell())
for i in range(pool_size):
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
defender_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
header_notes.addElement(defender_w_tc)
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_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)
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)
opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
opponent_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
@ -1623,13 +1626,13 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
coeff_row.addElement(coeff_tc)
coeff_row.addElement(CoveredTableCell())
for passage in self.object.passages.all():
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_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_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)
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)
opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style)
opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
@ -1668,12 +1671,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)
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}]"
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}]"
f" + [.{def_o_col}{max_row + 1}] * [.{def_o_col}{max_row + 2}]")
subtotal_row.addElement(defender_tc)
subtotal_row.addElement(reporter_tc)
subtotal_row.addElement(CoveredTableCell())
opp_w_col = getcol(min_column + passage_width * i + 2)
@ -1745,7 +1748,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.defender.team.name} ({passage.defender.team.trigram})"))
team_tc.addElement(P(text=f"{passage.reporter.team.name} ({passage.reporter.team.trigram})"))
team_tc.setAttribute('numbercolumnsspanned', "2")
team_row.addElement(team_tc)
@ -1755,17 +1758,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
team_row.addElement(problem_tc)
defender_pos = passage.position - 1
opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1
reviewer_pos = self.object.passages.get(reviewer=passage.defender).position - 1
observer_pos = self.object.passages.get(observer=passage.defender).position - 1 \
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 \
if has_observer else None
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
score_tc = TableCell(valuetype="float", value=self.object.average(passage.reporter),
stylename=style_bot if passage.position == pool_size else style)
score_tc.addElement(P(text=self.object.average(passage.defender)))
score_tc.addElement(P(text=self.object.average(passage.reporter)))
formula = "of:="
formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender
formula += getcol(min_column + reporter_pos * passage_width) + str(max_row + 3) # Reporter
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:
@ -1775,9 +1778,9 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
team_row.addElement(score_tc)
score_col = 'C'
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.defender) + 1,
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.reporter) + 1,
stylename=style_botright if passage.position == pool_size else style_right)
rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1)))
rank_tc.addElement(P(text=str(sorted_participations.index(passage.reporter) + 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}])")
@ -1981,7 +1984,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.defender, passage.opponent, passage.reviewer, passage.observer]:
and reg.team.participation in [passage.reporter, passage.opponent, passage.reviewer, passage.observer]:
return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission()
@ -2002,8 +2005,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['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['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['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})"
@ -2028,9 +2031,9 @@ class PassageUpdateView(VolunteerMixin, UpdateView):
return self.handle_no_permission()
class SynthesisUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_synthesis.html"
form_class = SynthesisForm
class WrittenReviewUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_written_review.html"
form_class = WrittenReviewForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.registration.participates:
@ -2057,14 +2060,14 @@ class SynthesisUploadView(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 = Synthesis.objects.filter(participation=self.participation,
syn_qs = WrittenReview.objects.filter(participation=self.participation,
passage=self.passage,
type=form_syn.type).all()
deadline = self.passage.pool.tournament.syntheses_first_phase_limit if self.passage.pool.round == 1 \
else self.passage.pool.tournament.syntheses_second_phase_limit
deadline = self.passage.pool.tournament.reviews_first_phase_limit if self.passage.pool.round == 1 \
else self.passage.pool.tournament.reviews_second_phase_limit
if syn_qs.exists() and timezone.now() > deadline:
form.add_error(None, _("You can't upload a synthesis after the deadline."))
form.add_error(None, _("You can't upload a written review after the deadline."))
return self.form_invalid(form)
# Drop previous solution if existing
@ -2098,8 +2101,8 @@ class NoteUpdateView(VolunteerMixin, UpdateView):
def get_form(self, form_class=None):
form = super().get_form(form_class)
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['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})"
form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.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, Synthesis, Tournament
from participation.models import Passage, Solution, Tournament, WrittenReview
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(defender=user.registration.team.participation)
passage_participant_qs = Passage.objects.filter(Q(reporter=user.registration.team.participation)
| Q(opponent=user.registration.team.participation)
| Q(reviewer=user.registration.team.participation)
| Q(observer=user.registration.team.participation),
defender=solution.participation,
reporter=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()),
defender=solution.participation,
reporter=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 SynthesisView(LoginRequiredMixin, View):
class WrittenReviewView(LoginRequiredMixin, View):
"""
Display the sent synthesis.
Display the sent written reviews.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/syntheses/{filename}"
path = f"media/reviews/{filename}"
if not os.path.exists(path):
raise Http404
synthesis = Synthesis.objects.get(file__endswith=filename)
review = WrittenReview.objects.get(file__endswith=filename)
user = request.user
if not (user.registration.is_admin or user.registration.is_volunteer
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):
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):
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(synthesis) + f".{ext}"
true_file_name = str(review) + 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{~~~~} defended by team \underline{~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Problem \underline{~~~~} reported 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, SynthesisView, VaccineSheetView
ReceiptView, SolutionView, VaccineSheetView, WrittenReviewView
from .views import AdminSearchView
@ -61,8 +61,8 @@ urlpatterns = [
path('media/solutions/<str:filename>/', SolutionView.as_view(),
name='solution'),
path('media/syntheses/<str:filename>/', SynthesisView.as_view(),
name='synthesis'),
path('media/reviews/<str:filename>/', WrittenReviewView.as_view(),
name='reviews'),
]
if settings.DEBUG: