diff --git a/draw/consumers.py b/draw/consumers.py
index 7719b8d..2a7deb8 100644
--- a/draw/consumers.py
+++ b/draw/consumers.py
@@ -122,6 +122,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
self.tournament = await Tournament.objects.filter(pk=self.tournament_id)\
.prefetch_related('draw__current_round__current_pool__current_team__participation__team').aget()
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
+
match content['type']:
case 'set_language':
# Update the translation language
@@ -183,7 +185,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Create the draw
draw = await Draw.objects.acreate(tournament=self.tournament)
r1 = None
- for i in [1, 2]:
+ for i in range(1, settings.NB_ROUNDS + 1):
# Create the round
r = await Round.objects.acreate(draw=draw, number=i)
if i == 1:
@@ -233,8 +235,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': 'Tirage au sort du TFJM²',
- 'body': "Le tirage au sort du tournoi de "
- f"{self.tournament.name} a commencé !"})
+ 'body': _("The draw of tournament {tournament} started!")
+ .format(tournament=self.tournament.name)})
async def draw_start(self, content) -> None:
"""
@@ -403,8 +405,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(
f"team-{dup.participation.team.trigram}",
{'tid': self.tournament_id, 'type': 'draw.notify', 'title': 'Tirage au sort du TFJM²',
- 'body': 'Votre score de dé est identique à celui de une ou plusieurs équipes. '
- 'Veuillez le relancer.'}
+ 'body': _("Your dice score is identical to the one of one or multiple teams. "
+ "Please relaunch it.")}
)
# Alert the tournament
await self.channel_layer.group_send(
@@ -417,7 +419,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
return error
- async def process_dice_select_poules(self):
+ async def process_dice_select_poules(self): # noqa: C901
"""
Called when all teams launched their dice.
Place teams into pools and order their passage.
@@ -448,7 +450,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# We can add a joker team if there is not already a team in the pool that was in the same pool
# in the first round, and such that the number of such jokers is exactly the free space of the current pool.
# Exception: if there is one only pool with 5 teams, we exchange the first and the last teams of the pool.
- if not self.tournament.final:
+ if not self.tournament.final and settings.TFJM_APP == "TFJM":
tds_copy = sorted(tds, key=lambda td: (td.passage_index, -td.pool.letter,))
jokers = [td for td in tds if td.passage_index == 4]
round2 = await self.tournament.draw.round_set.filter(number=2).aget()
@@ -502,12 +504,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.tournament.draw.current_round.asave()
# Display dice result in the header of the information alert
- msg = "Les résultats des dés sont les suivants : "
- msg += ", ".join(f"{td.participation.team.trigram} ({td.passage_dice})" for td in tds)
- msg += ". L'ordre de passage et les compositions des différentes poules sont affiché⋅es sur le côté. "
- msg += "Les ordres de passage pour le premier tour sont déterminés à partir des scores des dés, "
- msg += "dans l'ordre croissant. Pour le deuxième tour, les ordres de passage sont déterminés à partir "
- msg += "des ordres de passage du premier tour."
+ trigrams = ", ".join(f"{td.participation.team.trigram} ({td.passage_dice})" for td in tds)
+ msg = _("The dice results are the following: {trigrams}. "
+ "The passage order and the compositions of the different pools are displayed on the side. "
+ "The passage orders for the first round are determined from the dice scores, in increasing order. "
+ "For the second round, the passage orders are determined from the passage orders of the first round.") \
+ .format(trigrams=trigrams)
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@@ -531,18 +533,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
- # First send the second pool to have the good team order
- r2 = await self.tournament.draw.round_set.filter(number=2).aget()
- await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
- {'tid': self.tournament_id, 'type': 'draw.send_poules',
- 'round': r2.number,
- 'poules': [
- {
- 'letter': pool.get_letter_display(),
- 'teams': await pool.atrigrams(),
- }
- async for pool in r2.pool_set.order_by('letter').all()
- ]})
+ # First send the pools of next rounds to have the good team order
+ async for next_round in self.tournament.draw.round_set.filter(number__gte=2).all():
+ await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
+ {'tid': self.tournament_id, 'type': 'draw.send_poules',
+ 'round': r.number,
+ 'poules': [
+ {
+ 'letter': pool.get_letter_display(),
+ 'teams': await pool.atrigrams(),
+ }
+ async for pool in next_round.pool_set.order_by('letter').all()
+ ]})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r.number,
@@ -610,8 +612,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{tds[0].participation.team.trigram}",
{'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': "À votre tour !",
- 'body': "C'est à vous de tirer un nouveau problème !"})
+ 'title': _("Your turn!"),
+ 'body': _("It's your turn to draw a problem!")})
async def select_problem(self, **kwargs):
"""
@@ -631,7 +633,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
.prefetch_related('team').aget()
# Ensure that the user can draws a problem at this time
if participation.id != td.participation_id:
- return await self.alert("This is not your turn.", 'danger')
+ return await self.alert(_("This is not your turn."), 'danger')
while True:
# Choose a random problem
@@ -702,19 +704,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
.prefetch_related('team').aget()
# Ensure that the user can accept a problem at this time
if participation.id != td.participation_id:
- return await self.alert("This is not your turn.", 'danger')
+ return await self.alert(_("This is not your turn."), 'danger')
td.accepted = td.purposed
td.purposed = None
await td.asave()
trigram = td.participation.team.trigram
- msg = f"L'équipe {trigram} a accepté le problème {td.accepted} : " \
- f"{settings.PROBLEMS[td.accepted - 1]}. "
+ msg = _("The team {trigram} accepted the problem {problem}: "
+ "{problem_name}. ").format(trigram=trigram, problem=td.accepted,
+ problem_name=settings.PROBLEMS[td.accepted - 1])
if pool.size == 5 and await pool.teamdraw_set.filter(accepted=td.accepted).acount() < 2:
- msg += "Une équipe peut encore l'accepter."
+ msg += _("One team more can accept this problem.")
else:
- msg += "Plus personne ne peut l'accepter."
+ msg += _("No team can accept this problem anymore.")
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@@ -749,8 +752,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{new_trigram}",
{'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': "À votre tour !",
- 'body': "C'est à vous de tirer un nouveau problème !"})
+ 'title': _("Your turn!"),
+ 'body': _("It's your turn to draw a problem!")})
else:
# Pool is ended
await self.end_pool(pool)
@@ -808,8 +811,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'problems': [td.accepted async for td in pool.team_draws],
})
- msg += f"
Le tirage de la poule {pool.get_letter_display()}{r.number} est terminé. " \
- f"Le tableau récapitulatif est en bas."
+ msg += "
" + _("The draw of the pool {pool} is ended. The summary is below.") \
+ .format(pool=f"{pool.get_letter_display()}{r.number}")
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@@ -826,8 +829,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Notify the team that it can draw a dice
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': "À votre tour !",
- 'body': "C'est à vous de lancer le dé !"})
+ 'title': _("Your turn!"),
+ 'body': _("It's your turn to launch the dice!")})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
@@ -843,11 +846,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
"""
msg = self.tournament.draw.last_message
- if r.number == 1 and not self.tournament.final:
+ if r.number < settings.NB_ROUNDS and not self.tournament.final and settings.TFJM_APP == "TFJM":
# Next round
- r2 = await self.tournament.draw.round_set.filter(number=2).aget()
- self.tournament.draw.current_round = r2
- msg += "
Le tirage au sort du tour 1 est terminé."
+ next_round = await self.tournament.draw.round_set.filter(number=r.number + 1).aget()
+ self.tournament.draw.current_round = next_round
+ msg += "
" + _("The draw of the round {round} is ended.").format(round=r.number)
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@@ -860,26 +863,26 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Notify the team that it can draw a dice
await self.channel_layer.group_send(f"team-{participation.team.trigram}",
{'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': "À votre tour !",
- 'body': "C'est à vous de lancer le dé !"})
+ 'title': _("Your turn!"),
+ 'body': _("It's your turn to launch the dice!")})
# Reorder dices
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules',
- 'round': r2.number,
+ 'round': next_round.number,
'poules': [
{
'letter': pool.get_letter_display(),
'teams': await pool.atrigrams(),
}
- async for pool in r2.pool_set.order_by('letter').all()
+ async for pool in next_round.pool_set.order_by('letter').all()
]})
# The passage order for the second round is already determined by the first round
# Start the first pool of the second round
- p1: Pool = await r2.pool_set.filter(letter=1).aget()
- r2.current_pool = p1
- await r2.asave()
+ p1: Pool = await next_round.pool_set.filter(letter=1).aget()
+ next_round.current_pool = p1
+ await next_round.asave()
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
@@ -888,9 +891,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
- elif r.number == 1 and self.tournament.final:
+ elif r.number == 1 and (self.tournament.final or not settings.HAS_FINAL):
# For the final tournament, we wait for a manual update between the two rounds.
- msg += "
Le tirage au sort du tour 1 est terminé."
+ msg += "
" + _("The draw of the first round is ended.")
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@@ -919,7 +922,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
.prefetch_related('team').aget()
# Ensure that the user can reject a problem at this time
if participation.id != td.participation_id:
- return await self.alert("This is not your turn.", 'danger')
+ return await self.alert(_("This is not your turn."), 'danger')
# Add the problem to the rejected problems list
problem = td.purposed
@@ -929,19 +932,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
td.purposed = None
await td.asave()
- remaining = len(settings.PROBLEMS) - 5 - len(td.rejected)
+ remaining = len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)
# Update messages
trigram = td.participation.team.trigram
- msg = f"L'équipe {trigram} a refusé le problème {problem} : " \
- f"{settings.PROBLEMS[problem - 1]}. "
+ msg = _("The team {trigram} refused the problem {problem}: "
+ "{problem_name}.").format(trigram=trigram, problem=problem,
+ problem_name=settings.PROBLEMS[problem - 1]) + " "
if remaining >= 0:
- msg += f"Il lui reste {remaining} refus sans pénalité."
+ msg += _("It remains {remaining} refusals without penalty.").format(remaining=remaining)
else:
if already_refused:
- msg += "Cela n'ajoute pas de pénalité."
+ msg += _("This problem was already refused by this team.")
else:
- msg += "Cela ajoute une pénalité de 25 % sur le coefficient de l'oral de la défense."
+ msg += _("It adds a 25% penalty on the coefficient of the oral defense.")
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@@ -984,8 +988,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{new_trigram}",
{'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': "À votre tour !",
- 'body': "C'est à vous de tirer un nouveau problème !"})
+ 'title': _("Your turn!"),
+ 'body': _("It's your turn to draw a problem!")})
@ensure_orga
async def export(self, **kwargs):
@@ -1017,44 +1021,49 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
if not await Draw.objects.filter(tournament=self.tournament).aexists():
return await self.alert(_("The draw has not started yet."), 'danger')
- if not self.tournament.final:
+ if not self.tournament.final and settings.TFJM_APP == "TFJM":
return await self.alert(_("This is only available for the final tournament."), 'danger')
- r2 = await self.tournament.draw.round_set.filter(number=2).aget()
+ r2 = await self.tournament.draw.round_set.filter(number=self.tournament.draw.current_round.number + 1).aget()
self.tournament.draw.current_round = r2
- msg = "Le tirage au sort pour le tour 2 va commencer. " \
- "L'ordre de passage est déterminé à partir du classement du premier tour, " \
- "de sorte à mélanger les équipes entre les deux jours."
+ if settings.TFJM_APP == "TFJM":
+ msg = str(_("The draw of the round {round} is starting. "
+ "The passage order is determined from the ranking of the first round, "
+ "in order to mix the teams between the two days.").format(round=r2.number))
+ else:
+ msg = str(_("The draw of the round {round} is starting. "
+ "The passage order is another time randomly drawn.").format(round=r2.number))
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
# Send notification to everyone
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': 'Tirage au sort du TFJM²',
- 'body': "Le tirage au sort pour le second tour de la finale a commencé !"})
+ 'title': _("Draw") + " " + settings.APP_NAME,
+ 'body': str(_("The draw of the second round is starting!"))})
- # Set the first pool of the second round as the active pool
- pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
- r2.current_pool = pool
- await r2.asave()
+ if settings.TFJM_APP == "TFJM":
+ # Set the first pool of the second round as the active pool
+ pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
+ r2.current_pool = pool
+ await r2.asave()
- # Fetch notes from the first round
- notes = dict()
- async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
- notes[participation] = sum([await pool.aaverage(participation)
- async for pool in self.tournament.pools.filter(participations=participation)
- .prefetch_related('passages')])
- # Sort notes in a decreasing order
- ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
- # Define pools and passage orders from the ranking of the first round
- async for pool in r2.pool_set.order_by('letter').all():
- for i in range(pool.size):
- participation = ordered_participations.pop(0)
- td = await TeamDraw.objects.aget(round=r2, participation=participation)
- td.pool = pool
- td.passage_index = i
- await td.asave()
+ # Fetch notes from the first round
+ notes = dict()
+ async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
+ notes[participation] = sum([await pool.aaverage(participation)
+ async for pool in self.tournament.pools.filter(participations=participation)
+ .prefetch_related('passages')])
+ # Sort notes in a decreasing order
+ ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
+ # Define pools and passage orders from the ranking of the first round
+ async for pool in r2.pool_set.order_by('letter').all():
+ for i in range(pool.size):
+ participation = ordered_participations.pop(0)
+ td = await TeamDraw.objects.aget(round=r2, participation=participation)
+ td.pool = pool
+ td.passage_index = i
+ await td.asave()
# Send pools to users
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
@@ -1074,16 +1083,22 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
- async for td in r2.current_pool.team_draws.prefetch_related('participation__team'):
- await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
- {'tid': self.tournament_id, 'type': 'draw.dice_visibility',
- 'visible': True})
+ if settings.TFJM_APP == "TFJM":
+ async for td in r2.current_pool.team_draws.prefetch_related('participation__team'):
+ await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
+ {'tid': self.tournament_id, 'type': 'draw.dice_visibility',
+ 'visible': True})
- # Notify the team that it can draw a problem
- await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
- {'tid': self.tournament_id, 'type': 'draw.notify',
- 'title': "À votre tour !",
- 'body': "C'est à vous de tirer un nouveau problème !"})
+ # Notify the team that it can draw a problem
+ await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
+ {'tid': self.tournament_id, 'type': 'draw.notify',
+ 'title': _("Your turn!"),
+ 'body': _("It's your turn to draw a problem!")})
+ else:
+ async for td in r2.team_draws.prefetch_related('participation__team'):
+ await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
+ {'tid': self.tournament_id, 'type': 'draw.dice_visibility',
+ 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
@@ -1098,7 +1113,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r2.number,
- 'pool': r2.current_pool.get_letter_display()})
+ 'pool': r2.current_pool.get_letter_display() if r2.current_pool else None})
@ensure_orga
async def cancel_last_step(self, **kwargs):
@@ -1372,32 +1387,21 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'round': r.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
- elif r.number == 2:
+ elif r.number >= 2 and settings.TFJM_APP == "TFJM":
if not self.tournament.final:
# Go to the previous round
- r1 = await self.tournament.draw.round_set \
- .prefetch_related('current_pool__current_team__participation__team').aget(number=1)
- self.tournament.draw.current_round = r1
+ previous_round = await self.tournament.draw.round_set \
+ .prefetch_related('current_pool__current_team__participation__team').aget(number=r.number - 1)
+ self.tournament.draw.current_round = previous_round
await self.tournament.draw.asave()
- async for td in r1.team_draws.prefetch_related('participation__team').all():
+ async for td in previous_round.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram,
'result': td.choice_dice})
- await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
- {'tid': self.tournament_id, 'type': 'draw.send_poules',
- 'round': r1.number,
- 'poules': [
- {
- 'letter': pool.get_letter_display(),
- 'teams': await pool.atrigrams(),
- }
- async for pool in r1.pool_set.order_by('letter').all()
- ]})
-
- previous_pool = r1.current_pool
+ previous_pool = previous_round.current_pool
td = previous_pool.current_team
td.purposed = td.accepted
@@ -1417,14 +1421,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.set_problem',
- 'round': r1.number,
+ 'round': previous_round.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
else:
# Don't continue the final tournament
- r1 = await self.tournament.draw.round_set \
+ previous_round = await self.tournament.draw.round_set \
.prefetch_related('current_pool__current_team__participation__team').aget(number=1)
- self.tournament.draw.current_round = r1
+ self.tournament.draw.current_round = previous_round
await self.tournament.draw.asave()
async for td in r.teamdraw_set.all():
@@ -1446,7 +1450,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
]
})
- async for td in r1.team_draws.prefetch_related('participation__team').all():
+ async for td in previous_round.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram,
@@ -1460,17 +1464,31 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'visible': True})
else:
# Go to the dice order
- async for r0 in self.tournament.draw.round_set.all():
- async for td in r0.teamdraw_set.all():
- td.pool = None
- td.passage_index = None
- td.choose_index = None
- td.choice_dice = None
- await td.asave()
+ async for td in r.teamdraw_set.all():
+ td.pool = None
+ td.passage_index = None
+ td.choose_index = None
+ td.choice_dice = None
+ await td.asave()
r.current_pool = None
await r.asave()
+ await self.channel_layer.group_send(
+ f"tournament-{self.tournament.id}",
+ {
+ 'tid': self.tournament_id,
+ 'type': 'draw.send_poules',
+ 'round': r.number,
+ 'poules': [
+ {
+ 'letter': pool.get_letter_display(),
+ 'teams': await pool.atrigrams(),
+ }
+ async for pool in r.pool_set.order_by('letter').all()
+ ]
+ })
+
round_tds = {td.id: td async for td in r.team_draws.prefetch_related('participation__team')}
# Reset the last dice
@@ -1540,8 +1558,45 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'team': last_td.participation.team.trigram,
'result': None})
break
- else:
+ elif r.number == 1:
+ # Cancel the draw if it is the first round
await self.abort()
+ else:
+ # Go back to the first round after resetting all
+ previous_round = await self.tournament.draw.round_set \
+ .prefetch_related('current_pool__current_team__participation__team').aget(number=r.number - 1)
+ self.tournament.draw.current_round = previous_round
+ await self.tournament.draw.asave()
+
+ async for td in previous_round.team_draws.prefetch_related('participation__team').all():
+ await self.channel_layer.group_send(
+ f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
+ 'team': td.participation.team.trigram,
+ 'result': td.choice_dice})
+
+ previous_pool = previous_round.current_pool
+
+ td = previous_pool.current_team
+ td.purposed = td.accepted
+ td.accepted = None
+ await td.asave()
+
+ await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
+ {'tid': self.tournament_id, 'type': 'draw.dice_visibility',
+ 'visible': False})
+
+ await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
+ {'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
+ 'visible': True})
+ await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
+ {'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
+ 'visible': True})
+
+ await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
+ {'tid': self.tournament_id, 'type': 'draw.set_problem',
+ 'round': previous_round.number,
+ 'team': td.participation.team.trigram,
+ 'problem': td.accepted})
async def draw_alert(self, content):
"""
diff --git a/draw/migrations/0004_alter_round_number.py b/draw/migrations/0004_alter_round_number.py
new file mode 100644
index 0000000..053c537
--- /dev/null
+++ b/draw/migrations/0004_alter_round_number.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.0.6 on 2024-06-07 12:46
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("draw", "0003_alter_teamdraw_options"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="round",
+ name="number",
+ field=models.PositiveSmallIntegerField(
+ choices=[(1, "Round 1"), (2, "Round 2")],
+ help_text="The number of the round, 1 or 2 (or 3 for ETEAM)",
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(2),
+ ],
+ verbose_name="number",
+ ),
+ ),
+ ]
diff --git a/draw/migrations/0005_alter_round_number_alter_teamdraw_accepted_and_more.py b/draw/migrations/0005_alter_round_number_alter_teamdraw_accepted_and_more.py
new file mode 100644
index 0000000..d044feb
--- /dev/null
+++ b/draw/migrations/0005_alter_round_number_alter_teamdraw_accepted_and_more.py
@@ -0,0 +1,69 @@
+# Generated by Django 5.0.6 on 2024-06-13 08:53
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("draw", "0004_alter_round_number"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="round",
+ name="number",
+ field=models.PositiveSmallIntegerField(
+ choices=[(1, "Round 1"), (2, "Round 2"), (3, "Round 3")],
+ help_text="The number of the round, 1 or 2 (or 3 for ETEAM)",
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(3),
+ ],
+ verbose_name="number",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="teamdraw",
+ name="accepted",
+ 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"),
+ ],
+ default=None,
+ null=True,
+ verbose_name="accepted problem",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="teamdraw",
+ name="purposed",
+ 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"),
+ ],
+ default=None,
+ null=True,
+ verbose_name="purposed problem",
+ ),
+ ),
+ ]
diff --git a/draw/migrations/0006_alter_round_current_pool.py b/draw/migrations/0006_alter_round_current_pool.py
new file mode 100644
index 0000000..dccf1da
--- /dev/null
+++ b/draw/migrations/0006_alter_round_current_pool.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.0.6 on 2024-07-09 11:07
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("draw", "0005_alter_round_number_alter_teamdraw_accepted_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="round",
+ name="current_pool",
+ field=models.ForeignKey(
+ default=None,
+ help_text="The current pool where teams select their problems.",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="draw.pool",
+ verbose_name="current pool",
+ ),
+ ),
+ ]
diff --git a/draw/models.py b/draw/models.py
index 9e54e25..7bf2c9f 100644
--- a/draw/models.py
+++ b/draw/models.py
@@ -5,6 +5,7 @@ import os
from asgiref.sync import sync_to_async
from django.conf import settings
+from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import QuerySet
@@ -81,7 +82,7 @@ class Draw(models.Model):
elif self.current_round.current_pool.current_team is None:
return 'DICE_ORDER_POULE'
elif self.current_round.current_pool.current_team.accepted is not None:
- if self.current_round.number == 1:
+ if self.current_round.number < settings.NB_ROUNDS:
# The last step can be the last problem acceptation after the first round
# only for the final between the two rounds
return 'WAITING_FINAL'
@@ -110,58 +111,61 @@ class Draw(models.Model):
# Waiting for dices to determine pools and passage order
if self.current_round.number == 1:
# Specific information for the first round
- s += """Nous allons commencer le tirage des problèmes.
- Vous pouvez à tout moment poser toute question si quelque chose
- n'est pas clair ou ne va pas.
- Nous allons d'abord tirer les poules et l'ordre de passage
- pour le premier tour avec toutes les équipes puis pour chaque poule,
- nous tirerons l'ordre de tirage pour le tour et les problèmes.
"""
- s += """
- Les capitaines, vous pouvez désormais toustes lancer un dé 100,
- en cliquant sur le gros bouton. Les poules et l'ordre de passage
- lors du premier tour sera l'ordre croissant des dés, c'est-à-dire
- que le plus petit lancer sera le premier à passer dans la poule A."""
+ s += _("We are going to start the problem draw. "
+ "You can ask any question if something is not clear or wrong.
"
+ "We are going to first draw the pools and the passage order for the first round "
+ "with all the teams, then for each pool, we will draw the draw order and the problems.")
+ s += "
"
+ s += _("The captains, you can now all throw a 100-sided dice, by clicking on the big dice button. "
+ "The pools and the passage order during the first round will be the increasing order "
+ "of the dices, ie. the smallest dice will be the first to pass in pool A.")
case 'DICE_ORDER_POULE':
# Waiting for dices to determine the choice order
- s += f"""Nous passons au tirage des problèmes pour la poule
- {self.current_round.current_pool}, entre les équipes
- {', '.join(td.participation.team.trigram
- for td in self.current_round.current_pool.teamdraw_set.all())}.
- Les capitaines peuvent lancer un dé 100 en cliquant sur le gros bouton
- pour déterminer l'ordre de tirage. L'équipe réalisant le plus gros score pourra
- tirer en premier."""
+ s += _("We are going to start the problem draw for the pool {pool}, "
+ "between the teams {teams}. "
+ "The captains can throw a 100-sided dice by clicking on the big dice button "
+ "to determine the order of draw. The team with the highest score will draw first.") \
+ .format(pool=self.current_round.current_pool,
+ teams=', '.join(td.participation.team.trigram
+ for td in self.current_round.current_pool.teamdraw_set.all()))
case 'WAITING_DRAW_PROBLEM':
# Waiting for a problem draw
td = self.current_round.current_pool.current_team
- s += f"""C'est au tour de l'équipe {td.participation.team.trigram}
- de choisir son problème. Cliquez sur l'urne au milieu pour tirer un problème au sort."""
+ s += _("The team {trigram} is going to draw a problem. "
+ "Click on the urn in the middle to draw a problem.") \
+ .format(trigram=td.participation.team.trigram)
case 'WAITING_CHOOSE_PROBLEM':
# Waiting for the team that can accept or reject the problem
td = self.current_round.current_pool.current_team
- s += f"""L'équipe {td.participation.team.trigram} a tiré le problème
- {td.purposed} : {settings.PROBLEMS[td.purposed - 1]}. """
+ s += _("The team {trigram} drew the problem {problem}: "
+ "{problem_name}.") \
+ .format(trigram=td.participation.team.trigram,
+ problem=td.purposed, problem_name=settings.PROBLEMS[td.purposed - 1]) + " "
if td.purposed in td.rejected:
# The problem was previously rejected
- s += """Elle a déjà refusé ce problème auparavant, elle peut donc le refuser sans pénalité et
- tirer un nouveau problème immédiatement, ou bien revenir sur son choix."""
+ s += _("It already refused this problem before, so it can refuse it without penalty and "
+ "draw a new problem immediately, or change its mind.")
else:
# The problem can be rejected
- s += "Elle peut décider d'accepter ou de refuser ce problème. "
- if len(td.rejected) >= len(settings.PROBLEMS) - 5:
- s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense."
+ s += _("It can decide to accept or refuse this problem.") + " "
+ if len(td.rejected) >= len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT:
+ s += _("Refusing this problem will add a new 25% penalty "
+ "on the coefficient of the oral defense.")
else:
- s += f"Il reste {len(settings.PROBLEMS) - 5 - len(td.rejected)} refus sans pénalité."
+ s += _("There are still {remaining} refusals without penalty.").format(
+ remaining=len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected))
case 'WAITING_FINAL':
# We are between the two rounds of the final tournament
- s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !"
+ s += _("The draw for the second round will take place at the end of the first round. Good luck!")
case 'DRAW_ENDED':
# The draw is ended
- s += "Le tirage au sort est terminé. Les solutions des autres équipes peuvent être trouvées dans l'onglet « Ma participation »."
+ s += _("The draw is ended. The solutions of the other teams can be found in the tab "
+ "\"My participation\".")
s += "
" if s else ""
- s += """Pour plus de détails sur le déroulement du tirage au sort,
- le règlement est accessible sur
- https://tfjm.org/reglement."""
+ rules_link = settings.RULES_LINK
+ s += _("For more details on the draw, the rules are available on "
+ "{link}.").format(link=rules_link)
return s
async def ainformation(self) -> str:
@@ -193,15 +197,15 @@ class Round(models.Model):
choices=[
(1, _('Round 1')),
(2, _('Round 2')),
- ],
+ (3, _('Round 3'))],
verbose_name=_('number'),
- help_text=_("The number of the round, 1 or 2"),
- validators=[MinValueValidator(1), MaxValueValidator(2)],
+ help_text=_("The number of the round, 1 or 2 (or 3 for ETEAM)"),
+ validators=[MinValueValidator(1), MaxValueValidator(3)],
)
current_pool = models.ForeignKey(
'Pool',
- on_delete=models.CASCADE,
+ on_delete=models.SET_NULL,
null=True,
default=None,
related_name='+',
@@ -230,6 +234,13 @@ class Round(models.Model):
def __str__(self):
return self.get_number_display()
+ def clean(self):
+ if self.number is not None and self.number > settings.NB_ROUNDS:
+ raise ValidationError({'number': _("The number of the round must be between 1 and {nb}.")
+ .format(nb=settings.NB_ROUNDS)})
+
+ return super().clean()
+
class Meta:
verbose_name = _('round')
verbose_name_plural = _('rounds')
@@ -389,11 +400,11 @@ class Pool(models.Model):
]
elif self.size == 5:
table = [
- [0, 2, 3],
- [1, 3, 4],
- [2, 4, 0],
- [3, 0, 1],
- [4, 1, 2],
+ [0, 2, 3, 4],
+ [1, 3, 4, 0],
+ [2, 4, 0, 1],
+ [3, 0, 1, 2],
+ [4, 1, 2, 3],
]
for i, line in enumerate(table):
@@ -405,15 +416,21 @@ class Pool(models.Model):
passage_pool = pool2
passage_position = 1 + i // 2
+ 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.HAS_OBSERVER else None
+
# Create the passage
await Passage.objects.acreate(
pool=passage_pool,
position=passage_position,
solution_number=tds[line[0]].accepted,
- defender=tds[line[0]].participation,
- opponent=tds[line[1]].participation,
- reporter=tds[line[2]].participation,
- defender_penalties=tds[line[0]].penalty_int,
+ reporter=reporter,
+ opponent=opponent,
+ reviewer=reviewer,
+ observer=observer,
+ reporter_penalties=tds[line[0]].penalty_int,
)
# Update Google Sheets
@@ -524,15 +541,15 @@ class TeamDraw(models.Model):
@property
def penalty_int(self):
"""
- The number of penalties, which is the number of rejected problems after the P - 5 free rejects,
- where P is the number of problems.
+ The number of penalties, which is the number of rejected problems after the P - 5 free rejects
+ (P - 6 for ETEAM), where P is the number of problems.
"""
- return max(0, len(self.rejected) - (len(settings.PROBLEMS) - 5))
+ return max(0, len(self.rejected) - (len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT))
@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
diff --git a/draw/static/tfjm/js/draw.js b/draw/static/tfjm/js/draw.js
index 03e17fc..c72cf31 100644
--- a/draw/static/tfjm/js/draw.js
+++ b/draw/static/tfjm/js/draw.js
@@ -4,6 +4,9 @@
await Notification.requestPermission()
})()
+const TFJM = JSON.parse(document.getElementById('TFJM_settings').textContent)
+const RECOMMENDED_SOLUTIONS_COUNT = TFJM.RECOMMENDED_SOLUTIONS_COUNT
+
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
@@ -308,7 +311,7 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Set the different pools for the given round, and update the interface.
* @param tid The tournament id
- * @param round The round number, as integer (1 or 2)
+ * @param round The round number, as integer (1 or 2, or 3 for ETEAM)
* @param poules The list of poules, which are represented with their letters and trigrams,
* [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
*/
@@ -430,7 +433,7 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Update the table for the given round and the given pool, where there will be the chosen problems.
* @param tid The tournament id
- * @param round The round number, as integer (1 or 2)
+ * @param round The round number, as integer (1 or 2, or 3 for ETEAM)
* @param poule The current pool, which id represented with its letter and trigrams,
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
*/
@@ -518,45 +521,45 @@ 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')
opponentTd.innerText = 'Opp'
- let reporterTd = document.createElement('td')
- reporterTd.classList.add('text-center')
- reporterTd.innerText = 'Rap'
+ let reviewerTd = document.createElement('td')
+ reviewerTd.classList.add('text-center')
+ reviewerTd.innerText = 'Rap'
// Put the cells in their right places, according to the pool size and the row number.
if (poule.teams.length === 3) {
switch (i) {
case 0:
- teamTr.append(defenderTd, reporterTd, opponentTd)
+ teamTr.append(reporterTd, reviewerTd, opponentTd)
break
case 1:
- teamTr.append(opponentTd, defenderTd, reporterTd)
+ teamTr.append(opponentTd, reporterTd, reviewerTd)
break
case 2:
- teamTr.append(reporterTd, 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, reporterTd, opponentTd)
+ teamTr.append(reporterTd, emptyTd, reviewerTd, opponentTd)
break
case 1:
- teamTr.append(opponentTd, defenderTd, emptyTd, reporterTd)
+ teamTr.append(opponentTd, reporterTd, emptyTd, reviewerTd)
break
case 2:
- teamTr.append(reporterTd, opponentTd, defenderTd, emptyTd)
+ teamTr.append(reviewerTd, opponentTd, reporterTd, emptyTd)
break
case 3:
- teamTr.append(emptyTd, reporterTd, opponentTd, defenderTd)
+ teamTr.append(emptyTd, reviewerTd, opponentTd, reporterTd)
break
}
} else if (poule.teams.length === 5) {
@@ -564,19 +567,19 @@ document.addEventListener('DOMContentLoaded', () => {
let emptyTd2 = document.createElement('td')
switch (i) {
case 0:
- teamTr.append(defenderTd, emptyTd, opponentTd, reporterTd, emptyTd2)
+ teamTr.append(reporterTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
break
case 1:
- teamTr.append(emptyTd, defenderTd, reporterTd, emptyTd2, opponentTd)
+ teamTr.append(emptyTd, reporterTd, reviewerTd, emptyTd2, opponentTd)
break
case 2:
- teamTr.append(opponentTd, emptyTd, defenderTd, emptyTd2, reporterTd)
+ teamTr.append(opponentTd, emptyTd, reporterTd, emptyTd2, reviewerTd)
break
case 3:
- teamTr.append(reporterTd, opponentTd, emptyTd, defenderTd, emptyTd2)
+ teamTr.append(reviewerTd, opponentTd, emptyTd, reporterTd, emptyTd2)
break
case 4:
- teamTr.append(emptyTd, reporterTd, emptyTd2, opponentTd, defenderTd)
+ teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, reporterTd)
break
}
}
@@ -587,7 +590,7 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Highlight the team that is currently choosing its problem.
* @param tid The tournament id
- * @param round The current round number, as integer (1 or 2)
+ * @param round The current round number, as integer (1 or 2, or 3 for ETEAM)
* @param pool The current pool letter (A, B, C or D) (null if non-relevant)
* @param team The current team trigram (null if non-relevant)
*/
@@ -624,7 +627,7 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Update the recap and the table when a team accepts a problem.
* @param tid The tournament id
- * @param round The current round, as integer (1 or 2)
+ * @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param team The current team trigram
* @param problem The accepted problem, as integer
*/
@@ -648,7 +651,7 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Update the recap when a team rejects a problem.
* @param tid The tournament id
- * @param round The current round, as integer (1 or 2)
+ * @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param team The current team trigram
* @param rejected The full list of rejected problems
*/
@@ -658,15 +661,16 @@ document.addEventListener('DOMContentLoaded', () => {
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
- if (rejected.length > problems_count - 5) {
- // If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
+ if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
+ // If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral reporter
+ // This is P - 6 for the ETEAM
if (penaltyDiv === null) {
penaltyDiv = document.createElement('div')
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
recapDiv.parentNode.append(penaltyDiv)
}
- penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - 5))} %`
+ penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - RECOMMENDED_SOLUTIONS_COUNT))} %`
} else {
// Eventually remove this div
if (penaltyDiv !== null)
@@ -678,7 +682,7 @@ document.addEventListener('DOMContentLoaded', () => {
* For a 5-teams pool, we may reorder the pool if two teams select the same problem.
* Then, we redraw the table and set the accepted problems.
* @param tid The tournament id
- * @param round The current round, as integer (1 or 2)
+ * @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param poule The pool represented by its letter
* @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"]
* @param problems The accepted problems in the same order than the teams, [1, 1, 2, 2, 3]
@@ -696,6 +700,9 @@ document.addEventListener('DOMContentLoaded', () => {
let problem = problems[i]
setProblemAccepted(tid, round, team, problem)
+
+ let recapTeam = document.getElementById(`recap-${tid}-round-${round}-team-${team}`)
+ recapTeam.style.order = i.toString()
}
}
diff --git a/draw/templates/draw/tournament_content.html b/draw/templates/draw/tournament_content.html
index d0f22d8..3d1d326 100644
--- a/draw/templates/draw/tournament_content.html
+++ b/draw/templates/draw/tournament_content.html
@@ -176,7 +176,7 @@
📁 {% trans "Export" %}
- {% if tournament.final %}
+ {% if tournament.final or not TFJM.HAS_FINAL %}
{# Volunteers can continue the second round for the final tournament #}
@@ -307,71 +307,71 @@
{{ td.participation.team.trigram }}
{% if pool.size == 3 %}
{% if forloop.counter == 1 %}
-
{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}
+
{% trans "Rev" context "Role abbreviation" %}
+
{% trans "Opp" context "Role abbreviation" %}
+
{% elif forloop.counter == 2 %}
-
-
Déf
-
-
Rap
-
Opp
+
+
{% trans "Rep" context "Role abbreviation" %}
+
{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}
+
{% trans "Rev" context "Role abbreviation" %}
+
{% trans "Opp" context "Role abbreviation" %}
{% elif forloop.counter == 3 %}
-
Opp
-
-
Déf
-
-
Rap
+
{% trans "Opp" context "Role abbreviation" %}
+
+
{% trans "Rep" context "Role abbreviation" %}
+
{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}
+
{% trans "Rev" context "Role abbreviation" %}
{% elif forloop.counter == 4 %}
-
Rap
-
Opp
-
-
Déf
-
+
{% trans "Rev" context "Role abbreviation" %}
+
{% trans "Opp" context "Role abbreviation" %}
+
+
{% trans "Rep" context "Role abbreviation" %}
+
{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}
{% elif forloop.counter == 5 %}
-
-
Rap
-
Opp
-
-
Déf
+
{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}
+
{% trans "Rev" context "Role abbreviation" %}
+
{% trans "Opp" context "Role abbreviation" %}
+
+
{% trans "Rep" context "Role abbreviation" %}
{% endif %}
{% endif %}
diff --git a/draw/tests.py b/draw/tests.py
index bbb209a..e495697 100644
--- a/draw/tests.py
+++ b/draw/tests.py
@@ -71,7 +71,7 @@ class TestDraw(TestCase):
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['alert_type'], 'danger')
- self.assertEqual(resp['message'], "The sum must be equal to the number of teams: expected 12, got 3")
+ self.assertEqual(resp['message'], "La somme doit être égale au nombre d'équipes : attendu 12, obtenu 3")
self.assertFalse(await Draw.objects.filter(tournament=self.tournament).aexists())
# Now start the draw
@@ -113,7 +113,7 @@ class TestDraw(TestCase):
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['alert_type'], 'danger')
- self.assertEqual(resp['message'], "The draw is already started.")
+ self.assertEqual(resp['message'], "Le tirage a déjà commencé.")
draw: Draw = await Draw.objects.prefetch_related(
'current_round__current_pool__current_team__participation__team').aget(tournament=self.tournament)
@@ -135,7 +135,7 @@ class TestDraw(TestCase):
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': team.trigram})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
- self.assertEqual(resp['message'], "You've already launched the dice.")
+ self.assertEqual(resp['message'], "Vous avez déjà lancé le dé.")
# Force exactly one duplicate
await td.arefresh_from_db()
@@ -207,7 +207,7 @@ class TestDraw(TestCase):
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': trigram})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
- self.assertEqual(resp['message'], "You've already launched the dice.")
+ self.assertEqual(resp['message'], "Vous avez déjà lancé le dé.")
# Force exactly one duplicate
await td.arefresh_from_db()
@@ -254,7 +254,7 @@ class TestDraw(TestCase):
await communicator.send_json_to({'tid': tid, 'type': 'dice', 'trigram': None})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
- self.assertEqual(resp['message'], "This is not the time for this.")
+ self.assertEqual(resp['message'], "Ce n'est pas le moment pour cela.")
# Draw a problem
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
@@ -277,7 +277,7 @@ class TestDraw(TestCase):
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
- self.assertEqual(resp['message'], "This is not the time for this.")
+ self.assertEqual(resp['message'], "Ce n'est pas le moment pour cela.")
# Reject the first problem
await communicator.send_json_to({'tid': tid, 'type': 'reject'})
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index a3d71fa..21fc99a 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-05-26 22:06+0200\n"
+"POT-Creation-Date: 2024-07-09 13:22+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello \n"
"Language-Team: LANGUAGE \n"
@@ -37,7 +37,7 @@ msgstr "Canaux d'équipes"
msgid "Private channels"
msgstr "Messages privés"
-#: chat/models.py:29 participation/models.py:35 participation/models.py:263
+#: chat/models.py:29 participation/models.py:36 participation/models.py:272
#: participation/tables.py:18 participation/tables.py:34
msgid "name"
msgstr "nom"
@@ -77,10 +77,10 @@ msgid "Permission type that is required to write a message to a channel."
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:26 participation/admin.py:79 participation/admin.py:140
-#: participation/admin.py:171 participation/models.py:693
-#: participation/models.py:717 participation/models.py:935
-#: registration/models.py:756
+#: draw/models.py:27 participation/admin.py:79 participation/admin.py:144
+#: participation/admin.py:176 participation/models.py:783
+#: participation/models.py:807 participation/models.py:1131
+#: registration/models.py:763
#: registration/templates/registration/payment_form.html:53
msgid "tournament"
msgstr "tournoi"
@@ -93,9 +93,9 @@ msgstr ""
"Pour une permission qui concerne un tournoi, indique quel est le tournoi "
"concerné."
-#: chat/models.py:73 draw/models.py:429 draw/models.py:456
-#: participation/admin.py:136 participation/admin.py:155
-#: participation/models.py:1434 participation/models.py:1443
+#: chat/models.py:73 draw/models.py:446 draw/models.py:473
+#: participation/admin.py:140 participation/admin.py:160
+#: participation/models.py:1666 participation/models.py:1675
#: participation/tables.py:84
msgid "pool"
msgstr "poule"
@@ -108,10 +108,10 @@ msgstr ""
"concernée."
#: chat/models.py:84 draw/templates/draw/tournament_content.html:277
-#: participation/admin.py:167 participation/models.py:252
-#: participation/models.py:708
+#: participation/admin.py:172 participation/models.py:261
+#: participation/models.py:798
#: participation/templates/participation/tournament_harmonize.html:15
-#: registration/models.py:157 registration/models.py:747
+#: registration/models.py:158 registration/models.py:754
#: registration/tables.py:39
#: registration/templates/registration/payment_form.html:52
msgid "team"
@@ -217,7 +217,7 @@ msgstr ""
msgid "Toggle fullscreen mode"
msgstr "Inverse le mode plein écran"
-#: chat/templates/chat/content.html:76 tfjm/templates/navbar.html:117
+#: chat/templates/chat/content.html:76 tfjm/templates/navbar.html:125
msgid "Log out"
msgstr "Déconnexion"
@@ -234,37 +234,42 @@ msgid "Send message…"
msgstr "Envoyer un message…"
#: chat/templates/chat/fullscreen.html:10
-#: chat/templates/chat/fullscreen.html:12 chat/templates/chat/login.html:10
-#: chat/templates/chat/login.html:12
+#: chat/templates/chat/fullscreen.html:11
msgid "TFJM² Chat"
msgstr "Chat du TFJM²"
-#: chat/templates/chat/login.html:10 chat/templates/chat/login.html:31
+#: chat/templates/chat/fullscreen.html:13
+#: chat/templates/chat/fullscreen.html:14
+msgid "ETEAM Chat"
+msgstr "Chat de l'ETEAM"
+
+#: chat/templates/chat/login.html:10 chat/templates/chat/login.html:12
+#: chat/urls.py:13 tfjm/templates/navbar.html:74
+msgid "Chat"
+msgstr "Chat"
+
+#: chat/templates/chat/login.html:10 chat/templates/chat/login.html:36
#: registration/templates/registration/password_reset_complete.html:10
-#: tfjm/templates/base.html:84 tfjm/templates/base.html:85
-#: tfjm/templates/navbar.html:98
+#: tfjm/templates/base.html:89 tfjm/templates/base.html:90
+#: tfjm/templates/navbar.html:106
#: tfjm/templates/registration/includes/login.html:22
#: tfjm/templates/registration/login.html:7
#: tfjm/templates/registration/login.html:8
msgid "Log in"
msgstr "Connexion"
-#: chat/urls.py:13 tfjm/templates/navbar.html:66
-msgid "Chat"
-msgstr "Chat"
-
#: draw/admin.py:39 draw/admin.py:57 draw/admin.py:75
-#: participation/admin.py:109 participation/models.py:253
+#: participation/admin.py:109 participation/models.py:262
#: participation/tables.py:88
msgid "teams"
msgstr "équipes"
-#: draw/admin.py:92 draw/models.py:234 draw/models.py:448
-#: participation/models.py:939
+#: draw/admin.py:92 draw/models.py:245 draw/models.py:465
+#: participation/models.py:1135
msgid "round"
msgstr "tour"
-#: draw/apps.py:10 tfjm/templates/navbar.html:60
+#: draw/apps.py:10 draw/consumers.py:1042 tfjm/templates/navbar.html:68
msgid "Draw"
msgstr "Tirage au sort"
@@ -272,239 +277,481 @@ msgstr "Tirage au sort"
msgid "You are not an organizer."
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice."
-#: draw/consumers.py:165
+#: draw/consumers.py:167
msgid "The draw is already started."
msgstr "Le tirage a déjà commencé."
-#: draw/consumers.py:171
+#: draw/consumers.py:173
msgid "Invalid format"
msgstr "Format invalide"
-#: draw/consumers.py:176
+#: draw/consumers.py:178
#, python-brace-format
msgid "The sum must be equal to the number of teams: expected {len}, got {sum}"
msgstr ""
"La somme doit être égale au nombre d'équipes : attendu {len}, obtenu {sum}"
-#: draw/consumers.py:181
+#: draw/consumers.py:183
msgid "There can be at most one pool with 5 teams."
msgstr "Il ne peut y avoir au plus qu'une seule poule de 5 équipes."
-#: draw/consumers.py:221
+#: draw/consumers.py:223
msgid "Draw started!"
msgstr "Le tirage a commencé !"
-#: draw/consumers.py:243
+#: draw/consumers.py:238
+#, python-brace-format
+msgid "The draw of tournament {tournament} started!"
+msgstr "Le tirage au sort du tournoi {tournament} a commencé !"
+
+#: draw/consumers.py:245
#, python-brace-format
msgid "The draw for the tournament {tournament} will start."
msgstr "Le tirage au sort du tournoi {tournament} va commencer."
-#: draw/consumers.py:254 draw/consumers.py:280 draw/consumers.py:690
-#: draw/consumers.py:907 draw/consumers.py:996 draw/consumers.py:1018
-#: draw/consumers.py:1109 draw/templates/draw/tournament_content.html:5
+#: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:692
+#: draw/consumers.py:910 draw/consumers.py:1000 draw/consumers.py:1022
+#: draw/consumers.py:1125 draw/templates/draw/tournament_content.html:5
msgid "The draw has not started yet."
msgstr "Le tirage au sort n'a pas encore commencé."
-#: draw/consumers.py:267
+#: draw/consumers.py:269
#, python-brace-format
msgid "The draw for the tournament {tournament} is aborted."
msgstr "Le tirage au sort du tournoi {tournament} est annulé."
-#: draw/consumers.py:307 draw/consumers.py:328 draw/consumers.py:624
-#: draw/consumers.py:695 draw/consumers.py:912
+#: draw/consumers.py:309 draw/consumers.py:330 draw/consumers.py:626
+#: draw/consumers.py:697 draw/consumers.py:915
msgid "This is not the time for this."
msgstr "Ce n'est pas le moment pour cela."
-#: draw/consumers.py:320 draw/consumers.py:323
+#: draw/consumers.py:322 draw/consumers.py:325
msgid "You've already launched the dice."
msgstr "Vous avez déjà lancé le dé."
-#: draw/consumers.py:326
+#: draw/consumers.py:328
msgid "It is not your turn."
msgstr "Ce n'est pas votre tour."
-#: draw/consumers.py:413
+#: draw/consumers.py:408
+msgid ""
+"Your dice score is identical to the one of one or multiple teams. Please "
+"relaunch it."
+msgstr ""
+"Votre score de dé est identique à celui d'une ou plusieurs équipes. Merci de "
+"le relancer."
+
+#: draw/consumers.py:415
#, python-brace-format
msgid "Dices from teams {teams} are identical. Please relaunch your dices."
msgstr ""
"Les dés des équipes {teams} sont identiques. Merci de relancer vos dés."
-#: draw/consumers.py:1021
+#: draw/consumers.py:508
+#, python-brace-format
+msgid ""
+"The dice results are the following: {trigrams}. The passage order and the "
+"compositions of the different pools are displayed on the side. The passage "
+"orders for the first round are determined from the dice scores, in "
+"increasing order. For the second round, the passage orders are determined "
+"from the passage orders of the first round."
+msgstr ""
+"Les résultats des dés sont les suivants : {trigrams}. L'ordre de passage et "
+"les compositions des différentes poules sont affichés sur le côté. Les "
+"ordres de passage pour le premier tour sont déterminés à partir des scores "
+"de dés, par ordre croissant. Pour le deuxième tour, les ordres de passage "
+"sont déterminés à partir des ordres de passage du premier tour."
+
+#: draw/consumers.py:615 draw/consumers.py:755 draw/consumers.py:832
+#: draw/consumers.py:866 draw/consumers.py:991 draw/consumers.py:1095
+msgid "Your turn!"
+msgstr "À votre tour !"
+
+#: draw/consumers.py:616 draw/consumers.py:756 draw/consumers.py:992
+#: draw/consumers.py:1096
+msgid "It's your turn to draw a problem!"
+msgstr "C'est à vous de tirer un problème !"
+
+#: draw/consumers.py:636 draw/consumers.py:707 draw/consumers.py:925
+msgid "This is not your turn."
+msgstr "Ce n'est pas votre tour."
+
+#: draw/consumers.py:714
+#, python-brace-format
+msgid ""
+"The team {trigram} accepted the problem {problem}"
+"strong>: {problem_name}. "
+msgstr ""
+"L'équipe {trigram} a accepté le problème {problem}"
+"strong> : {problem_name}. "
+
+#: draw/consumers.py:718
+msgid "One team more can accept this problem."
+msgstr "Une équipe de plus peut accepter ce problème."
+
+#: draw/consumers.py:720
+msgid "No team can accept this problem anymore."
+msgstr "Aucune autre équipe ne peut accepter ce problème."
+
+#: draw/consumers.py:814
+#, python-brace-format
+msgid "The draw of the pool {pool} is ended. The summary is below."
+msgstr "Le tirage de la poule {pool} est terminé. Le résumé est ci-dessous."
+
+#: draw/consumers.py:833 draw/consumers.py:867
+msgid "It's your turn to launch the dice!"
+msgstr "C'est à vous de lancer le dé !"
+
+#: draw/consumers.py:853
+#, python-brace-format
+msgid "The draw of the round {round} is ended."
+msgstr "Le tirage au sort du tour {round} est annulé."
+
+#: draw/consumers.py:896
+msgid "The draw of the first round is ended."
+msgstr "Le tirage au sort du premier tour est terminé."
+
+#: draw/consumers.py:939
+#, python-brace-format
+msgid ""
+"The team {trigram} refused the problem {problem}"
+"strong>: {problem_name}."
+msgstr ""
+"L'équipe {trigram} a refusé le problème {problem}"
+"strong> : {problem_name}."
+
+#: draw/consumers.py:943
+#, python-brace-format
+msgid "It remains {remaining} refusals without penalty."
+msgstr "Il reste {remaining} refus sans pénalité."
+
+#: draw/consumers.py:946
+msgid "This problem was already refused by this team."
+msgstr "Ce problème a déjà été refusé par cette équipe."
+
+#: draw/consumers.py:948
+msgid "It adds a 25% penalty on the coefficient of the oral defense."
+msgstr ""
+"Cela ajoute une pénalité de 25 % sur le coefficient de l'oral de la "
+"défense."
+
+#: draw/consumers.py:1025
msgid "This is only available for the final tournament."
msgstr "Cela n'est possible que pour la finale."
-#: draw/models.py:27
+#: draw/consumers.py:1030
+#, python-brace-format
+msgid ""
+"The draw of the round {round} is starting. The passage order is determined "
+"from the ranking of the first round, in order to mix the teams between the "
+"two days."
+msgstr ""
+"Le tirage au sort du tour {round} commence. L'ordre de passage est déterminé à "
+"partir du classement du premier tour, afin de mélanger les équipes entre les "
+"deux jours."
+
+#: draw/consumers.py:1034
+#, python-brace-format
+msgid ""
+"The draw of the round {round} is starting. The passage order is another time "
+"randomly drawn."
+msgstr ""
+"Le tirage au sort du tour {round} commence. L'ordre de passage est à nouveau tiré "
+"au hasard."
+
+#: draw/consumers.py:1043
+msgid "The draw of the second round is starting!"
+msgstr "Le tirage au sort du deuxième tour commence !"
+
+#: draw/models.py:28
msgid "The associated tournament."
msgstr "Le tournoi associé."
-#: draw/models.py:36
+#: draw/models.py:37
msgid "current round"
msgstr "tour actuel"
-#: draw/models.py:37
+#: draw/models.py:38
msgid "The current round where teams select their problems."
msgstr "Le tour en cours où les équipes choisissent leurs problèmes."
-#: draw/models.py:43
+#: draw/models.py:44
msgid "last message"
msgstr "dernier message"
-#: draw/models.py:44
+#: draw/models.py:45
msgid "The last message that is displayed on the drawing interface."
msgstr "Le dernier message qui est affiché sur l'interface de tirage."
-#: draw/models.py:94
+#: draw/models.py:95
msgid "State"
msgstr "État"
-#: draw/models.py:174
+#: draw/models.py:114
+msgid ""
+"We are going to start the problem draw. You can ask any question if "
+"something is not clear or wrong.
We are going to first draw the pools "
+"and the passage order for the first round with all the teams, then for each "
+"pool, we will draw the draw order and the problems."
+msgstr ""
+"Nous allons commencer le tirage des problèmes. Vous pouvez poser des "
+"questions si quelque chose n'est pas clair ou faux.
Nous allons "
+"d'abord tirer les poules et l'ordre de passage pour le premier tour avec "
+"toutes les équipes, puis pour chaque poule, nous allons tirer l'ordre de "
+"tirage et les problèmes."
+
+#: draw/models.py:119
+msgid ""
+"The captains, you can now all throw a 100-sided dice, by clicking on the big "
+"dice button. The pools and the passage order during the first round will be "
+"the increasing order of the dices, ie. the smallest dice will be the first "
+"to pass in pool A."
+msgstr ""
+"Les capitaines, vous pouvez maintenant tous lancer un dé à 100 faces, en "
+"cliquant sur le gros bouton de dé. Les poules et l'ordre de passage pendant "
+"le premier tour seront l'ordre croissant des dés, c'est-à-dire que le plus "
+"petit dé passera en premier dans la poule A."
+
+#: draw/models.py:124
+#, python-brace-format
+msgid ""
+"We are going to start the problem draw for the pool {pool}, "
+"between the teams {teams}. The captains can throw a 100-"
+"sided dice by clicking on the big dice button to determine the order of "
+"draw. The team with the highest score will draw first."
+msgstr ""
+"Nous allons commencer le tirage des problèmes pour la poule {pool}"
+"strong>, entre les équipes {teams}. Les capitaines peuvent "
+"lancer un dé à 100 faces en cliquant sur le gros bouton de dé pour "
+"déterminer l'ordre de tirage. L'équipe avec le score le plus élevé tirera en "
+"premier."
+
+#: draw/models.py:134
+#, python-brace-format
+msgid ""
+"The team {trigram} is going to draw a problem. Click on the "
+"urn in the middle to draw a problem."
+msgstr ""
+"L'équipe {trigram} va tirer un problème. Cliquez sur l'urne "
+"au milieu pour tirer un problème."
+
+#: draw/models.py:140
+#, python-brace-format
+msgid ""
+"The team {trigram} drew the problem {problem}: "
+"{problem_name}."
+msgstr ""
+"L'équipe {trigram} a tiré le problème {problem} : "
+"{problem_name}."
+
+#: draw/models.py:146
+msgid ""
+"It already refused this problem before, so it can refuse it without penalty "
+"and draw a new problem immediately, or change its mind."
+msgstr ""
+"Elle a déjà refusé ce problème auparavant, donc elle peut le refuser sans "
+"pénalité et tirer un nouveau problème immédiatement, ou changer d'avis."
+
+#: draw/models.py:150
+msgid "It can decide to accept or refuse this problem."
+msgstr "Elle peut décider d'accepter ou de refuser ce problème."
+
+#: draw/models.py:152
+msgid ""
+"Refusing this problem will add a new 25% penalty on the coefficient of the "
+"oral defense."
+msgstr ""
+"Refuser ce problème ajoutera une nouvelle pénalité de 25nbsp;% sur le "
+"coefficient de l'oral de la défense."
+
+#: draw/models.py:155
+#, python-brace-format
+msgid "There are still {remaining} refusals without penalty."
+msgstr "Il reste {remaining} refus sans pénalité."
+
+#: draw/models.py:159
+msgid ""
+"The draw for the second round will take place at the end of the first round. "
+"Good luck!"
+msgstr ""
+"Le tirage au sort du deuxième tour aura lieu à la fin du premier tour. Bonne "
+"chance !"
+
+#: draw/models.py:162
+msgid ""
+"The draw is ended. The solutions of the other teams can be found in the tab "
+"\"My participation\"."
+msgstr ""
+"Le tirage est terminé. Les solutions des autres équipes peuvent être "
+"trouvées dans l'onglet « Ma participation »."
+
+#: draw/models.py:167
+#, python-brace-format
+msgid ""
+"For more details on the draw, the rules are available on {link}."
+msgstr ""
+"Pour plus de détails sur le tirage, les règles sont disponibles sur {link}."
+
+#: draw/models.py:178
#, python-brace-format
msgid "Draw of tournament {tournament}"
msgstr "Tirage au sort du tournoi {tournament}"
-#: draw/models.py:177 draw/models.py:189
+#: draw/models.py:181 draw/models.py:193
msgid "draw"
msgstr "tirage au sort"
-#: draw/models.py:178
+#: draw/models.py:182
msgid "draws"
msgstr "tirages au sort"
-#: draw/models.py:194
+#: draw/models.py:198
msgid "Round 1"
msgstr "Tour 1"
-#: draw/models.py:195
+#: draw/models.py:199
msgid "Round 2"
msgstr "Tour 2"
-#: draw/models.py:197
+#: draw/models.py:200
+msgid "Round 3"
+msgstr "Tour 3"
+
+#: draw/models.py:201
msgid "number"
msgstr "numéro"
-#: draw/models.py:198
-msgid "The number of the round, 1 or 2"
-msgstr "Le numéro du tour, 1 ou 2"
+#: draw/models.py:202
+msgid "The number of the round, 1 or 2 (or 3 for ETEAM)"
+msgstr "Le numéro du tour, 1 ou 2 (ou 3 pour ETEAM)"
-#: draw/models.py:208
+#: draw/models.py:212
msgid "current pool"
msgstr "poule actuelle"
-#: draw/models.py:209
+#: draw/models.py:213
msgid "The current pool where teams select their problems."
msgstr "La poule en cours, où les équipes choisissent leurs problèmes"
-#: draw/models.py:235
+#: draw/models.py:239
+#, python-brace-format
+msgid "The number of the round must be between 1 and {nb}."
+msgstr "Le numéro du tour doit être entre 1 et {nb}."
+
+#: draw/models.py:246
msgid "rounds"
msgstr "tours"
-#: draw/models.py:257 participation/models.py:947
+#: draw/models.py:268 participation/models.py:1143
msgid "letter"
msgstr "lettre"
-#: draw/models.py:258
+#: draw/models.py:269
msgid "The letter of the pool: A, B, C or D."
msgstr "La lettre de la poule : A, B, C ou D."
-#: draw/models.py:262
+#: draw/models.py:273
#: participation/templates/participation/tournament_detail.html:15
msgid "size"
msgstr "taille"
-#: draw/models.py:264
+#: draw/models.py:275
msgid "The number of teams in this pool, between 3 and 5."
msgstr "Le nombre d'équipes dans la poule, entre 3 et 5."
-#: draw/models.py:273
+#: draw/models.py:284
msgid "current team"
msgstr "équipe actuelle"
-#: draw/models.py:274
+#: draw/models.py:285
msgid "The current team that is selecting its problem."
msgstr "L'équipe qui est en train de choisir son problème."
-#: draw/models.py:283
+#: draw/models.py:294
msgid "associated pool"
msgstr "poule associée"
-#: draw/models.py:284
+#: draw/models.py:295
msgid "The full pool instance."
msgstr "L'instance complète de la poule."
-#: draw/models.py:426
+#: draw/models.py:443
#, python-brace-format
msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}"
-#: draw/models.py:430 participation/models.py:1435
+#: draw/models.py:447 participation/models.py:1667
msgid "pools"
msgstr "poules"
-#: draw/models.py:442 participation/models.py:925 participation/models.py:1584
-#: participation/models.py:1614 participation/models.py:1656
+#: draw/models.py:459 participation/models.py:1121 participation/models.py:1886
+#: participation/models.py:1920 participation/models.py:1962
msgid "participation"
msgstr "participation"
-#: draw/models.py:463
+#: draw/models.py:480
msgid "passage index"
msgstr "numéro de passage"
-#: draw/models.py:464
+#: draw/models.py:481
msgid ""
"The passage order in the pool, between 0 and the size of the pool minus 1."
msgstr ""
"L'ordre de passage dans la poule, de 0 à la taille de la poule moins 1."
-#: draw/models.py:472
+#: draw/models.py:489
msgid "choose index"
msgstr "numéro de choix"
-#: draw/models.py:473
+#: draw/models.py:490
msgid ""
"The choice order in the pool, between 0 and the size of the pool minus 1."
msgstr ""
"L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1."
-#: draw/models.py:479 draw/models.py:502 participation/models.py:1457
-#: participation/models.py:1621
+#: 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}"
-#: draw/models.py:483
+#: draw/models.py:500
msgid "accepted problem"
msgstr "problème accepté"
-#: draw/models.py:490
+#: draw/models.py:507
msgid "passage dice"
msgstr "dé d'ordre de passage"
-#: draw/models.py:497
+#: draw/models.py:514
msgid "choice dice"
msgstr "dé d'ordre de choix"
-#: draw/models.py:506
+#: draw/models.py:523
msgid "purposed problem"
msgstr "problème proposé"
-#: draw/models.py:511
+#: draw/models.py:528
msgid "rejected problems"
msgstr "problèmes rejetés"
-#: draw/models.py:540
+#: draw/models.py:557
#, python-brace-format
msgid "Draw of the team {trigram} for the pool {letter}{number}"
msgstr "Tirage de l'équipe {trigram} pour la poule {letter}{number}"
-#: draw/models.py:546
+#: draw/models.py:563
msgid "team draw"
msgstr "tirage d'équipe"
-#: draw/models.py:547
+#: draw/models.py:564
msgid "team draws"
msgstr "tirages d'équipe"
-#: draw/templates/draw/index.html:31
+#: draw/templates/draw/index.html:32
msgid "You don't participate to any tournament."
msgstr "Vous ne participez pas à un tournoi."
@@ -577,6 +824,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:347
+#: draw/templates/draw/tournament_content.html:354
+#: draw/templates/draw/tournament_content.html:361
+#: draw/templates/draw/tournament_content.html:368
+#: draw/templates/draw/tournament_content.html:370
+msgctxt "Role abbreviation"
+msgid "Obs"
+msgstr "Obs"
+
#: draw/templates/draw/tournament_content.html:395
#: draw/templates/draw/tournament_content.html:414
msgid "Abort"
@@ -603,8 +911,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
msgid "Close"
msgstr "Fermer"
-#: draw/views.py:31 participation/views.py:162 participation/views.py:504
-#: participation/views.py:535
+#: draw/views.py:31 participation/views.py:162 participation/views.py:506
+#: participation/views.py:537
msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe."
@@ -612,7 +920,7 @@ msgstr "Vous n'êtes pas dans une équipe."
msgid "Logs"
msgstr "Logs"
-#: logs/models.py:22 registration/models.py:34
+#: logs/models.py:22 registration/models.py:35
msgid "user"
msgstr "utilisateur"
@@ -677,98 +985,103 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}"
msgid "valid"
msgstr "valide"
-#: participation/admin.py:87 participation/models.py:729
+#: participation/admin.py:87 participation/models.py:819
msgid "selected for final"
msgstr "sélectionnée pour la finale"
-#: participation/admin.py:124 participation/admin.py:183
-#: participation/models.py:1464 participation/tables.py:112
-msgid "defender"
+#: participation/admin.py:124 participation/admin.py:188
+#: participation/models.py:1696 participation/tables.py:114
+msgid "reporter"
msgstr "défenseur⋅se"
-#: participation/admin.py:128 participation/models.py:1471
-#: participation/models.py:1668
+#: participation/admin.py:128 participation/models.py:1703
+#: participation/models.py:1974
msgid "opponent"
msgstr "opposant⋅e"
-#: participation/admin.py:132 participation/models.py:1478
-#: participation/models.py:1669
-msgid "reporter"
+#: participation/admin.py:132 participation/models.py:1710
+#: participation/models.py:1975
+msgid "reviewer"
msgstr "rapporteur⋅rice"
-#: participation/admin.py:187 participation/models.py:1619
+#: participation/admin.py:136 participation/models.py:1717
+#: participation/models.py:1976
+msgid "observer"
+msgstr "observateur⋅rice"
+
+#: participation/admin.py:192 participation/models.py:1925
msgid "problem"
msgstr "numéro de problème"
-#: participation/forms.py:29
+#: participation/forms.py:30
msgid "This name is already used."
msgstr "Ce nom est déjà utilisé."
-#: participation/forms.py:36 participation/models.py:42
+#: participation/forms.py:37
msgid "The trigram must be composed of three uppercase letters."
msgstr "Le trigramme doit être composé de trois lettres majuscules."
-#: participation/forms.py:39
+#: participation/forms.py:40
msgid "This trigram is already used."
msgstr "Ce trigramme est déjà utilisé."
-#: participation/forms.py:54
+#: participation/forms.py:55
msgid "No team was found with this access code."
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
-#: participation/forms.py:58 participation/views.py:506
+#: participation/forms.py:59 participation/views.py:508
msgid "The team is already validated or the validation is pending."
msgstr "La validation de l'équipe est déjà faite ou en cours."
-#: participation/forms.py:87 participation/forms.py:347
-#: registration/forms.py:122 registration/forms.py:144
-#: registration/forms.py:166 registration/forms.py:188
-#: registration/forms.py:210 registration/forms.py:232
-#: registration/forms.py:281 registration/forms.py:314
+#: participation/forms.py:94 participation/forms.py:367
+#: registration/forms.py:126 registration/forms.py:148
+#: registration/forms.py:170 registration/forms.py:192
+#: registration/forms.py:214 registration/forms.py:236
+#: registration/forms.py:295 registration/forms.py:328
msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
-#: participation/forms.py:89 registration/forms.py:124
-#: registration/forms.py:146 registration/forms.py:168
-#: registration/forms.py:190 registration/forms.py:212
-#: registration/forms.py:234 registration/forms.py:283
-#: registration/forms.py:316
+#: participation/forms.py:96 registration/forms.py:128
+#: registration/forms.py:150 registration/forms.py:172
+#: registration/forms.py:194 registration/forms.py:216
+#: registration/forms.py:238 registration/forms.py:297
+#: registration/forms.py:330
msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
-#: participation/forms.py:107
-msgid "I engage myself to participate to the whole TFJM²."
-msgstr "Je m'engage à participer à l'intégralité du TFJM²."
+#: participation/forms.py:114
+msgid "I engage myself to participate to the whole tournament."
+msgstr "Je m'engage à participer à l'intégralité du tournoi."
-#: participation/forms.py:122
+#: participation/forms.py:129
msgid "Message to address to the team:"
msgstr "Message à adresser à l'équipe :"
-#: participation/forms.py:157
+#: participation/forms.py:176
msgid "The uploaded file size must be under 5 Mo."
msgstr "Le fichier envoyé doit peser moins de 5 Mo."
-#: participation/forms.py:159 participation/forms.py:349
+#: participation/forms.py:178 participation/forms.py:369
msgid "The uploaded file must be a PDF file."
msgstr "Le fichier envoyé doit être au format PDF."
-#: participation/forms.py:163
+#: participation/forms.py:182
msgid "The PDF file must not have more than 30 pages."
msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages."
-#: participation/forms.py:217
+#: participation/forms.py:236
msgid "Add"
msgstr "Ajouter"
-#: participation/forms.py:232
+#: participation/forms.py:251
msgid "This user already exists, but is a participant."
msgstr "Cet⋅te utilisateur⋅rice existe déjà, mais en tant que participant⋅e."
-#: participation/forms.py:243
+#: participation/forms.py:262
msgid "Spreadsheet file:"
msgstr "Fichier tableur :"
-#: participation/forms.py:269
+#: participation/forms.py:288
msgid ""
"This file contains non-UTF-8 and non-ISO-8859-1 content. Please send your "
"sheet as a CSV file."
@@ -776,48 +1089,53 @@ msgstr ""
"Ce fichier contient des éléments non-UTF-8 et non-ISO-8859-1. Merci "
"d'envoyer votre tableur au format CSV."
-#: participation/forms.py:308
+#: participation/forms.py:328
msgid "The following note is higher of the maximum expected value:"
msgstr "La note suivante est supérieure au maximum attendu :"
-#: participation/forms.py:314
+#: participation/forms.py:334
msgid "The following user was not found:"
msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :"
-#: participation/forms.py:330
-msgid "The defender, the opponent and the reporter must be different."
+#: participation/forms.py:350
+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:334
-msgid "This defender did not work on this problem."
+#: participation/forms.py:354
+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:353
+#: participation/forms.py:373
msgid "The PDF file must not have more than 2 pages."
msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages."
-#: participation/models.py:41 participation/tables.py:39
-msgid "trigram"
-msgstr "trigramme"
+#: participation/models.py:42
+msgid "code"
+msgstr "code"
-#: participation/models.py:47
-msgid "This trigram is forbidden."
-msgstr "Ce trigramme est interdit."
+#: participation/models.py:43
+#, python-brace-format
+msgid "The code must be composed of {nb_letters} uppercase letters."
+msgstr "Le code doit être composé de {nb_letters} lettres majuscules."
-#: participation/models.py:53
+#: participation/models.py:49
+msgid "This team code is forbidden."
+msgstr "Ce code d'équipe est interdit."
+
+#: participation/models.py:55
msgid "access code"
msgstr "code d'accès"
-#: participation/models.py:54
+#: participation/models.py:56
msgid "The access code let other people to join the team."
msgstr "Le code d'accès permet aux autres participants de rejoindre l'équipe."
-#: participation/models.py:58
+#: participation/models.py:60
msgid "motivation letter"
msgstr "lettre de motivation"
-#: participation/models.py:99
+#: participation/models.py:101
#, python-brace-format
msgid ""
"The team {trigram} is not registered to any tournament. You can register the "
@@ -826,11 +1144,11 @@ msgstr ""
"L'équipe {trigram} n'est inscrite à aucun tournoi. Vous pouvez inscrire "
"l'équipe à un tournoi en cliquant sur ce lien."
-#: participation/models.py:104
+#: participation/models.py:106
msgid "No tournament"
msgstr "Pas de tournoi"
-#: participation/models.py:110
+#: participation/models.py:112
msgid ""
"Registrations for the tournament of {tournament} are ending on the {date:%Y-"
"%m-%d %H:%M}."
@@ -838,11 +1156,11 @@ msgstr ""
"Les inscriptions pour le tournoi de {tournament} se terminent le {date:%d/%m/"
"%Y %H:%M}."
-#: participation/models.py:115
+#: participation/models.py:117
msgid "Registrations closure"
msgstr "Clôture des inscriptions"
-#: participation/models.py:122
+#: participation/models.py:124
#, python-brace-format
msgid ""
"The team {trigram} has not uploaded a motivation letter. You can upload your "
@@ -852,11 +1170,11 @@ msgstr ""
"envoyer votre lettre de motivation en cliquant sur ce lien"
"a>."
-#: participation/models.py:127
+#: participation/models.py:129
msgid "No motivation letter"
msgstr "Pas de lettre de motivation"
-#: participation/models.py:136
+#: participation/models.py:138
#, python-brace-format
msgid ""
"The team {trigram} has less than 4 students ({nb_students}). You can invite "
@@ -866,11 +1184,11 @@ msgstr ""
"plus d'élèves à rejoindre l'équipe en utilisant le code d'invitation "
"{code}."
-#: participation/models.py:141
+#: participation/models.py:143
msgid "Not enough students"
msgstr "Pas assez d'élèves"
-#: participation/models.py:148
+#: participation/models.py:150
#, python-brace-format
msgid ""
"The team {trigram} has no coach. You can invite a coach to join the team "
@@ -880,11 +1198,11 @@ msgstr ""
"encadrant⋅e à rejoindre l'équipe en utilisant le code d'invitation "
"{code}."
-#: participation/models.py:152
+#: participation/models.py:154
msgid "No coach"
msgstr "Pas d'encadrant⋅e"
-#: participation/models.py:159
+#: participation/models.py:161
#, python-brace-format
msgid ""
"The team {trigram} has more than 6 students ({nb_students}) or more than 2 "
@@ -895,11 +1213,11 @@ msgstr ""
"encadrant⋅es ({nb_coaches}). Vous devez restreindre le nombre d'élèves et "
"d'encadrant⋅es à 6 et 2, respectivement."
-#: participation/models.py:164
+#: participation/models.py:166
msgid "Too many members"
msgstr "Trop de membres"
-#: participation/models.py:171
+#: participation/models.py:173
#, python-brace-format
msgid ""
"The team {trigram} is ready to be validated. You can request validation on "
@@ -908,11 +1226,11 @@ msgstr ""
"L'équipe {trigram} est prête à être validée. Vous pouvez demander la "
"validation sur la page de votre équipe."
-#: participation/models.py:176 participation/models.py:187
+#: participation/models.py:178 participation/models.py:189
msgid "Validate team"
msgstr "Valider l'équipe"
-#: participation/models.py:182
+#: participation/models.py:184
#, python-brace-format
msgid ""
"The team {trigram} has enough participants, but is not ready to be "
@@ -925,7 +1243,7 @@ msgstr ""
"documents requis. Pour inviter plus de participant⋅es, utilisez le code "
"d'invitation {code}."
-#: participation/models.py:193
+#: participation/models.py:195
#, python-brace-format
msgid ""
"The team {trigram} has not been validated by the organizers yet. Please be "
@@ -934,115 +1252,211 @@ msgstr ""
"L'équipe {trigram} n'a pas encore été validée par les organisateurices. "
"Merci de patienter."
-#: participation/models.py:196 registration/models.py:575
+#: participation/models.py:198 registration/models.py:582
msgid "Pending validation"
msgstr "Validation en attente"
-#: participation/models.py:249
+#: participation/models.py:241
+#, python-brace-format
+msgid "The team code must be composed of {nb_letters} uppercase letters."
+msgstr "Le code d'équipe doit être composé de {nb_letters} lettres majuscules."
+
+#: participation/models.py:258
#, python-brace-format
msgid "Team {name} ({trigram})"
msgstr "Équipe {name} ({trigram})"
-#: participation/models.py:268
+#: participation/models.py:277
msgid "start"
msgstr "début"
-#: participation/models.py:273
+#: participation/models.py:282
msgid "end"
msgstr "fin"
-#: participation/models.py:279
+#: participation/models.py:288
#: participation/templates/participation/tournament_detail.html:18
msgid "place"
msgstr "lieu"
-#: participation/models.py:283
+#: participation/models.py:292
msgid "max team count"
msgstr "nombre maximal d'équipes"
-#: participation/models.py:288
-#: participation/templates/participation/tournament_detail.html:21
+#: participation/models.py:297
+#: participation/templates/participation/tournament_detail.html:22
msgid "price"
msgstr "prix"
-#: participation/models.py:293
-#: participation/templates/participation/tournament_detail.html:24
+#: participation/models.py:302
+#: participation/templates/participation/tournament_detail.html:26
msgid "remote"
msgstr "à distance"
-#: participation/models.py:298
+#: participation/models.py:307
msgid "limit date for registrations"
msgstr "date limite d'inscription"
-#: participation/models.py:303
+#: participation/models.py:312
msgid "limit date to upload solutions"
msgstr "date limite pour envoyer les solutions"
-#: participation/models.py:308
+#: participation/models.py:317
msgid "random draw for solutions"
msgstr "tirage au sort des solutions"
-#: participation/models.py:313
-msgid "limit date to upload the syntheses for the first phase"
+#: participation/models.py:322
+msgid "first phase date"
+msgstr "date du premier tour"
+
+#: participation/models.py:327
+msgid "limit date to upload the written reviews for the first phase"
msgstr "date limite pour envoyer les notes de synthèses pour la première phase"
-#: participation/models.py:318
-msgid "date when the solutions for the second round become available"
-msgstr "date à laquelle les solutions pour le second tour sont accessibles"
+#: participation/models.py:332
+msgid "first second date"
+msgstr "date du second tour"
-#: participation/models.py:323
-msgid "limit date to upload the syntheses for the second phase"
+#: participation/models.py:337
+msgid "check this case when solutions for the second round become available"
+msgstr ""
+"cocher la case lorsque les solutions pour le second tour sont accessibles"
+
+#: participation/models.py:342
+msgid "limit date to upload the written reviews for the second phase"
msgstr "date limite d'envoi des notes de synthèse pour la seconde phase"
-#: participation/models.py:328
-#: participation/templates/participation/tournament_detail.html:48
+#: participation/models.py:347
+msgid "third phase date"
+msgstr "date du troisième tour"
+
+#: participation/models.py:352
+msgid "check this case when solutions for the third round become available"
+msgstr ""
+"cocher la case lorsque les solutions pour le second tour sont accessibles"
+
+#: participation/models.py:357
+msgid "limit date to upload the written reviews for the third phase"
+msgstr ""
+"date limite pour envoyer les notes de synthèses pour la troisième phase"
+
+#: participation/models.py:362
+#: participation/templates/participation/tournament_detail.html:52
msgid "description"
msgstr "description"
-#: participation/models.py:334
+#: participation/models.py:368
#: participation/templates/participation/tournament_detail.html:12
msgid "organizers"
msgstr "organisateur⋅rices"
-#: participation/models.py:339
-#: participation/templates/participation/team_detail.html:161
+#: participation/models.py:373
+#: participation/templates/participation/team_detail.html:167
msgid "final"
msgstr "finale"
-#: participation/models.py:347
+#: participation/models.py:381
msgid "Google Sheet ID"
msgstr "ID de la feuille Google Sheets"
-#: participation/models.py:694 registration/admin.py:125
+#: participation/models.py:461
+msgid "Notation sheet"
+msgstr "Feuille de notation"
+
+#: participation/models.py:473 participation/models.py:474
+#: participation/models.py:476 participation/models.py:716
+msgid "Final ranking"
+msgstr "Classement final"
+
+#: participation/models.py:481 participation/models.py:553
+#: participation/models.py:1327 participation/views.py:1731
+msgid "Team"
+msgstr "Équipe"
+
+#: participation/models.py:481
+msgid "Scores day 1"
+msgstr "Scores jour 1"
+
+#: participation/models.py:481
+msgid "Tweaks day 1"
+msgstr "Ajustements 1"
+
+#: participation/models.py:482
+msgid "Scores day 2"
+msgstr "Scores jour 2"
+
+#: participation/models.py:482
+msgid "Tweaks day 2"
+msgstr "Ajustements 2"
+
+#: participation/models.py:483
+msgid "Total D1 + D2"
+msgstr "Total J1 + J2"
+
+#: participation/models.py:483
+msgid "Scores day 3"
+msgstr "Scores jour 3"
+
+#: participation/models.py:483
+msgid "Tweaks day 3"
+msgstr "Ajustements 3"
+
+#: 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:1327
+#: participation/templates/participation/tournament_harmonize.html:14
+#: participation/views.py:1741
+msgid "Rank"
+msgstr "Rang"
+
+#: participation/models.py:553 participation/models.py:718
+msgid "Score"
+msgstr "Score"
+
+#: participation/models.py:553
+msgid "Mention"
+msgstr "Mention"
+
+#: 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 "
+"automatisée."
+
+#: participation/models.py:784 registration/admin.py:125
msgid "tournaments"
msgstr "tournois"
-#: participation/models.py:723
+#: participation/models.py:813
msgid "valid team"
msgstr "équipe valide"
-#: participation/models.py:724
+#: participation/models.py:814
msgid "The participation got the validation of the organizers."
msgstr "La participation a été validée par les organisateur⋅rices."
-#: participation/models.py:730
+#: participation/models.py:820
msgid "The team is selected for the final tournament."
msgstr "L'équipe est sélectionnée pour la finale."
-#: participation/models.py:734
+#: participation/models.py:824
msgid "mention"
msgstr "mention"
-#: participation/models.py:741
+#: participation/models.py:831
msgid "mention (final)"
msgstr "Mention (pour la finale) :"
-#: participation/models.py:751
+#: participation/models.py:841
#, python-brace-format
msgid "Participation of the team {name} ({trigram})"
msgstr "Participation de l'équipe {name} ({trigram})"
-#: participation/models.py:758
+#: participation/models.py:848
#, python-brace-format
msgid ""
"
The team {trigram} has {nb_missing_payments} missing payments. Each "
@@ -1055,11 +1469,11 @@ msgstr ""
"notification de bourse) pour participer au tournoi.
Les participant⋅es "
"qui n'ont pas encore payé sont : {participants}.
The draw of the solutions for the tournament {tournament} is planned on "
"the {date:%Y-%m-%d %H:%M}. You can join it on this link."
@@ -1101,15 +1515,15 @@ msgstr ""
"{date:%d/%m/%Y %H:%M}. Vous pouvez y participer sur ce lien"
"a>.
"
-#: participation/models.py:819 registration/models.py:607
+#: participation/models.py:909 registration/models.py:614
msgid "Draw of solutions"
msgstr "Tirage au sort des solutions"
-#: participation/models.py:829
+#: participation/models.py:921
#, python-brace-format
msgid ""
"
The solutions draw is ended. You can check the result on this page.
For the first round, you will defend "
+"href='{draw_url}'>this page.
Le tirage au sort des solutions est terminé. Vous pouvez consulter les "
@@ -1117,46 +1531,76 @@ msgstr ""
"tour, vous défendrez votre solution du problème "
"{problem}.
"
-#: participation/models.py:916
+#: participation/models.py:1112
msgid "Tournament ended"
msgstr "Tournoi terminé"
-#: participation/models.py:926 participation/models.py:969
+#: participation/models.py:1122 participation/models.py:1165
msgid "participations"
msgstr "participations"
-#: participation/models.py:941 participation/models.py:942
+#: participation/models.py:1137 participation/models.py:1138
+#: participation/models.py:1139
#, python-brace-format
msgid "Round {round}"
msgstr "Tour {round}"
-#: participation/models.py:957
+#: participation/models.py:1153
msgid "room"
msgstr "salle"
-#: participation/models.py:959
+#: participation/models.py:1155
msgid "Room 1"
msgstr "Salle 1"
-#: participation/models.py:960
+#: participation/models.py:1156
msgid "Room 2"
msgstr "Salle 2"
-#: participation/models.py:963
+#: participation/models.py:1159
msgid "For 5-teams pools only"
msgstr "Pour les poules de 5 équipe uniquement"
-#: participation/models.py:975
+#: participation/models.py:1171
msgid "juries"
msgstr "jurys"
-#: participation/models.py:984
+#: participation/models.py:1180
msgid "president of the jury"
msgstr "président⋅e du jury"
-#: participation/models.py:991
+#: participation/models.py:1187
msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton"
-#: participation/models.py:992
+#: 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:997
+#: participation/models.py:1193
msgid "results available"
msgstr "résultats disponibles"
-#: participation/models.py:998
+#: participation/models.py:1194
msgid ""
"Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given."
@@ -1223,157 +1668,227 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées."
-#: participation/models.py:1026
+#: 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:1415
+#: participation/models.py:1253 participation/models.py:1327
+#: participation/views.py:1486 participation/views.py:1735
+msgid "Problem"
+msgstr "Problème"
+
+#: participation/models.py:1254 participation/views.py:1507
+msgid "Reporter"
+msgstr "Défenseur⋅se"
+
+#: participation/models.py:1255 participation/views.py:1513
+msgid "Opponent"
+msgstr "Opposant⋅e"
+
+#: participation/models.py:1256 participation/views.py:1520
+msgid "Reviewer"
+msgstr "Rapporteur⋅rice"
+
+#: participation/models.py:1257 participation/views.py:1527
+msgid "Observer"
+msgstr "Observateur⋅rice"
+
+#: participation/models.py:1258 participation/views.py:1501
+msgid "Role"
+msgstr "Rôle"
+
+#: participation/models.py:1259 participation/models.py:1261
+#: participation/models.py:1262
+msgid "Writing"
+msgstr "Écrit"
+
+#: participation/models.py:1260 participation/models.py:1261
+#: participation/models.py:1262
+msgid "Oral"
+msgstr "Oral"
+
+#: participation/models.py:1263 participation/views.py:1535
+#: participation/views.py:1536
+msgid "Juree"
+msgstr "Juré⋅e"
+
+#: participation/models.py:1286 participation/models.py:1612
+#: participation/models.py:1634 participation/views.py:1605
+msgid "Average"
+msgstr "Moyenne"
+
+#: participation/models.py:1292 participation/views.py:1624
+msgid "Coefficient"
+msgstr "Coefficien"
+
+#: participation/models.py:1293 participation/views.py:1667
+msgid "Subtotal"
+msgstr "Sous-total"
+
+#: 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: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:1428
+#: 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:1448
+#: participation/models.py:1680
msgid "position"
msgstr "position"
-#: participation/models.py:1455
-msgid "defended solution"
+#: participation/models.py:1687
+msgid "reported solution"
msgstr "solution défendue"
-#: participation/models.py:1483
+#: participation/models.py:1725
msgid "penalties"
msgstr "pénalités"
-#: participation/models.py:1485
+#: 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:1554 participation/models.py:1557
-#: participation/models.py:1560
+#: 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:1565
+#: 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:1569 participation/models.py:1578
-#: participation/models.py:1663 participation/models.py:1705
+#: participation/models.py:1871 participation/models.py:1880
+#: participation/models.py:1969 participation/models.py:2012
msgid "passage"
msgstr "passage"
-#: participation/models.py:1570
+#: participation/models.py:1872
msgid "passages"
msgstr "passages"
-#: participation/models.py:1589
+#: participation/models.py:1891
msgid "difference"
msgstr "différence"
-#: participation/models.py:1590
+#: participation/models.py:1892
msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final"
-#: participation/models.py:1597
+#: participation/models.py:1899
msgid "tweak"
msgstr "harmonisation"
-#: participation/models.py:1598
+#: participation/models.py:1900
msgid "tweaks"
msgstr "harmonisations"
-#: participation/models.py:1626
+#: participation/models.py:1932
msgid "solution for the final tournament"
msgstr "solution pour la finale"
-#: participation/models.py:1631 participation/models.py:1674
+#: participation/models.py:1937 participation/models.py:1981
msgid "file"
msgstr "fichier"
-#: participation/models.py:1641
+#: 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:1643
+#: participation/models.py:1949
msgid "for final"
msgstr "pour la finale"
-#: participation/models.py:1646
+#: participation/models.py:1952
msgid "solution"
msgstr "solution"
-#: participation/models.py:1647
+#: participation/models.py:1953
msgid "solutions"
msgstr "solutions"
-#: participation/models.py:1680
+#: 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:1688
-msgid "synthesis"
+#: participation/models.py:1995
+msgid "written review"
msgstr "note de synthèse"
-#: participation/models.py:1689
-msgid "syntheses"
+#: participation/models.py:1996
+msgid "written reviews"
msgstr "notes de synthèse"
-#: participation/models.py:1698
+#: participation/models.py:2005
msgid "jury"
msgstr "jury"
-#: participation/models.py:1710
-msgid "defender writing note"
+#: participation/models.py:2017
+msgid "reporter writing note"
msgstr "note d'écrit défenseur⋅se"
-#: participation/models.py:1716
-msgid "defender oral note"
+#: participation/models.py:2023
+msgid "reporter oral note"
msgstr "note d'oral défenseur⋅se"
-#: participation/models.py:1722
+#: participation/models.py:2029
msgid "opponent writing note"
msgstr "note d'écrit opposant⋅e"
-#: participation/models.py:1728
+#: participation/models.py:2035
msgid "opponent oral note"
msgstr "note d'oral opposant⋅e"
-#: participation/models.py:1734
-msgid "reporter writing note"
+#: participation/models.py:2041
+msgid "reviewer writing note"
msgstr "note d'écrit rapporteur⋅rice"
-#: participation/models.py:1740
-msgid "reporter oral note"
+#: participation/models.py:2047
+msgid "reviewer oral note"
msgstr "note d'oral du rapporteur⋅rice"
-#: participation/models.py:1800
+#: participation/models.py:2053
+msgid "observer writing note"
+msgstr "note d'écrit de l'observateur⋅rice"
+
+#: participation/models.py:2059
+msgid "observer oral note"
+msgstr "note d'oral de l'observateur⋅rice"
+
+#: participation/models.py:2124
#, python-brace-format
msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}"
-#: participation/models.py:1803
+#: participation/models.py:2127
msgid "note"
msgstr "note"
-#: participation/models.py:1804
+#: participation/models.py:2128
msgid "notes"
msgstr "notes"
+#: participation/tables.py:39
+msgid "trigram"
+msgstr "trigramme"
+
#: participation/tables.py:50
msgid "Validated"
msgstr "Validée"
@@ -1404,32 +1919,32 @@ msgstr "Poule {code}"
msgid "No defined team"
msgstr "Pas d'équipe définie"
-#: participation/tables.py:142
+#: participation/tables.py:147
#: participation/templates/participation/note_form.html:14
#: participation/templates/participation/passage_detail.html:15
-#: participation/templates/participation/passage_detail.html:140
-#: participation/templates/participation/passage_detail.html:146
+#: participation/templates/participation/passage_detail.html:176
+#: participation/templates/participation/passage_detail.html:182
#: participation/templates/participation/pool_detail.html:13
#: participation/templates/participation/pool_detail.html:152
-#: participation/templates/participation/team_detail.html:179
-#: participation/templates/participation/team_detail.html:243
+#: participation/templates/participation/team_detail.html:185
+#: participation/templates/participation/team_detail.html:251
#: participation/templates/participation/tournament_form.html:12
#: participation/templates/participation/update_team.html:12
#: registration/tables.py:46
#: registration/templates/registration/payment_form.html:210
#: registration/templates/registration/update_user.html:16
-#: registration/templates/registration/user_detail.html:211
+#: registration/templates/registration/user_detail.html:220
msgid "Update"
msgstr "Modifier"
#: participation/templates/participation/create_team.html:11
#: participation/templates/participation/tournament_form.html:14
-#: tfjm/templates/base.html:80
+#: tfjm/templates/base.html:85
msgid "Create"
msgstr "Créer"
#: participation/templates/participation/join_team.html:11
-#: tfjm/templates/base.html:75
+#: tfjm/templates/base.html:80
msgid "Join"
msgstr "Rejoindre"
@@ -1496,23 +2011,23 @@ msgid "Upload solution"
msgstr "Envoyer une solution"
#: participation/templates/participation/participation_detail.html:65
-#: participation/templates/participation/passage_detail.html:152
+#: participation/templates/participation/passage_detail.html:188
#: participation/templates/participation/pool_detail.html:157
-#: participation/templates/participation/team_detail.html:238
+#: participation/templates/participation/team_detail.html:245
#: 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
#: registration/templates/registration/upload_vaccine_sheet.html:13
-#: registration/templates/registration/user_detail.html:221
-#: registration/templates/registration/user_detail.html:227
-#: registration/templates/registration/user_detail.html:232
+#: registration/templates/registration/user_detail.html:230
#: registration/templates/registration/user_detail.html:237
-#: registration/templates/registration/user_detail.html:245
-#: registration/templates/registration/user_detail.html:251
+#: registration/templates/registration/user_detail.html:244
+#: registration/templates/registration/user_detail.html:250
+#: registration/templates/registration/user_detail.html:258
+#: registration/templates/registration/user_detail.html:264
msgid "Upload"
msgstr "Téléverser"
@@ -1525,7 +2040,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
@@ -1533,78 +2048,94 @@ msgid "Opponent:"
msgstr "Opposant⋅e :"
#: participation/templates/participation/passage_detail.html:34
-msgid "Reporter:"
+msgid "Reviewer:"
msgstr "Rapporteur⋅rice :"
-#: participation/templates/participation/passage_detail.html:37
-msgid "Defended solution:"
+#: participation/templates/participation/passage_detail.html:38
+msgid "Observer:"
+msgstr "Observateur⋅rice :"
+
+#: participation/templates/participation/passage_detail.html:42
+msgid "Reported solution:"
msgstr "Solution défendue"
-#: participation/templates/participation/passage_detail.html:40
-msgid "Defender penalties count:"
+#: participation/templates/participation/passage_detail.html:45
+msgid "Reporter penalties count:"
msgstr "Nombre de pénalités :"
-#: participation/templates/participation/passage_detail.html:43
+#: participation/templates/participation/passage_detail.html:48
#: participation/templates/participation/pool_detail.html:59
msgid "Syntheses:"
msgstr "Notes de synthèse :"
-#: participation/templates/participation/passage_detail.html:48
+#: 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:56
-#: participation/templates/participation/passage_detail.html:145
+#: participation/templates/participation/passage_detail.html:61
+#: participation/templates/participation/passage_detail.html:181
msgid "Update notes"
msgstr "Modifier les notes"
-#: participation/templates/participation/passage_detail.html:61
-#: participation/templates/participation/passage_detail.html:151
-msgid "Upload synthesis"
-msgstr "Envoyer une note de synthèse"
+#: participation/templates/participation/passage_detail.html:66
+#: participation/templates/participation/passage_detail.html:187
+msgid "Upload review"
+msgstr "Envoyer la note de synthèse"
-#: participation/templates/participation/passage_detail.html:69
+#: participation/templates/participation/passage_detail.html:74
msgid "Notes detail"
msgstr "Détails des notes"
-#: participation/templates/participation/passage_detail.html:77
-msgid "Average points for the defender writing"
+#: participation/templates/participation/passage_detail.html:82
+msgid "Average points for the reporter writing"
msgstr "Moyenne de l'écrit de l'équipe défenseuse"
-#: participation/templates/participation/passage_detail.html:83
-msgid "Average points for the defender oral"
+#: participation/templates/participation/passage_detail.html:90
+msgid "Average points for the reporter oral"
msgstr "Moyenne de l'oral de l'équipe défenseuse"
-#: participation/templates/participation/passage_detail.html:89
+#: participation/templates/participation/passage_detail.html:98
msgid "Average points for the opponent writing"
msgstr "Moyenne de l'écrit de l'équipe opposante"
-#: participation/templates/participation/passage_detail.html:95
+#: participation/templates/participation/passage_detail.html:104
msgid "Average points for the opponent oral"
msgstr "Moyenne de l'oral de l'équipe opposante"
-#: participation/templates/participation/passage_detail.html:101
-msgid "Average points for the reporter writing"
+#: participation/templates/participation/passage_detail.html:110
+msgid "Average points for the reviewer writing"
msgstr "Moyenne de l'écrit de l'équipe rapportrice"
-#: participation/templates/participation/passage_detail.html:107
-msgid "Average points for the reporter oral"
+#: participation/templates/participation/passage_detail.html:116
+msgid "Average points for the reviewer oral"
msgstr "Moyenne de l'oral de l'équipe rapportrice"
-#: participation/templates/participation/passage_detail.html:117
-msgid "Defender points"
+#: participation/templates/participation/passage_detail.html:123
+msgid "Average points for the observer writing"
+msgstr "Moyenne de l'écrit de l'équipe observatrice"
+
+#: participation/templates/participation/passage_detail.html:129
+msgid "Average points for the observer oral"
+msgstr "Moyenne de l'oral de l'équipe observatrice"
+
+#: participation/templates/participation/passage_detail.html:140
+msgid "Reporter points"
msgstr "Points de l'équipe défenseuse"
-#: participation/templates/participation/passage_detail.html:123
+#: participation/templates/participation/passage_detail.html:148
msgid "Opponent points"
msgstr "Points de l'équipe opposante"
-#: participation/templates/participation/passage_detail.html:129
-msgid "Reporter points"
+#: participation/templates/participation/passage_detail.html:156
+msgid "reviewer points"
msgstr "Points de l'équipe rapportrice"
-#: participation/templates/participation/passage_detail.html:139
+#: participation/templates/participation/passage_detail.html:163
+msgid "observer points"
+msgstr "Points de l'équipe observatrice"
+
+#: participation/templates/participation/passage_detail.html:175
#: participation/templates/participation/passage_form.html:11
msgid "Update passage"
msgstr "Modifier le passage"
@@ -1634,7 +2165,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
@@ -1671,7 +2202,7 @@ msgid "Go to the Google Sheets page of the pool"
msgstr "Aller à la page Google Sheets de la poule"
#: participation/templates/participation/pool_detail.html:116
-#: participation/templates/participation/tournament_detail.html:101
+#: participation/templates/participation/tournament_detail.html:109
#: participation/templates/participation/tournament_harmonize.html:8
msgid "Ranking"
msgstr "Classement"
@@ -1766,11 +2297,11 @@ msgstr "Autorisations de droit à l'image :"
#: participation/templates/participation/team_detail.html:58
#: participation/templates/participation/team_detail.html:70
-#: participation/templates/participation/team_detail.html:84
-#: participation/templates/participation/team_detail.html:97
-#: participation/templates/participation/team_detail.html:110
-#: participation/templates/participation/team_detail.html:124
-#: participation/templates/participation/team_detail.html:137
+#: participation/templates/participation/team_detail.html:85
+#: participation/templates/participation/team_detail.html:100
+#: participation/templates/participation/team_detail.html:114
+#: participation/templates/participation/team_detail.html:128
+#: participation/templates/participation/team_detail.html:142
msgid "Not uploaded yet"
msgstr "Pas encore envoyée"
@@ -1778,80 +2309,80 @@ msgstr "Pas encore envoyée"
msgid "Photo authorizations (final):"
msgstr "Autorisations de droit à l'image (finale) :"
-#: participation/templates/participation/team_detail.html:77
+#: participation/templates/participation/team_detail.html:78
msgid "Health sheets:"
msgstr "Fiches sanitaires :"
-#: participation/templates/participation/team_detail.html:90
+#: participation/templates/participation/team_detail.html:93
msgid "Vaccine sheets:"
msgstr "Carnets de vaccination :"
-#: participation/templates/participation/team_detail.html:103
+#: participation/templates/participation/team_detail.html:107
msgid "Parental authorizations:"
msgstr "Autorisations parentales :"
-#: participation/templates/participation/team_detail.html:117
+#: participation/templates/participation/team_detail.html:121
msgid "Parental authorizations (final):"
msgstr "Autorisations parentale (finale) :"
-#: participation/templates/participation/team_detail.html:132
+#: participation/templates/participation/team_detail.html:137
msgid "Motivation letter:"
msgstr "Lettre de motivation :"
-#: participation/templates/participation/team_detail.html:135
+#: participation/templates/participation/team_detail.html:140
#: registration/templates/registration/payment_form.html:247
#: registration/templates/registration/payment_form.html:269
#: registration/templates/registration/upload_health_sheet.html:12
#: registration/templates/registration/upload_parental_authorization.html:12
-#: registration/templates/registration/user_detail.html:69
-#: registration/templates/registration/user_detail.html:80
-#: registration/templates/registration/user_detail.html:92
-#: registration/templates/registration/user_detail.html:102
-#: registration/templates/registration/user_detail.html:112
-#: registration/templates/registration/user_detail.html:123
+#: registration/templates/registration/user_detail.html:72
+#: registration/templates/registration/user_detail.html:83
+#: registration/templates/registration/user_detail.html:96
+#: registration/templates/registration/user_detail.html:108
+#: registration/templates/registration/user_detail.html:119
+#: registration/templates/registration/user_detail.html:130
msgid "Download"
msgstr "Télécharger"
-#: participation/templates/participation/team_detail.html:140
-#: registration/templates/registration/user_detail.html:72
-#: registration/templates/registration/user_detail.html:82
-#: registration/templates/registration/user_detail.html:95
-#: registration/templates/registration/user_detail.html:105
-#: registration/templates/registration/user_detail.html:115
-#: registration/templates/registration/user_detail.html:125
+#: participation/templates/participation/team_detail.html:145
+#: registration/templates/registration/user_detail.html:75
+#: registration/templates/registration/user_detail.html:85
+#: registration/templates/registration/user_detail.html:99
+#: registration/templates/registration/user_detail.html:111
+#: registration/templates/registration/user_detail.html:122
+#: registration/templates/registration/user_detail.html:132
msgid "Replace"
msgstr "Remplacer"
-#: participation/templates/participation/team_detail.html:148
+#: participation/templates/participation/team_detail.html:154
msgid "Download all submitted authorizations"
msgstr "Télécharger toutes les autorisations soumises"
-#: participation/templates/participation/team_detail.html:159
+#: participation/templates/participation/team_detail.html:165
msgid "Payment of"
msgstr "Paiement de"
-#: participation/templates/participation/team_detail.html:160
-#: registration/models.py:677
+#: participation/templates/participation/team_detail.html:166
+#: registration/models.py:684
msgid "grouped"
msgstr "groupé"
-#: participation/templates/participation/team_detail.html:168
-#: registration/templates/registration/user_detail.html:188
-#: registration/views.py:490
+#: participation/templates/participation/team_detail.html:174
+#: registration/templates/registration/user_detail.html:197
+#: registration/views.py:510
msgid "Update payment"
msgstr "Mettre à jour le paiement"
-#: participation/templates/participation/team_detail.html:181
-#: participation/templates/participation/team_detail.html:248
+#: participation/templates/participation/team_detail.html:187
+#: participation/templates/participation/team_detail.html:256
#: participation/templates/participation/team_leave.html:11
msgid "Leave"
msgstr "Quitter"
-#: participation/templates/participation/team_detail.html:191
+#: participation/templates/participation/team_detail.html:197
msgid "Access to team participation"
msgstr "Accéder à la participation de l'équipe"
-#: participation/templates/participation/team_detail.html:198
+#: participation/templates/participation/team_detail.html:204
msgid ""
"Your team has at least 4 members and a coach and all authorizations were "
"given: the team can be validated."
@@ -1859,11 +2390,11 @@ msgstr ""
"Votre équipe contient au moins 4 personnes et un⋅e encadrant⋅e et toutes les "
"autorisations ont été données : l'équipe peut être validée."
-#: participation/templates/participation/team_detail.html:203
+#: participation/templates/participation/team_detail.html:209
msgid "Submit my team to validation"
msgstr "Soumettre mon équipe à validation"
-#: participation/templates/participation/team_detail.html:209
+#: participation/templates/participation/team_detail.html:215
msgid ""
"Your team must be composed of 4 members and a coach and each member must "
"upload their authorizations and confirm its email address."
@@ -1871,15 +2402,15 @@ msgstr ""
"Votre équipe doit être composée d'au moins 4 membres et un⋅e encadrant⋅e et "
"chaque membre doit envoyer ses autorisations et confirmé son adresse e-mail."
-#: participation/templates/participation/team_detail.html:214
+#: participation/templates/participation/team_detail.html:220
msgid "This team didn't ask for validation yet."
msgstr "L'équipe n'a pas encore demandé à être validée."
-#: participation/templates/participation/team_detail.html:220
+#: participation/templates/participation/team_detail.html:226
msgid "Your validation is pending."
msgstr "Votre validation est en attente."
-#: participation/templates/participation/team_detail.html:224
+#: participation/templates/participation/team_detail.html:230
msgid ""
"The team requested to be validated. You may now control the authorizations "
"and confirm that they can participate."
@@ -1887,25 +2418,25 @@ msgstr ""
"L'équipe a demandé à être validée. Vous pouvez désormais contrôler les "
"différentes autorisations et confirmer qu'elle peut participer."
-#: participation/templates/participation/team_detail.html:230
+#: participation/templates/participation/team_detail.html:236
msgid "Validate"
msgstr "Valider"
-#: participation/templates/participation/team_detail.html:231
+#: participation/templates/participation/team_detail.html:237
msgid "Invalidate"
msgstr "Invalider"
-#: participation/templates/participation/team_detail.html:237
-#: participation/views.py:336
+#: participation/templates/participation/team_detail.html:244
+#: participation/views.py:338
msgid "Upload motivation letter"
msgstr "Envoyer la lettre de motivation"
-#: participation/templates/participation/team_detail.html:242
+#: participation/templates/participation/team_detail.html:250
msgid "Update team"
msgstr "Modifier l'équipe"
-#: participation/templates/participation/team_detail.html:247
-#: participation/views.py:498
+#: participation/templates/participation/team_detail.html:255
+#: participation/views.py:500
msgid "Leave team"
msgstr "Quitter l'équipe"
@@ -1917,124 +2448,165 @@ msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?"
msgid "All teams"
msgstr "Toutes les équipes"
-#: participation/templates/participation/tournament_detail.html:22
+#: participation/templates/participation/tournament_detail.html:23
msgid "Free"
msgstr "Gratuit"
-#: participation/templates/participation/tournament_detail.html:27
+#: participation/templates/participation/tournament_detail.html:29
msgid "dates"
msgstr "dates"
-#: participation/templates/participation/tournament_detail.html:28
+#: participation/templates/participation/tournament_detail.html:30
#: registration/templates/registration/mails/payment_confirmation.html:31
#: registration/templates/registration/mails/payment_confirmation.txt:14
msgid "From"
msgstr "Du"
-#: participation/templates/participation/tournament_detail.html:28
+#: participation/templates/participation/tournament_detail.html:30
#: registration/templates/registration/mails/payment_confirmation.html:31
#: registration/templates/registration/mails/payment_confirmation.txt:14
msgid "to"
msgstr "au"
-#: participation/templates/participation/tournament_detail.html:30
+#: participation/templates/participation/tournament_detail.html:32
msgid "date of registration closing"
msgstr "date de clôture des inscriptions"
-#: participation/templates/participation/tournament_detail.html:33
+#: participation/templates/participation/tournament_detail.html:35
msgid "date of maximal solution submission"
msgstr "date limite d'envoi des solutions"
-#: participation/templates/participation/tournament_detail.html:36
+#: participation/templates/participation/tournament_detail.html:38
msgid "date of the random draw"
msgstr "date du tirage au sort"
-#: participation/templates/participation/tournament_detail.html:39
-msgid "date of maximal syntheses submission for the first round"
+#: participation/templates/participation/tournament_detail.html:41
+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:42
-msgid "date when solutions of round 2 are available"
-msgstr ""
-"date à partir de laquelle les solutions pour le second tour sont disponibles"
-
-#: participation/templates/participation/tournament_detail.html:45
-msgid "date of maximal syntheses submission for the second round"
+#: participation/templates/participation/tournament_detail.html:44
+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:51
+#: participation/templates/participation/tournament_detail.html:48
+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
msgid "To contact organizers"
msgstr "Pour contacter les organisateur⋅rices"
-#: participation/templates/participation/tournament_detail.html:54
+#: participation/templates/participation/tournament_detail.html:59
msgid "To contact juries"
msgstr "Pour contacter les juré⋅es"
-#: participation/templates/participation/tournament_detail.html:57
+#: participation/templates/participation/tournament_detail.html:62
msgid "To contact valid teams"
msgstr "Pour contacter les équipes valides"
-#: participation/templates/participation/tournament_detail.html:66
+#: participation/templates/participation/tournament_detail.html:72
msgid "Edit tournament"
msgstr "Modifier le tournoi"
-#: participation/templates/participation/tournament_detail.html:74
-#: tfjm/templates/navbar.html:29
+#: participation/templates/participation/tournament_detail.html:80
+#: tfjm/templates/navbar.html:37
msgid "Teams"
msgstr "Équipes"
-#: participation/templates/participation/tournament_detail.html:82
+#: participation/templates/participation/tournament_detail.html:89
msgid "Access to payments list"
msgstr "Accéder à la liste des paiements"
-#: participation/templates/participation/tournament_detail.html:90
+#: participation/templates/participation/tournament_detail.html:98
msgid "Pools"
msgstr "Poules"
-#: participation/templates/participation/tournament_detail.html:119
+#: participation/templates/participation/tournament_detail.html:127
msgid "Selected for final tournament"
msgstr "Sélectionnée pour la finale"
-#: participation/templates/participation/tournament_detail.html:127
+#: participation/templates/participation/tournament_detail.html:135
msgid "Select for final tournament"
msgstr "Sélectionner pour la finale"
-#: participation/templates/participation/tournament_detail.html:140
-#: participation/templates/participation/tournament_detail.html:144
+#: participation/templates/participation/tournament_detail.html:148
+#: participation/templates/participation/tournament_detail.html:152
+#: participation/templates/participation/tournament_detail.html:157
msgid "Harmonize"
msgstr "Harmoniser"
-#: participation/templates/participation/tournament_detail.html:140
-#: participation/templates/participation/tournament_detail.html:144
+#: participation/templates/participation/tournament_detail.html:148
+#: participation/templates/participation/tournament_detail.html:152
+#: participation/templates/participation/tournament_detail.html:157
msgid "Day"
msgstr "Jour"
-#: participation/templates/participation/tournament_detail.html:153
+#: participation/templates/participation/tournament_detail.html:167
msgid "Publish notes for first round"
msgstr "Publier les notes pour le premier tour"
-#: participation/templates/participation/tournament_detail.html:158
+#: participation/templates/participation/tournament_detail.html:172
msgid "Unpublish notes for first round"
msgstr "Dépublier les notes pour le premier tour"
-#: participation/templates/participation/tournament_detail.html:164
+#: participation/templates/participation/tournament_detail.html:178
msgid "Publish notes for second round"
msgstr "Publier les notes pour le second tour"
-#: participation/templates/participation/tournament_detail.html:169
+#: participation/templates/participation/tournament_detail.html:183
msgid "Unpublish notes for second round"
msgstr "Dépublier les notes pour le second tour"
-#: participation/templates/participation/tournament_detail.html:181
+#: participation/templates/participation/tournament_detail.html:190
+msgid "Publish notes for third round"
+msgstr "Publier les notes pour le troisième tour"
+
+#: participation/templates/participation/tournament_detail.html:195
+msgid "Unpublish notes for third round"
+msgstr "Dépublier les notes pour le troisième tour"
+
+#: participation/templates/participation/tournament_detail.html:208
msgid "Files available for download"
msgstr "Fichiers disponibles au téléchargement"
-#: participation/templates/participation/tournament_harmonize.html:14
-msgid "Rank"
-msgstr "Rang"
+#: participation/templates/participation/tournament_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:648
+#: registration/models.py:655
msgid "Note"
msgstr "Note"
@@ -2052,7 +2624,7 @@ msgid "Back to tournament page"
msgstr "Retour à la page du tournoi"
#: participation/templates/participation/tournament_list.html:6
-#: tfjm/templates/base.html:67
+#: tfjm/templates/base.html:72
msgid "All tournaments"
msgstr "Tous les tournois"
@@ -2075,16 +2647,12 @@ msgstr ""
msgid "Download empty notation sheet"
msgstr "Télécharger la fiche de notation vierge"
-#: participation/templates/participation/upload_synthesis.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"
-#: participation/views.py:62 tfjm/templates/base.html:79
-#: tfjm/templates/navbar.html:35
+#: participation/views.py:62 tfjm/templates/base.html:84
+#: tfjm/templates/navbar.html:43
msgid "Create team"
msgstr "Créer une équipe"
@@ -2096,12 +2664,12 @@ msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
msgid "You are already in a team."
msgstr "Vous êtes déjà dans une équipe."
-#: participation/views.py:103 tfjm/templates/base.html:74
-#: tfjm/templates/navbar.html:40
+#: participation/views.py:103 tfjm/templates/base.html:79
+#: tfjm/templates/navbar.html:48
msgid "Join team"
msgstr "Rejoindre une équipe"
-#: participation/views.py:163 participation/views.py:536
+#: participation/views.py:163 participation/views.py:538
msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
@@ -2137,170 +2705,170 @@ msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente."
-#: participation/views.py:279
+#: participation/views.py:281
msgid "You must specify if you validate the registration or not."
msgstr "Vous devez spécifier si vous validez l'inscription ou non."
-#: participation/views.py:314
+#: participation/views.py:316
#, python-brace-format
msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {trigram}"
-#: participation/views.py:375 participation/views.py:483
+#: participation/views.py:377 participation/views.py:485
#, python-brace-format
msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}"
-#: participation/views.py:408
+#: participation/views.py:410
#, python-brace-format
msgid "Authorizations of team {trigram}.zip"
msgstr "Autorisations de l'équipe {trigram}.zip"
-#: participation/views.py:412
+#: participation/views.py:414
#, python-brace-format
msgid "Authorizations of {tournament}.zip"
msgstr "Autorisations du tournoi {tournament}.zip"
-#: participation/views.py:431
+#: participation/views.py:433
#, python-brace-format
msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
-#: participation/views.py:440
+#: participation/views.py:442
#, python-brace-format
msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}"
-#: participation/views.py:448
+#: participation/views.py:450
#, python-brace-format
msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}"
-#: participation/views.py:456
+#: participation/views.py:458
#, python-brace-format
msgid "Vaccine sheet of {participant}.{ext}"
msgstr "Carnet de vaccination de {participant}.{ext}"
-#: participation/views.py:467
+#: participation/views.py:469
#, python-brace-format
msgid "Photo authorization of {participant} (final).{ext}"
msgstr "Autorisation de droit à l'image de {participant} (finale).{ext}"
-#: participation/views.py:476
+#: participation/views.py:478
#, python-brace-format
msgid "Parental authorization of {participant} (final).{ext}"
msgstr "Autorisation parentale de {participant} (finale).{ext}"
-#: participation/views.py:550
+#: participation/views.py:552
msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée."
-#: participation/views.py:564
+#: participation/views.py:566
#, python-brace-format
msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}"
-#: participation/views.py:652
+#: participation/views.py:655
#, python-brace-format
msgid "Payments of {tournament}"
msgstr "Paiements de {tournament}"
-#: participation/views.py:751
+#: participation/views.py:755
msgid "Notes published!"
msgstr "Notes publiées !"
-#: participation/views.py:753
+#: participation/views.py:757
msgid "Notes hidden!"
msgstr "Notes dissimulées !"
-#: participation/views.py:784
+#: participation/views.py:788
#, python-brace-format
msgid "Harmonize notes of {tournament} - Day {round}"
msgstr "Harmoniser les notes de {tournament} - Jour {round}"
-#: participation/views.py:897
+#: participation/views.py:902
msgid "You can't upload a solution after the deadline."
msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
-#: participation/views.py:1017
+#: participation/views.py:1022
#, python-brace-format
msgid "Solutions of team {trigram}.zip"
msgstr "Solutions de l'équipe {trigram}.zip"
-#: participation/views.py:1017
+#: 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:1034 participation/views.py:1049
+#: participation/views.py:1040 participation/views.py:1056
#, python-brace-format
msgid "Solutions of {tournament}.zip"
msgstr "Solutions de {tournament}.zip"
-#: participation/views.py:1034 participation/views.py:1049
+#: 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:1058
+#: 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:1059
+#: 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:1101
+#: 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:1117
+#: 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:1137
-msgid "New TFJM² jury account"
-msgstr "Nouveau compte de juré⋅e pour le TFJM²"
+#: participation/views.py:1145
+msgid "New jury account"
+msgstr "Nouveau compte de juré⋅e"
-#: participation/views.py:1158
+#: 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:1194
+#: 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:1220
+#: 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:1248
+#: 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:1264
+#: participation/views.py:1272
msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées."
-#: participation/views.py:1842
+#: participation/views.py:1904
#, 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:1847
+#: participation/views.py:1909
#, python-brace-format
msgid "Notation sheets of {tournament}.zip"
msgstr "Feuilles de notation de {tournament}.zip"
-#: participation/views.py:2012
-msgid "You can't upload a synthesis after the deadline."
+#: participation/views.py:2076
+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
@@ -2330,44 +2898,44 @@ msgstr "Marquer comme en attente"
msgid "Mark as invalid"
msgstr "Marquer comme invalide"
-#: registration/forms.py:22
+#: registration/forms.py:23
msgid "role"
msgstr "rôle"
-#: registration/forms.py:24
+#: registration/forms.py:25
msgid "participant"
msgstr "participant⋅e"
-#: registration/forms.py:25 registration/models.py:509
+#: registration/forms.py:26 registration/models.py:516
msgid "coach"
msgstr "encadrant⋅e"
-#: registration/forms.py:35 registration/forms.py:60 registration/forms.py:91
+#: registration/forms.py:36 registration/forms.py:61 registration/forms.py:92
msgid "This email address is already used."
msgstr "Cette adresse e-mail est déjà utilisée."
-#: registration/forms.py:272
+#: registration/forms.py:286
msgid "Pending"
msgstr "En attente"
-#: registration/forms.py:291 registration/forms.py:324
+#: registration/forms.py:305 registration/forms.py:338
msgid "You must upload your receipt."
msgstr "Vous devez envoyer votre justificatif."
-#: registration/models.py:39
+#: registration/models.py:40
msgid "Grant Animath to contact me in the future about other actions"
msgstr ""
"Autoriser Animath à me recontacter à l'avenir à propos d'autres actions"
-#: registration/models.py:44
+#: registration/models.py:45
msgid "email confirmed"
msgstr "email confirmé"
-#: registration/models.py:52
-msgid "Activate your TFJM² account"
-msgstr "Activez votre compte du TFJM²"
+#: registration/models.py:53
+msgid "Activate your account"
+msgstr "Activez votre compte"
-#: registration/models.py:109
+#: registration/models.py:110
#, python-brace-format
msgid ""
"Your email address is not validated. Please click on the link you received "
@@ -2378,52 +2946,56 @@ msgstr ""
"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur ce lien."
-#: registration/models.py:129
+#: registration/models.py:130
msgid "registration"
msgstr "inscription"
-#: registration/models.py:130 registration/models.py:673
+#: registration/models.py:131 registration/models.py:680
msgid "registrations"
msgstr "inscriptions"
-#: registration/models.py:162
+#: registration/models.py:163
msgid "gender"
msgstr "genre"
-#: registration/models.py:164
+#: registration/models.py:165
msgid "Female"
msgstr "Femme"
-#: registration/models.py:165
+#: registration/models.py:166
msgid "Male"
msgstr "Homme"
-#: registration/models.py:166
+#: registration/models.py:167
#: registration/templates/registration/payment_form.html:89
msgid "Other"
msgstr "Autre"
-#: registration/models.py:173
+#: registration/models.py:174
msgid "address"
msgstr "adresse"
-#: registration/models.py:177
+#: registration/models.py:178
msgid "zip code"
msgstr "code postal"
-#: registration/models.py:183
+#: registration/models.py:184
msgid "city"
msgstr "ville"
-#: registration/models.py:187
+#: registration/models.py:189
+msgid "country"
+msgstr "pays"
+
+#: registration/models.py:194
msgid "phone number"
msgstr "numéro de téléphone"
-#: registration/models.py:192
+#: registration/models.py:199
msgid "health issues"
msgstr "problèmes de santé"
-#: registration/models.py:194
+#: registration/models.py:201
msgid ""
"You can indicate here your allergies or anything that is important to know "
"for organizers."
@@ -2431,11 +3003,11 @@ msgstr ""
"Vous pouvez indiquer ici vos allergies ou n'importe quoi qui peut être bon à "
"savoir pour les organisateur⋅rices."
-#: registration/models.py:198
+#: registration/models.py:205
msgid "housing constraints"
msgstr "contraintes de logement"
-#: registration/models.py:200
+#: registration/models.py:207
msgid ""
"You can fill in something here if you have any housing constraints, e.g. "
"medical problems, scheduling issues, gender issues, or anything else you "
@@ -2448,15 +3020,15 @@ msgstr ""
"organisateur⋅rices. Laissez vide si vous n'avez rien de spécifique à "
"déclarer."
-#: registration/models.py:207
+#: registration/models.py:214
msgid "photo authorization"
msgstr "autorisation de droit à l'image"
-#: registration/models.py:214
+#: registration/models.py:221
msgid "photo authorization (final)"
msgstr "autorisation de droit à l'image (finale)"
-#: registration/models.py:258
+#: registration/models.py:265
#, python-brace-format
msgid ""
"You are not in a team. You can create one or ou rejoindre une équipe existante pour "
"participer."
-#: registration/models.py:264
+#: registration/models.py:271
msgid "No team"
msgstr "Pas d'équipe"
-#: registration/models.py:272
+#: registration/models.py:279
#, python-brace-format
msgid ""
"You have not uploaded your photo authorization. You can do it by clicking on "
@@ -2479,11 +3051,11 @@ msgstr ""
"Vous n'avez pas envoyé votre autorisation de droit à l'image. Vous pouvez le "
"faire en cliquant sur ce lien."
-#: registration/models.py:277 registration/models.py:290
+#: registration/models.py:284 registration/models.py:297
msgid "Photo authorization"
msgstr "Autorisation de droit à l'image"
-#: registration/models.py:285
+#: registration/models.py:292
#, python-brace-format
msgid ""
"You have not uploaded your photo authorization for the final tournament. You "
@@ -2492,75 +3064,75 @@ msgstr ""
"Vous n'avez pas envoyé votre autorisation de droit à l'image pour la finale. "
"Vous pouvez le faire en cliquant sur ce lien."
-#: registration/models.py:305
+#: registration/models.py:312
msgid "Team selected for the final tournament"
msgstr "Équipe sélectionnée pour la finale"
-#: registration/models.py:327
+#: registration/models.py:334
msgid "participant registration"
msgstr "inscription de participant⋅e"
-#: registration/models.py:328
+#: registration/models.py:335
msgid "participant registrations"
msgstr "inscriptions de participant⋅es"
-#: registration/models.py:337
+#: registration/models.py:344
msgid "birth date"
msgstr "date de naissance"
-#: registration/models.py:343
+#: registration/models.py:350
msgid "12th grade"
msgstr "Terminale"
-#: registration/models.py:344
+#: registration/models.py:351
msgid "11th grade"
msgstr "Première"
-#: registration/models.py:345
+#: registration/models.py:352
msgid "10th grade or lower"
msgstr "Seconde ou inférieur"
-#: registration/models.py:347
+#: registration/models.py:354
msgid "student class"
msgstr "classe"
-#: registration/models.py:352
+#: registration/models.py:359
msgid "school"
msgstr "école"
-#: registration/models.py:357
+#: registration/models.py:364
msgid "responsible name"
msgstr "nom d'un⋅e responsable légal⋅e"
-#: registration/models.py:362
+#: registration/models.py:369
msgid "responsible phone number"
msgstr "numéro de téléphone d'un⋅e responsable légal⋅e"
-#: registration/models.py:367
+#: registration/models.py:374
msgid "responsible email address"
msgstr "adresse e-mail d'un⋅e responsable légal⋅e"
-#: registration/models.py:372
+#: registration/models.py:379
msgid "parental authorization"
msgstr "autorisation parentale"
-#: registration/models.py:379
+#: registration/models.py:386
msgid "parental authorization (final)"
msgstr "autorisation parentale (finale)"
-#: registration/models.py:386
+#: registration/models.py:393
msgid "health sheet"
msgstr "fiche sanitaire"
-#: registration/models.py:393
+#: registration/models.py:400
msgid "vaccine sheet"
msgstr "carnet de vaccination"
-#: registration/models.py:401
+#: registration/models.py:408
msgid "student"
msgstr "élève"
-#: registration/models.py:412
+#: registration/models.py:419
#, python-brace-format
msgid ""
"You have not uploaded your parental authorization. You can do it by clicking "
@@ -2569,11 +3141,11 @@ msgstr ""
"Vous n'avez pas envoyé votre autorisation parentale. Vous pouvez le faire en "
"cliquant sur ce lien."
-#: registration/models.py:417 registration/models.py:477
+#: registration/models.py:424 registration/models.py:484
msgid "Parental authorization"
msgstr "Autorisation parentale"
-#: registration/models.py:423
+#: registration/models.py:430
#, python-brace-format
msgid ""
"You have not uploaded your health sheet. You can do it by clicking on ce lien."
-#: registration/models.py:428
+#: registration/models.py:435
msgid "Health sheet"
msgstr "Fiche sanitaire"
-#: registration/models.py:434
+#: registration/models.py:441
#, python-brace-format
msgid ""
"You have not uploaded your vaccine sheet. You can do it by clicking on ce lien."
-#: registration/models.py:439
+#: registration/models.py:446
msgid "Vaccine sheet"
msgstr "Carnet de vaccination"
-#: registration/models.py:448
+#: registration/models.py:455
#, python-brace-format
msgid ""
"You have to pay {amount} € for your registration, or send a scholarship "
@@ -2610,15 +3182,15 @@ msgstr ""
"notification de bourse ou un justificatif de paiement. Vous pouvez le faire "
"sur la page de paiement."
-#: registration/models.py:454 registration/models.py:463
+#: registration/models.py:461 registration/models.py:470
msgid "Payment"
msgstr "Paiement"
-#: registration/models.py:460
+#: registration/models.py:467
msgid "Your payment is under approval."
msgstr "Votre paiement est en cours de validation."
-#: registration/models.py:471
+#: registration/models.py:478
#, python-brace-format
msgid ""
"You have not uploaded your parental authorization for the final tournament. "
@@ -2627,19 +3199,19 @@ msgstr ""
"Vous n'avez pas envoyé votre autorisation parentale pour la finale. Vous "
"pouvez le faire en cliquant sur ce lien."
-#: registration/models.py:486
+#: registration/models.py:493
msgid "student registration"
msgstr "inscription d'élève"
-#: registration/models.py:487
+#: registration/models.py:494
msgid "student registrations"
msgstr "inscriptions d'élève"
-#: registration/models.py:498
+#: registration/models.py:505
msgid "most recent degree in mathematics, computer science or physics"
msgstr "Dernier diplôme obtenu en mathématiques, informatique ou physique"
-#: registration/models.py:499
+#: registration/models.py:506
msgid ""
"Your most recent degree in maths, computer science or physics, or your last "
"entrance exam (CAPES, Agrégation,…)"
@@ -2647,23 +3219,23 @@ msgstr ""
"Votre dernier diplôme en mathématiques, informatique ou physique, ou votre "
"dernier concours obtenu (CAPES, Agrégation, …)"
-#: registration/models.py:504 registration/models.py:526
+#: registration/models.py:511 registration/models.py:533
msgid "professional activity"
msgstr "activité professionnelle"
-#: registration/models.py:517
+#: registration/models.py:524
msgid "coach registration"
msgstr "inscription d'encadrant⋅e"
-#: registration/models.py:518
+#: registration/models.py:525
msgid "coach registrations"
msgstr "inscriptions d'encadrant⋅es"
-#: registration/models.py:530
+#: registration/models.py:537
msgid "administrator"
msgstr "administrateur⋅rice"
-#: registration/models.py:531
+#: registration/models.py:538
msgid ""
"An administrator has all rights. Please don't give this right to all juries "
"and volunteers."
@@ -2671,15 +3243,15 @@ msgstr ""
"Un⋅e administrateur⋅rice a tous les droits. Merci de ne pas donner ce droit "
"à toustes les juré⋅es et bénévoles."
-#: registration/models.py:541
+#: registration/models.py:548
msgid "admin"
msgstr "admin"
-#: registration/models.py:541
+#: registration/models.py:548
msgid "volunteer"
msgstr "bénévole"
-#: registration/models.py:554
+#: registration/models.py:561
msgid ""
"Registrations for tournament {tournament} are closing on {date:%Y-%m-%d %H:"
"%M}. There are for now {validated_teams} validated teams (+ {pending_teams} "
@@ -2689,11 +3261,11 @@ msgstr ""
"%M}. Il y a pour l'instant {validated_teams} équipes validées (+ "
"{pending_teams} en attente) sur {max_teams} attendues."
-#: registration/models.py:562
+#: registration/models.py:569
msgid "Registrations"
msgstr "Inscriptions"
-#: registration/models.py:569
+#: registration/models.py:576
#, python-brace-format
msgid ""
"The team {trigram} requested to be validated for the tournament of "
@@ -2704,7 +3276,7 @@ msgstr ""
"Vous pouvez vérifier le statut de l'équipe sur la page de "
"l'équipe."
-#: registration/models.py:586
+#: registration/models.py:593
#, python-brace-format
msgid ""
"There are {valid} validated payments, {pending} pending and {invalid} "
@@ -2715,11 +3287,11 @@ msgstr ""
"invalides pour le tournoi {tournament}. Vous pouvez vérifier le statut des "
"paiements sur la liste des paiements."
-#: registration/models.py:593
+#: registration/models.py:600
msgid "Payments"
msgstr "Paiements"
-#: registration/models.py:617 registration/models.py:628
+#: registration/models.py:624 registration/models.py:635
#, python-brace-format
msgid ""
"
You are in the jury of the pool {pool} for the tournament of "
@@ -2728,7 +3300,7 @@ msgstr ""
"
Vous êtes dans le jury de la poule {pool} pour le tournoi {tournament}. "
"Vous pouvez trouver la page de la poule ici.
You don't have given any note as a jury for the passage {passage} in the "
@@ -2739,15 +3311,15 @@ msgstr ""
"{passage} dans la poule {pool} de {tournament}. Vous pouvez donner vos notes "
"ici.
"
-#: registration/models.py:657
+#: registration/models.py:664
msgid "volunteer registration"
msgstr "inscription de bénévole"
-#: registration/models.py:658
+#: registration/models.py:665
msgid "volunteer registrations"
msgstr "inscriptions de bénévoles"
-#: registration/models.py:679
+#: registration/models.py:686
msgid ""
"If set to true, then one payment is made for the full team, for example if "
"the school pays for all."
@@ -2755,100 +3327,100 @@ msgstr ""
"Si vrai, alors un seul paiement est fait pour toute l'équipe, par exemple si "
"le lycée paie pour tout le monde."
-#: registration/models.py:684
+#: registration/models.py:691
msgid "total amount"
msgstr "montant total"
-#: registration/models.py:685
+#: registration/models.py:692
msgid "Corresponds to the total required amount to pay, in euros."
msgstr "Correspond au montant total à payer, en euros."
-#: registration/models.py:690
+#: registration/models.py:697
msgid "token"
msgstr "jeton"
-#: registration/models.py:693
+#: registration/models.py:700
msgid "A token to authorize external users to make this payment."
msgstr "Un jeton pour autoriser des utilisateurs externes à faire ce paiement."
-#: registration/models.py:697
+#: registration/models.py:704
msgid "for final tournament"
msgstr "pour la finale"
-#: registration/models.py:702
+#: registration/models.py:709
msgid "type"
msgstr "type"
-#: registration/models.py:705
+#: registration/models.py:712
msgid "No payment"
msgstr "Pas de paiement"
-#: registration/models.py:706
+#: registration/models.py:713
#: registration/templates/registration/payment_form.html:72
msgid "Credit card"
msgstr "Carte bancaire"
-#: registration/models.py:707
+#: registration/models.py:714
msgid "Scholarship"
msgstr "Notification de bourse"
-#: registration/models.py:708
+#: registration/models.py:715
#: registration/templates/registration/payment_form.html:77
msgid "Bank transfer"
msgstr "Virement bancaire"
-#: registration/models.py:709
+#: registration/models.py:716
msgid "Other (please indicate)"
msgstr "Autre (veuillez spécifier)"
-#: registration/models.py:710
+#: registration/models.py:717
msgid "The tournament is free"
msgstr "Le tournoi est gratuit"
-#: registration/models.py:717
+#: registration/models.py:724
msgid "Hello Asso checkout intent ID"
msgstr "ID de l'intention de paiement Hello Asso"
-#: registration/models.py:724
+#: registration/models.py:731
msgid "receipt"
msgstr "justificatif"
-#: registration/models.py:725
+#: registration/models.py:732
msgid "only if you have a scholarship or if you chose a bank transfer."
msgstr ""
"Nécessaire seulement si vous déclarez être boursièr⋅e ou si vous payez par "
"virement bancaire."
-#: registration/models.py:732
+#: registration/models.py:739
msgid "additional information"
msgstr "informations additionnelles"
-#: registration/models.py:733
+#: registration/models.py:740
msgid "To help us to find your payment."
msgstr "Pour nous aider à retrouver votre paiement, si nécessaire."
-#: registration/models.py:739
+#: registration/models.py:746
msgid "payment valid"
msgstr "paiement valide"
-#: registration/models.py:799
+#: registration/models.py:806
msgid "Reminder for your payment"
msgstr "Rappel pour votre paiement"
-#: registration/models.py:810
+#: registration/models.py:817
msgid "Payment confirmation"
msgstr "Confirmation de paiement"
-#: registration/models.py:832
+#: registration/models.py:839
#, python-brace-format
msgid "Payment of {registrations}"
msgstr "Paiements de {registrations}"
-#: registration/models.py:835
+#: registration/models.py:842
msgid "payment"
msgstr "paiement"
-#: registration/models.py:836
+#: registration/models.py:843
msgid "payments"
msgstr "paiements"
@@ -2900,14 +3472,14 @@ msgstr ""
"Un email a été envoyé. Merci de cliquer sur le lien pour activer votre "
"compte."
-#: registration/templates/registration/mails/add_organizer.html:37
-#: registration/templates/registration/mails/add_organizer.txt:17
+#: registration/templates/registration/mails/add_organizer.html:36
+#: registration/templates/registration/mails/add_organizer.txt:14
#: registration/templates/registration/mails/email_validation_email.html:39
#: registration/templates/registration/mails/email_validation_email.txt:15
-#: registration/templates/registration/mails/payment_confirmation.html:45
+#: registration/templates/registration/mails/payment_confirmation.html:41
#: registration/templates/registration/mails/payment_reminder.html:52
-msgid "The TFJM² team."
-msgstr "L'équipe du TFJM²"
+msgid "The ETEAM team."
+msgstr "L'équipe de l'ETEAM"
#: registration/templates/registration/mails/email_validation_email.html:12
#: registration/templates/registration/mails/email_validation_email.txt:3
@@ -2921,11 +3493,11 @@ msgstr "Bonjour"
#: registration/templates/registration/mails/email_validation_email.html:16
#: registration/templates/registration/mails/email_validation_email.txt:5
msgid ""
-"You recently registered on the TFJM² platform. Please click on the link "
+"You recently registered on the ETEAM platform. Please click on the link "
"below to confirm your registration."
msgstr ""
-"Vous vous êtes inscrit⋅e sur la plateforme du TFJM². Merci de cliquer sur le "
-"lien ci-dessous pour confirmer votre inscription."
+"Vous vous êtes inscrit⋅e sur la plateforme de l'ETEAM. Merci de cliquer sur "
+"le lien ci-dessous pour confirmer votre inscription."
#: registration/templates/registration/mails/email_validation_email.html:26
#: registration/templates/registration/mails/email_validation_email.txt:9
@@ -2951,10 +3523,10 @@ msgstr "Merci"
#, python-format
msgid ""
"We successfully received the payment of %(amount)s € for your participation "
-"for the TFJM² in the team %(team)s for the tournament %(tournament)s!"
+"for the ETEAM in the team %(team)s!"
msgstr ""
-"Nous avons bien reçu le paiement de %(amount)s € pour la participation au "
-"TFJM² de %(tournament)s dans l'équipe %(team)s !"
+"Nous avons bien reçu le paiement de %(amount)s € pour la participation à "
+"l'ETEAM dans l'équipe %(team)s !"
#: registration/templates/registration/mails/payment_confirmation.html:22
#: registration/templates/registration/mails/payment_confirmation.txt:8
@@ -2995,16 +3567,6 @@ msgstr "Dates du tournoi :"
#: registration/templates/registration/mails/payment_confirmation.html:36
#: registration/templates/registration/mails/payment_confirmation.txt:16
msgid ""
-"Please note that these dates may be subject to change. If your local "
-"organizers gave you different dates, trust them."
-msgstr ""
-"Veuillez noter que ces dates peuvent être sujettes à changement. Si vos "
-"organisateur⋅rices locaux⋅ales vous ont donné des dates différentes, faites-"
-"leur confiance."
-
-#: registration/templates/registration/mails/payment_confirmation.html:40
-#: registration/templates/registration/mails/payment_confirmation.txt:18
-msgid ""
"NB: This mail don't represent a payment receipt. The payer should receive a "
"mail from Hello Asso. If it is not the case, please contact us if necessary"
msgstr ""
@@ -3012,21 +3574,20 @@ msgstr ""
"a payé devrait recevoir un mail de Hello Asso. Si ce n'est pas le cas, merci "
"de nous contacter si nécessaire."
-#: registration/templates/registration/mails/payment_confirmation.txt:21
-msgid "The TFJM² team"
-msgstr "L'équipe du TFJM²"
+#: registration/templates/registration/mails/payment_confirmation.txt:19
+msgid "The ETEAM team"
+msgstr "L'équipe de l'ETEAM"
#: registration/templates/registration/mails/payment_reminder.html:16
#: registration/templates/registration/mails/payment_reminder.txt:4
#, python-format
msgid ""
-"You are registered for the TFJM² of %(tournament)s. Your team %(team)s has "
-"been successfully validated. To end your inscription, you must pay the "
-"amount of %(amount)s €."
+"You are registered for the ETEAM. Your team %(team)s has been successfully "
+"validated. To end your inscription, you must pay the amount of %(amount)s €."
msgstr ""
-"Vous êtes inscrit⋅e pour le TFJM² du %(tournament)s. Votre équipe %(team)s a "
-"été validée avec succès. Pour finaliser votre inscription, vous devez payer "
-"le montant de %(amount)s €."
+"Vous êtes inscrit⋅e pour l'ETEAM. Votre équipe %(team)s a été validée avec "
+"succès. Pour finaliser votre inscription, vous devez payer le montant de "
+"%(amount)s €."
#: registration/templates/registration/mails/payment_reminder.html:24
#: registration/templates/registration/mails/payment_reminder.txt:9
@@ -3423,139 +3984,139 @@ msgstr "Genre :"
msgid "Address:"
msgstr "Adresse :"
-#: registration/templates/registration/user_detail.html:53
+#: registration/templates/registration/user_detail.html:56
msgid "Phone number:"
msgstr "Numéro de téléphone :"
-#: registration/templates/registration/user_detail.html:57
+#: registration/templates/registration/user_detail.html:60
msgid "Health issues:"
msgstr "Problèmes de santé :"
-#: registration/templates/registration/user_detail.html:62
+#: registration/templates/registration/user_detail.html:65
msgid "Housing constraints:"
msgstr "Contraintes de logement :"
-#: registration/templates/registration/user_detail.html:66
+#: registration/templates/registration/user_detail.html:69
msgid "Photo authorization:"
msgstr "Autorisation de droit à l'image :"
-#: registration/templates/registration/user_detail.html:77
+#: registration/templates/registration/user_detail.html:80
msgid "Photo authorization (final):"
msgstr "Autorisation de droit à l'image (finale) :"
-#: registration/templates/registration/user_detail.html:89
+#: registration/templates/registration/user_detail.html:93
msgid "Health sheet:"
msgstr "Fiche sanitaire :"
-#: registration/templates/registration/user_detail.html:99
+#: registration/templates/registration/user_detail.html:105
msgid "Vaccine sheet:"
msgstr "Carnet de vaccination :"
-#: registration/templates/registration/user_detail.html:109
+#: registration/templates/registration/user_detail.html:116
msgid "Parental authorization:"
msgstr "Autorisation parentale :"
-#: registration/templates/registration/user_detail.html:120
+#: registration/templates/registration/user_detail.html:127
msgid "Parental authorization (final):"
msgstr "Autorisation parentale (finale) :"
-#: registration/templates/registration/user_detail.html:130
+#: registration/templates/registration/user_detail.html:137
msgid "Student class:"
msgstr "Classe :"
-#: registration/templates/registration/user_detail.html:133
+#: registration/templates/registration/user_detail.html:140
msgid "School:"
msgstr "École :"
-#: registration/templates/registration/user_detail.html:136
+#: registration/templates/registration/user_detail.html:143
msgid "Responsible name:"
msgstr "Nom d'un⋅e responsable légal⋅e :"
-#: registration/templates/registration/user_detail.html:139
+#: registration/templates/registration/user_detail.html:146
msgid "Responsible phone number:"
msgstr "Numéro de téléphone d'un⋅e responsable légal⋅e :"
-#: registration/templates/registration/user_detail.html:142
+#: registration/templates/registration/user_detail.html:149
msgid "Responsible email address:"
msgstr "Adresse e-mail d'un⋅e responsable légal⋅e :"
-#: registration/templates/registration/user_detail.html:147
+#: registration/templates/registration/user_detail.html:154
msgid "Most recent degree:"
msgstr "Dernier diplôme obtenu :"
-#: registration/templates/registration/user_detail.html:150
-#: registration/templates/registration/user_detail.html:154
+#: registration/templates/registration/user_detail.html:157
+#: registration/templates/registration/user_detail.html:161
msgid "Professional activity:"
msgstr "Activité professionnelle :"
-#: registration/templates/registration/user_detail.html:157
+#: registration/templates/registration/user_detail.html:164
msgid "Admin:"
msgstr "Administrateur⋅rice :"
-#: registration/templates/registration/user_detail.html:161
+#: registration/templates/registration/user_detail.html:169
msgid "Grant Animath to contact me in the future about other actions:"
msgstr "Autorise Animath à recontacter à propos d'autres actions :"
-#: registration/templates/registration/user_detail.html:171
+#: registration/templates/registration/user_detail.html:180
msgid "Payment information (final):"
msgstr "Informations de paiement (finale) :"
-#: registration/templates/registration/user_detail.html:173
+#: registration/templates/registration/user_detail.html:182
msgid "Payment information:"
msgstr "Informations de paiement :"
-#: registration/templates/registration/user_detail.html:177
+#: registration/templates/registration/user_detail.html:186
msgid "yes,no,pending"
msgstr "oui,non,en attente"
-#: registration/templates/registration/user_detail.html:181
-#: registration/templates/registration/user_detail.html:184
+#: registration/templates/registration/user_detail.html:190
+#: registration/templates/registration/user_detail.html:193
msgid "valid:"
msgstr "valide :"
-#: registration/templates/registration/user_detail.html:196
+#: registration/templates/registration/user_detail.html:205
msgid "Download scholarship attestation"
msgstr "Télécharger l'attestation de bourse"
-#: registration/templates/registration/user_detail.html:198
+#: registration/templates/registration/user_detail.html:207
msgid "Download bank transfer receipt"
msgstr "Télécharger le justificatif de virement bancaire"
-#: registration/templates/registration/user_detail.html:213
+#: registration/templates/registration/user_detail.html:222
msgid "Impersonate"
msgstr "Impersonifier"
-#: registration/templates/registration/user_detail.html:220
+#: registration/templates/registration/user_detail.html:229
#: registration/views.py:317
msgid "Upload photo authorization"
msgstr "Téléverser l'autorisation de droit à l'image"
-#: registration/templates/registration/user_detail.html:226
+#: registration/templates/registration/user_detail.html:236
#: registration/views.py:351
msgid "Upload health sheet"
msgstr "Téléverser la fiche sanitaire"
-#: registration/templates/registration/user_detail.html:231
+#: registration/templates/registration/user_detail.html:243
#: registration/views.py:372
msgid "Upload vaccine sheet"
msgstr "Téléverser le carnet de vaccination"
-#: registration/templates/registration/user_detail.html:236
+#: registration/templates/registration/user_detail.html:249
#: registration/views.py:392
msgid "Upload parental authorization"
msgstr "Téléverser l'autorisation parentale"
-#: registration/templates/registration/user_detail.html:244
+#: registration/templates/registration/user_detail.html:257
msgid "Upload photo authorization (final)"
msgstr "Téléverser l'autorisation de droit à l'image (finale)"
-#: registration/templates/registration/user_detail.html:250
+#: registration/templates/registration/user_detail.html:263
msgid "Upload parental authorization (final)"
msgstr "Téléverser l'autorisation parentale (finale)"
#: registration/views.py:124
-msgid "New TFJM² organizer account"
-msgstr "Nouveau compte organisateur⋅rice pour le TFJM²"
+msgid "New organizer account"
+msgstr "Nouveau compte organisateur⋅rice"
#: registration/views.py:150
msgid "Email validation"
@@ -3587,11 +4148,11 @@ msgstr "Détails de l'utilisateur⋅rice {user}"
msgid "Update user {user}"
msgstr "Mise à jour de l'utilisateur⋅rice {user}"
-#: registration/views.py:512 registration/views.py:546
+#: registration/views.py:532 registration/views.py:566
msgid "This payment is already valid or pending validation."
msgstr "Le paiement est déjà validé ou en attente de validation."
-#: registration/views.py:538
+#: registration/views.py:558
msgid ""
"Since one payment is already validated, or pending validation, grouping is "
"not possible."
@@ -3599,34 +4160,34 @@ msgstr ""
"Puisque un paiement est déjà validé, ou en attente de validation, le "
"regroupement n'est pas possible."
-#: registration/views.py:613
+#: registration/views.py:633
msgid "The payment is already valid or pending validation."
msgstr "Le paiement est déjà validé ou en attente de validation."
-#: registration/views.py:627
+#: registration/views.py:647
msgid "The payment is not found or is already validated."
msgstr "Le paiement n'est pas trouvé ou déjà validé."
-#: registration/views.py:646
+#: registration/views.py:666
#, python-brace-format
msgid "An error occurred during the payment: {error}"
msgstr "Une erreur est survenue lors du paiement : {error}"
-#: registration/views.py:652
+#: registration/views.py:672
msgid "The payment has been refused."
msgstr "Le paiement a été refusé."
-#: registration/views.py:655
+#: registration/views.py:675
#, python-brace-format
msgid "The return code is unknown: {code}"
msgstr "Le code de retour est inconnu : {code}"
-#: registration/views.py:658
+#: registration/views.py:678
#, python-brace-format
msgid "The return type is unknown: {type}"
msgstr "Le type de retour est inconnu : {type}"
-#: registration/views.py:667
+#: registration/views.py:687
msgid ""
"The payment has been successfully validated! Your registration is now "
"complete."
@@ -3634,7 +4195,7 @@ msgstr ""
"Le paiement a été validé avec succès ! Votre inscription est désormais "
"complète."
-#: registration/views.py:674
+#: registration/views.py:694
msgid ""
"Your payment is done! The validation of your payment may takes a few "
"minutes, and will be automatically done. If it is not the case, please "
@@ -3644,27 +4205,27 @@ msgstr ""
"quelques minutes, et sera faite automatiquement. Si ce n'est pas le cas, "
"merci de nous contacter."
-#: registration/views.py:706
+#: registration/views.py:726
#, python-brace-format
msgid "Photo authorization of {student}.{ext}"
msgstr "Autorisation de droit à l'image de {student}.{ext}"
-#: registration/views.py:730
+#: registration/views.py:750
#, python-brace-format
msgid "Health sheet of {student}.{ext}"
msgstr "Fiche sanitaire de {student}.{ext}"
-#: registration/views.py:754
+#: registration/views.py:774
#, python-brace-format
msgid "Vaccine sheet of {student}.{ext}"
msgstr "Carnet de vaccination de {student}.{ext}"
-#: registration/views.py:779
+#: registration/views.py:799
#, python-brace-format
msgid "Parental authorization of {student}.{ext}"
msgstr "Autorisation parentale de {student}.{ext}"
-#: registration/views.py:803
+#: registration/views.py:823
#, python-brace-format
msgid "Payment receipt of {registrations}.{ext}"
msgstr "Justificatif de paiement de {registrations}.{ext}"
@@ -3713,11 +4274,11 @@ msgstr "Privé, réservé aux utilisateur⋅rices explicitement autorisé⋅es"
msgid "Admin users"
msgstr "Administrateur⋅rices"
-#: tfjm/settings.py:169
+#: tfjm/settings.py:173
msgid "English"
msgstr "Anglais"
-#: tfjm/settings.py:170
+#: tfjm/settings.py:174
msgid "French"
msgstr "Français"
@@ -3774,7 +4335,15 @@ msgstr ""
"administrateur⋅rices avec les détails de l'erreur. Vous pouvez désormais "
"retourner chercher d'autres solutions.."
-#: tfjm/templates/base.html:71
+#: tfjm/templates/base.html:15
+msgid "Registration platform to the TFJM²."
+msgstr "Plateforme d'inscription au TFJM²."
+
+#: tfjm/templates/base.html:17
+msgid "Registration platform to the ETEAM."
+msgstr "Plateforme d'inscription à l'ETEAM."
+
+#: tfjm/templates/base.html:76
msgid "Search results"
msgstr "Résultats de la recherche"
@@ -3786,43 +4355,213 @@ msgstr "Nous contacter"
msgid "About"
msgstr "À propos"
-#: tfjm/templates/navbar.html:17
+#: tfjm/templates/index_eteam.html:11 tfjm/templates/index_tfjm.html:11
+msgid "Welcome onto the registration site of the"
+msgstr " Bienvenue sur le site d'inscription au"
+
+#: tfjm/templates/index_eteam.html:20
+msgid "You want to participate to the ETEAM ?"
+msgstr " Tu souhaites participer à l'ETEAM ?"
+
+#: tfjm/templates/index_eteam.html:22
+msgid "Your team is selected and already complete?"
+msgstr "Ton équipe est sélectionnée et déjà formée ?"
+
+#: tfjm/templates/index_eteam.html:27 tfjm/templates/index_tfjm.html:27
+msgid "Register now!"
+msgstr "Inscris-toi maintenant !"
+
+#: tfjm/templates/index_eteam.html:28 tfjm/templates/index_tfjm.html:28
+msgid "I already have an account"
+msgstr "J'ai déjà un compte"
+
+#: tfjm/templates/index_eteam.html:34 tfjm/templates/index_tfjm.html:34
+msgid "How does it work?"
+msgstr "Comment ça marche ?"
+
+#: tfjm/templates/index_eteam.html:37
+#, python-format
+msgid ""
+"To participate to the ETEAM, you must be selected by your national "
+"organization. If so, you just need to create an account on the Registration page. You will then have "
+"to confirm your email address."
+msgstr ""
+"Pour participer à l'ETEAM, vous devez préalablement avoir été sélectionné⋅es "
+"par votre organisation nationale. Si c'est bien le cas, il vous suffit de "
+"créer un compte sur la page "
+"d'inscription. Vous devrez ensuite confirmer votre adresse mail."
+
+#: tfjm/templates/index_eteam.html:46
+#, python-format
+msgid ""
+"You can access your account via the Login"
+"a> page. Once logged in, you will be able to create a team or join "
+"one already created by one of your comrades via an access code that will "
+"have been transmitted to you. You will then be invited to submit a right to "
+"image authorization, essential for the smooth running of the ETEAM. Once "
+"your team has at least 4 participants (maximum 6) and a supervisor, you can "
+"request to validate your team to be able to work on the problems of your "
+"choice."
+msgstr ""
+"Vous pourrez accéder à votre compte via la page de connexion. Une fois connecté⋅e, "
+"vous pourrez créer une équipe ou en rejoindre une déjà créée par un⋅e de vos "
+"camarades via un code d'accès qui vous aura été transmis. Vous serez ensuite "
+"invité⋅e à soumettre une autorisation de droit à l'image, essentielle pour "
+"le bon déroulement de l'ETEAM. Une fois votre équipe constituée d'au moins 4 "
+"participant⋅es (maximum 6) et d'un⋅e encadrant⋅e, vous pourrez demander à "
+"valider votre équipe pour pouvoir travailler sur les problèmes de votre "
+"choix."
+
+#: tfjm/templates/index_eteam.html:55 tfjm/templates/index_tfjm.html:64
+msgid "I have a question"
+msgstr "J'ai une question"
+
+#: tfjm/templates/index_eteam.html:58
+msgid ""
+"Do not hesitate to consult the documentation of the site, to check if the answer is "
+"not already there. Also refer of course to the ETEAM rules. For any other question, do "
+"not hesitate to contact us by email at the address eteam_moc@proton.me ."
+msgstr ""
+"N'hésitez pas à consulter la documentation du site, pour vérifier si la réponse n'y "
+"est pas déjà. Référez-vous également bien sûr au règlement de l'ETEAM. Pour toute "
+"autre question, n'hésitez pas à nous contacter par mail à l'adresse eteam_moc@proton.me ."
+
+#: tfjm/templates/index_tfjm.html:20
+msgid "You want to participate to the 𝕋𝔽𝕁𝕄² ?"
+msgstr " Tu souhaites participer au 𝕋𝔽𝕁𝕄² ?"
+
+#: tfjm/templates/index_tfjm.html:22
+msgid "Your team is already complete?"
+msgstr "Ton équipe est déjà formée ?"
+
+#: tfjm/templates/index_tfjm.html:37
+#, python-format
+msgid ""
+"To participate to the 𝕋𝔽𝕁𝕄², you just need to create an account on the "
+"Registration page. You will "
+"then have to confirm your email address."
+msgstr ""
+"Pour participer au 𝕋𝔽𝕁𝕄², il vous suffit de créer un compte sur la "
+"page d'inscription. Vous "
+"devrez ensuite confirmer votre adresse mail."
+
+#: tfjm/templates/index_tfjm.html:45
+#, python-format
+msgid ""
+"You can access your account via the Login"
+"a> page. Once logged in, you will be able to create a team or join "
+"one already created by one of your comrades via an access code that will "
+"have been transmitted to you. You will then be invited to submit a right to "
+"image authorization, essential for the smooth running of the 𝕋𝔽𝕁𝕄². Once "
+"your team has at least 4 participants (maximum 6) and a supervisor, you can "
+"request to validate your team to be able to work on the problems of your "
+"choice."
+msgstr ""
+"Vous pourrez accéder à votre compte via la page de connexion. Une fois connecté⋅e, "
+"vous pourrez créer une équipe ou en rejoindre une déjà créée par un⋅e de vos "
+"camarades via un code d'accès qui vous aura été transmis. Vous serez ensuite "
+"invité⋅e à soumettre une autorisation de droit à l'image, essentielle pour "
+"le bon déroulement du 𝕋𝔽𝕁𝕄². Une fois votre équipe constituée d'au moins 4 "
+"participant⋅es (maximum 6) et d'un⋅e encadrant⋅e, vous pourrez demander à "
+"valider votre équipe pour pouvoir travailler sur les problèmes de votre "
+"choix."
+
+#: tfjm/templates/index_tfjm.html:54
+msgid "I can't find a team, help me!"
+msgstr "Je ne trouve pas d'équipe, aidez-moi !"
+
+#: tfjm/templates/index_tfjm.html:58
+msgid ""
+"You can contact us at the address contact@tfjm.org so that we can help you get in touch with other "
+"participants who are also looking for a team."
+msgstr ""
+"Vous pouvez nous contacter à l'adresse contact@tfjm.org pour que nous puissions vous mettre en relation "
+"avec d'autres participant⋅es qui cherchent également une équipe."
+
+#: tfjm/templates/index_tfjm.html:67
+msgid ""
+"Do not hesitate to consult the documentation of the site, to check if the answer is "
+"not already there. Also refer of course to the 𝕋𝔽𝕁𝕄² rules. For any other question, do "
+"not hesitate to contact us by email at the address contact@tf"
+"jm.org ."
+msgstr ""
+"N'hésitez pas à consulter la documentation du site, pour vérifier si la réponse n'y "
+"est pas déjà. Référez-vous également bien sûr au règlement du 𝕋𝔽𝕁𝕄². Pour toute autre "
+"question, n'hésitez pas à nous contacter par mail à l'adresse contact@"
+"tfjm.org ."
+
+#: tfjm/templates/index_tfjm.html:79
+msgid "Save the dates!"
+msgstr "Attention aux dates !"
+
+#: tfjm/templates/index_tfjm.html:80
+msgid ""
+"If you don't end your registration by the indicated deadline, you will "
+"unfortunately not be able to participate in the 𝕋𝔽𝕁𝕄²."
+msgstr ""
+"Si vous ne finalisez pas votre inscription avant la date limite indiquée, "
+"vous ne pourrez malheureusement pas participer au 𝕋𝔽𝕁𝕄²."
+
+#: tfjm/templates/navbar.html:19 tfjm/urls.py:34
msgid "Home"
msgstr "Accueil"
-#: tfjm/templates/navbar.html:21
+#: tfjm/templates/navbar.html:24
+msgid "Tournament"
+msgstr "Tournoi"
+
+#: tfjm/templates/navbar.html:28
msgid "Tournaments"
msgstr "Tournois"
-#: tfjm/templates/navbar.html:26
+#: tfjm/templates/navbar.html:34
msgid "Users"
msgstr "Utilisateur⋅rices"
-#: tfjm/templates/navbar.html:46
+#: tfjm/templates/navbar.html:54
msgid "My team"
msgstr "Mon équipe"
-#: tfjm/templates/navbar.html:51
+#: tfjm/templates/navbar.html:59
msgid "My participation"
msgstr "Ma participation"
-#: tfjm/templates/navbar.html:72
+#: tfjm/templates/navbar.html:80
msgid "Administration"
msgstr "Administration"
-#: tfjm/templates/navbar.html:80
+#: tfjm/templates/navbar.html:88
msgid "Search…"
msgstr "Chercher…"
-#: tfjm/templates/navbar.html:89
+#: tfjm/templates/navbar.html:97
msgid "Return to admin view"
msgstr "Retourner à l'interface administrateur⋅rice"
-#: tfjm/templates/navbar.html:94
+#: tfjm/templates/navbar.html:102
msgid "Register"
msgstr "S'inscrire"
-#: tfjm/templates/navbar.html:110
+#: tfjm/templates/navbar.html:118
msgid "My account"
msgstr "Mon compte"
diff --git a/participation/admin.py b/participation/admin.py
index 50a319f..86ad076 100644
--- a/participation/admin.py
+++ b/participation/admin.py
@@ -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', 'reporter',)
+ 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,25 +113,29 @@ class PoolAdmin(admin.ModelAdmin):
@admin.register(Passage)
class PassageAdmin(admin.ModelAdmin):
- list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reporter_trigram',
- 'pool_abbr', 'position', 'tournament')
+ 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', 'reporter',)
+ 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):
return record.opponent.team.trigram
- @admin.display(description=_("reporter"), ordering='reporter__team__trigram')
- def reporter_trigram(self, record: Passage):
- return record.reporter.team.trigram
+ @admin.display(description=_("reviewer"), ordering='reviewer__team__trigram')
+ def reviewer_trigram(self, record: Passage):
+ return record.reviewer.team.trigram
+
+ @admin.display(description=_("observer"), ordering='observer__team__trigram')
+ def observer_trigram(self, record: Passage):
+ return record.observer.team.trigram
@admin.display(description=_("pool"), ordering='pool__letter')
def pool_abbr(self, record):
@@ -144,12 +148,13 @@ class PassageAdmin(admin.ModelAdmin):
@admin.register(Note)
class NoteAdmin(admin.ModelAdmin):
- list_display = ('passage', 'pool', 'jury', 'defender_writing', 'defender_oral',
- 'opponent_writing', 'opponent_oral', 'reporter_writing', 'reporter_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')
- search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__defender__team__trigram',)
+ '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__reporter__team__trigram',)
autocomplete_fields = ('jury', 'passage',)
@admin.display(description=_("pool"))
@@ -173,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
diff --git a/participation/api/serializers.py b/participation/api/serializers.py
index 5aa2b27..86d25ec 100644
--- a/participation/api/serializers.py
+++ b/participation/api/serializers.py
@@ -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,8 +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',
+ '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',)
diff --git a/participation/api/urls.py b/participation/api/urls.py
index 7034d0c..5dd5003 100644
--- a/participation/api/urls.py
+++ b/participation/api/urls.py
@@ -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)
diff --git a/participation/api/views.py b/participation/api/views.py
index aa6793a..c4906e5 100644
--- a/participation/api/views.py
+++ b/participation/api/views.py
@@ -4,16 +4,16 @@ 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',
- 'opponent_oral', 'reporter_writing', 'reporter_oral', ]
+ filterset_fields = ['jury', 'passage', 'reporter_writing', 'reporter_oral', 'opponent_writing',
+ 'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', ]
class ParticipationViewSet(ModelViewSet):
@@ -27,7 +27,7 @@ class PassageViewSet(ModelViewSet):
queryset = Passage.objects.all()
serializer_class = PassageSerializer
filter_backends = [DjangoFilterBackend]
- filterset_fields = ['pool', 'solution_number', 'defender', 'opponent', 'reporter', '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,8 +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',
+ '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', ]
diff --git a/participation/forms.py b/participation/forms.py
index 4dc32ae..04795d4 100644
--- a/participation/forms.py
+++ b/participation/forms.py
@@ -14,8 +14,9 @@ from django.utils.translation import gettext_lazy as _
import pandas
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):
@@ -74,6 +75,12 @@ class ParticipationForm(forms.ModelForm):
"""
Form to update the problem of a team participation.
"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if settings.TFJM_APP == "ETEAM":
+ # One single tournament only
+ del self.fields['tournament']
+
class Meta:
model = Participation
fields = ('tournament', 'final',)
@@ -104,7 +111,7 @@ class RequestValidationForm(forms.Form):
)
engagement = forms.BooleanField(
- label=_("I engage myself to participate to the whole TFJM²."),
+ label=_("I engage myself to participate to the whole tournament."),
required=True,
)
@@ -125,6 +132,15 @@ class ValidateParticipationForm(forms.Form):
class TournamentForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if settings.NB_ROUNDS < 3:
+ del self.fields['date_third_phase']
+ del self.fields['solutions_available_third_phase']
+ del self.fields['reviews_third_phase_limit']
+ if not settings.PAYMENT_MANAGEMENT:
+ del self.fields['price']
+
class Meta:
model = Tournament
exclude = ('notes_sheet_id', )
@@ -134,12 +150,15 @@ class TournamentForm(forms.ModelForm):
'inscription_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'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'),
- 'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
- format='%Y-%m-%d %H:%M'),
- 'solutions_available_second_phase': forms.DateTimeInput(attrs={'type': 'datetime-local'},
- format='%Y-%m-%d %H:%M'),
- 'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
- format='%Y-%m-%d %H:%M'),
+ 'date_first_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
+ 'reviews_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
+ format='%Y-%m-%d %H:%M'),
+ 'date_second_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
+ 'reviews_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
+ format='%Y-%m-%d %H:%M'),
+ 'date_third_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
+ 'reviews_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
+ format='%Y-%m-%d %H:%M'),
'organizers': forms.SelectMultiple(attrs={
'class': 'selectpicker',
'data-live-search': 'true',
@@ -283,25 +302,26 @@ class UploadNotesForm(forms.Form):
line = [s for s in line if s == s]
# Strip cases
line = [str(s).strip() for s in line if str(s)]
- if line and line[0] == 'Problème':
+ if line and line[0] in ["Problème", "Problem"]:
pool_size = len(line) - 1
- line_length = 2 + 6 * pool_size
+ line_length = 2 + (8 if df.iat[1, 8] == "Observer" else 6) * pool_size
continue
if pool_size == 0 or len(line) < line_length:
continue
name = line[0]
- if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]:
+ if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe",
+ "role", "juree", "average", "coefficient", "subtotal", "team"]:
continue
notes = line[2:line_length]
- print(name, notes)
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
continue
notes = list(map(lambda x: int(float(x)), notes))
- print(notes)
- max_notes = pool_size * [20, 20, 10, 10, 10, 10]
+ max_notes = pool_size * [20 if settings.TFJM_APP == "TFJM" else 10,
+ 20 if settings.TFJM_APP == "TFJM" else 10,
+ 10, 10, 10, 10, 10, 10]
for n, max_n in zip(notes, max_notes):
if n > max_n:
self.add_error('file',
@@ -325,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 "reporter" in cleaned_data \
- and len({cleaned_data["defender"], cleaned_data["opponent"], cleaned_data["reporter"]}) < 3:
- self.add_error(None, _("The defender, the opponent and the reporter 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', 'reporter', '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"]
@@ -355,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',
- 'opponent_oral', 'reporter_writing', 'reporter_oral', )
+ fields = ('reporter_writing', 'reporter_oral', 'opponent_writing',
+ 'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', )
diff --git a/participation/management/commands/export_results.py b/participation/management/commands/export_results.py
index 2cf4d04..8924b69 100644
--- a/participation/management/commands/export_results.py
+++ b/participation/management/commands/export_results.py
@@ -1,6 +1,7 @@
# Copyright (C) 2021 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
+from django.conf import settings
from django.core.management import BaseCommand
from django.utils.formats import date_format
from django.utils.translation import activate
@@ -9,7 +10,7 @@ from participation.models import Tournament
class Command(BaseCommand):
def handle(self, *args, **kwargs):
- activate('fr')
+ activate(settings.PREFERRED_LANGUAGE_CODE)
tournaments = Tournament.objects.order_by('-date_start', 'name')
for tournament in tournaments:
diff --git a/participation/management/commands/export_solutions.py b/participation/management/commands/export_solutions.py
index c4a15d2..a97c315 100644
--- a/participation/management/commands/export_solutions.py
+++ b/participation/management/commands/export_solutions.py
@@ -11,7 +11,7 @@ from participation.models import Solution, Tournament
class Command(BaseCommand):
def handle(self, *args, **kwargs):
- activate('fr')
+ activate(settings.PROBLEMS)
base_dir = Path(__file__).parent.parent.parent.parent
base_dir /= "output"
diff --git a/participation/management/commands/fix_sympa_lists.py b/participation/management/commands/fix_sympa_lists.py
index afa8649..8dd4681 100644
--- a/participation/management/commands/fix_sympa_lists.py
+++ b/participation/management/commands/fix_sympa_lists.py
@@ -1,6 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
-
+from django.conf import settings
from django.core.management import BaseCommand
from django.db.models import Q
from participation.models import Team, Tournament
@@ -13,6 +13,9 @@ class Command(BaseCommand):
"""
Create Sympa mailing lists and register teams.
"""
+ if not settings.ML_MANAGEMENT:
+ return
+
sympa = get_sympa_client()
sympa.create_list("equipes", "Equipes du TFJM2", "hotline",
diff --git a/participation/management/commands/generate_seconds_sheet.py b/participation/management/commands/generate_seconds_sheet.py
index 38959c4..74ab1d0 100644
--- a/participation/management/commands/generate_seconds_sheet.py
+++ b/participation/management/commands/generate_seconds_sheet.py
@@ -12,7 +12,7 @@ from ...models import Passage, Tournament
class Command(BaseCommand):
def handle(self, *args, **options):
- activate('fr')
+ activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
try:
spreadsheet = gc.open("Tableau des deuxièmes", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
@@ -51,25 +51,25 @@ 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)
- opponent_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, opponent=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)
- opponent_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, opponent=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,
- reporter_passage_1.average_reporter_writing, reporter_passage_1.average_reporter_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,
- reporter_passage_2.average_reporter_writing, reporter_passage_2.average_reporter_oral])
+ reviewer_passage_2.average_reviewer_writing, reviewer_passage_2.average_reviewer_oral])
line.extend([score2, f"{score1:.1f} ({team1.team.trigram})",
f"{score3:.1f} ({team3.team.trigram})"])
diff --git a/participation/migrations/0014_alter_team_trigram.py b/participation/migrations/0014_alter_team_trigram.py
new file mode 100644
index 0000000..bf105fb
--- /dev/null
+++ b/participation/migrations/0014_alter_team_trigram.py
@@ -0,0 +1,31 @@
+# Generated by Django 5.0.6 on 2024-06-07 12:46
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("participation", "0013_alter_pool_options_pool_room"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="team",
+ name="trigram",
+ field=models.CharField(
+ help_text="The code must be composed of 3 uppercase letters.",
+ max_length=3,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator("^[A-Z]{3}$"),
+ django.core.validators.RegexValidator(
+ "^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)",
+ message="This team code is forbidden.",
+ ),
+ ],
+ verbose_name="code",
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0015_tournament_solutions_available_third_phase_and_more.py b/participation/migrations/0015_tournament_solutions_available_third_phase_and_more.py
new file mode 100644
index 0000000..17adadb
--- /dev/null
+++ b/participation/migrations/0015_tournament_solutions_available_third_phase_and_more.py
@@ -0,0 +1,42 @@
+# Generated by Django 5.0.6 on 2024-06-07 13:51
+
+import django.utils.timezone
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("participation", "0014_alter_team_trigram"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="tournament",
+ name="solutions_available_second_phase",
+ ),
+ migrations.AddField(
+ model_name="tournament",
+ name="solutions_available_second_phase",
+ field=models.BooleanField(
+ default=False,
+ verbose_name="check this case when solutions for the second round become available",
+ ),
+ ),
+ migrations.AddField(
+ model_name="tournament",
+ name="solutions_available_third_phase",
+ field=models.BooleanField(
+ default=False,
+ verbose_name="check this case when solutions for the third round become available",
+ ),
+ ),
+ migrations.AddField(
+ model_name="tournament",
+ name="syntheses_third_phase_limit",
+ field=models.DateTimeField(
+ default=django.utils.timezone.now,
+ verbose_name="limit date to upload the syntheses for the third phase",
+ ),
+ )
+ ]
diff --git a/participation/migrations/0016_tournament_date_first_phase_and_more.py b/participation/migrations/0016_tournament_date_first_phase_and_more.py
new file mode 100644
index 0000000..7d1aa33
--- /dev/null
+++ b/participation/migrations/0016_tournament_date_first_phase_and_more.py
@@ -0,0 +1,35 @@
+# Generated by Django 5.0.6 on 2024-06-07 14:01
+
+import datetime
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("participation", "0015_tournament_solutions_available_third_phase_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="tournament",
+ name="date_first_phase",
+ field=models.DateField(
+ default=datetime.date.today, verbose_name="first phase date"
+ ),
+ ),
+ migrations.AddField(
+ model_name="tournament",
+ name="date_second_phase",
+ field=models.DateField(
+ default=datetime.date.today, verbose_name="first second date"
+ ),
+ ),
+ migrations.AddField(
+ model_name="tournament",
+ name="date_third_phase",
+ field=models.DateField(
+ default=datetime.date.today, verbose_name="third phase date"
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0017_alter_passage_solution_number_alter_pool_round_and_more.py b/participation/migrations/0017_alter_passage_solution_number_alter_pool_round_and_more.py
new file mode 100644
index 0000000..124c9f6
--- /dev/null
+++ b/participation/migrations/0017_alter_passage_solution_number_alter_pool_round_and_more.py
@@ -0,0 +1,77 @@
+# Generated by Django 5.0.6 on 2024-06-13 08:53
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("participation", "0016_tournament_date_first_phase_and_more"),
+ ]
+
+ operations = [
+ 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="defended solution",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="pool",
+ name="round",
+ field=models.PositiveSmallIntegerField(
+ choices=[(1, "Round 1"), (2, "Round 2"), (3, "Round 3")],
+ verbose_name="round",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="solution",
+ name="problem",
+ 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="problem",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="team",
+ name="trigram",
+ field=models.CharField(
+ help_text="The code must be composed of 4 uppercase letters.",
+ max_length=4,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator("^[A-Z]{3}[A-Z]*$"),
+ django.core.validators.RegexValidator(
+ "^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)",
+ message="This team code is forbidden.",
+ ),
+ ],
+ verbose_name="code",
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0018_rename_reporter_to_reviewer.py b/participation/migrations/0018_rename_reporter_to_reviewer.py
new file mode 100644
index 0000000..0a4c67b
--- /dev/null
+++ b/participation/migrations/0018_rename_reporter_to_reviewer.py
@@ -0,0 +1,91 @@
+# Generated by Django 5.0.6 on 2024-07-05 08:53
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ (
+ "participation",
+ "0017_alter_passage_solution_number_alter_pool_round_and_more",
+ ),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name="note",
+ old_name="reporter_oral",
+ new_name="reviewer_oral",
+ ),
+ migrations.RenameField(
+ model_name="note",
+ old_name="reporter_writing",
+ new_name="reviewer_writing",
+ ),
+ migrations.RenameField(
+ model_name="passage",
+ old_name="reporter",
+ new_name="reviewer",
+ ),
+ migrations.AlterField(
+ model_name="note",
+ name="reviewer_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),
+ ],
+ default=0,
+ verbose_name="reviewer oral note",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="note",
+ name="reviewer_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),
+ ],
+ default=0,
+ verbose_name="reviewer writing note",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="passage",
+ name="reviewer",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="+",
+ to="participation.participation",
+ verbose_name="reviewer",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="synthesis",
+ name="type",
+ field=models.PositiveSmallIntegerField(
+ choices=[(1, "opponent"), (2, "reviewer")]
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0019_note_observer_oral_note_observer_writing_and_more.py b/participation/migrations/0019_note_observer_oral_note_observer_writing_and_more.py
new file mode 100644
index 0000000..20c2c98
--- /dev/null
+++ b/participation/migrations/0019_note_observer_oral_note_observer_writing_and_more.py
@@ -0,0 +1,86 @@
+# Generated by Django 5.0.6 on 2024-07-05 09:47
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("participation", "0018_rename_reporter_to_reviewer"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="note",
+ name="observer_oral",
+ field=models.PositiveSmallIntegerField(
+ choices=[
+ (-10, -10),
+ (-9, -9),
+ (-8, -8),
+ (-7, -7),
+ (-6, -6),
+ (-5, -5),
+ (-4, -4),
+ (-3, -3),
+ (-2, -2),
+ (-1, -1),
+ (0, 0),
+ (1, 1),
+ (2, 2),
+ (3, 3),
+ (4, 4),
+ (5, 5),
+ (6, 6),
+ (7, 7),
+ (8, 8),
+ (9, 9),
+ (10, 10),
+ ],
+ default=0,
+ verbose_name="observer oral note",
+ ),
+ ),
+ migrations.AddField(
+ model_name="note",
+ name="observer_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),
+ ],
+ default=0,
+ verbose_name="observer writing note",
+ ),
+ ),
+ migrations.AddField(
+ model_name="passage",
+ name="observer",
+ field=models.ForeignKey(
+ blank=True,
+ default=None,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="participation.participation",
+ verbose_name="observer",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="synthesis",
+ name="type",
+ field=models.PositiveSmallIntegerField(
+ choices=[(1, "opponent"), (2, "reviewer"), (3, "observer")]
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0020_rename_synthesis_writtenreview_and_more.py b/participation/migrations/0020_rename_synthesis_writtenreview_and_more.py
new file mode 100644
index 0000000..e292683
--- /dev/null
+++ b/participation/migrations/0020_rename_synthesis_writtenreview_and_more.py
@@ -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",
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0021_rename_defender_oral_note_reporter_oral_and_more.py b/participation/migrations/0021_rename_defender_oral_note_reporter_oral_and_more.py
new file mode 100644
index 0000000..328653d
--- /dev/null
+++ b/participation/migrations/0021_rename_defender_oral_note_reporter_oral_and_more.py
@@ -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",
+ ),
+ ),
+ ]
diff --git a/participation/migrations/0022_alter_note_observer_oral.py b/participation/migrations/0022_alter_note_observer_oral.py
new file mode 100644
index 0000000..6dc47a3
--- /dev/null
+++ b/participation/migrations/0022_alter_note_observer_oral.py
@@ -0,0 +1,44 @@
+# Generated by Django 5.0.6 on 2024-07-11 08:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("participation", "0021_rename_defender_oral_note_reporter_oral_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="note",
+ name="observer_oral",
+ field=models.SmallIntegerField(
+ choices=[
+ (-10, -10),
+ (-9, -9),
+ (-8, -8),
+ (-7, -7),
+ (-6, -6),
+ (-5, -5),
+ (-4, -4),
+ (-3, -3),
+ (-2, -2),
+ (-1, -1),
+ (0, 0),
+ (1, 1),
+ (2, 2),
+ (3, 3),
+ (4, 4),
+ (5, 5),
+ (6, 6),
+ (7, 7),
+ (8, 8),
+ (9, 9),
+ (10, 10),
+ ],
+ default=0,
+ verbose_name="observer oral note",
+ ),
+ ),
+ ]
diff --git a/participation/models.py b/participation/models.py
index d2e4b8e..8ee43b7 100644
--- a/participation/models.py
+++ b/participation/models.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date, timedelta
+import math
import os
from django.conf import settings
@@ -27,7 +28,7 @@ def get_motivation_letter_filename(instance, filename):
class Team(models.Model):
"""
- The Team model represents a real team that participates to the TFJM².
+ The Team model represents a real team that participates to the tournament.
This only includes the registration detail.
"""
name = models.CharField(
@@ -37,14 +38,15 @@ class Team(models.Model):
)
trigram = models.CharField(
- max_length=3,
- verbose_name=_("trigram"),
- help_text=_("The trigram must be composed of three uppercase letters."),
+ max_length=4,
+ verbose_name=_("code"),
+ help_text=format_lazy(_("The code must be composed of {nb_letters} uppercase letters."),
+ nb_letters=settings.TEAM_CODE_LENGTH),
unique=True,
validators=[
- RegexValidator(r"^[A-Z]{3}$"),
+ RegexValidator("^[A-Z]{3}[A-Z]*$"),
RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})",
- message=_("This trigram is forbidden.")),
+ message=_("This team code is forbidden.")),
],
)
@@ -80,12 +82,12 @@ class Team(models.Model):
return False
if any(not r.photo_authorization for r in self.participants.all()):
return False
- if not self.motivation_letter:
+ if settings.MOTIVATION_LETTER_REQUIRED and not self.motivation_letter:
return False
if not self.participation.tournament.remote:
- if any(r.under_18 and not r.health_sheet for r in self.students.all()):
+ if settings.HEALTH_SHEET_REQUIRED and any(r.under_18 and not r.health_sheet for r in self.students.all()):
return False
- if any(r.under_18 and not r.vaccine_sheet for r in self.students.all()):
+ if settings.VACCINE_SHEET_REQUIRED and any(r.under_18 and not r.vaccine_sheet for r in self.students.all()):
return False
if any(r.under_18 and not r.parental_authorization for r in self.students.all()):
return False
@@ -118,7 +120,7 @@ class Team(models.Model):
'content': content,
})
- if not self.motivation_letter:
+ if settings.MOTIVATION_LETTER_REQUIRED and not self.motivation_letter:
text = _("The team {trigram} has not uploaded a motivation letter. "
"You can upload your motivation letter using this link.")
url = reverse_lazy("participation:upload_team_motivation_letter", args=(self.pk,))
@@ -234,11 +236,18 @@ class Team(models.Model):
get_sympa_client().unsubscribe(self.email, "equipes-non-valides", False)
get_sympa_client().delete_list(f"equipe-{self.trigram}")
+ def clean(self):
+ if self.trigram and len(self.trigram) != settings.TEAM_CODE_LENGTH:
+ raise ValidationError({'trigram': _("The team code must be composed of {nb_letters} uppercase letters.")},
+ params={'nb_letters': settings.TEAM_CODE_LENGTH})
+ return super().clean()
+
def save(self, *args, **kwargs):
if not self.access_code:
# if the team got created, generate the access code, create the contact mailing list
self.access_code = get_random_string(6)
- self.create_mailing_list()
+ if settings.ML_MANAGEMENT:
+ self.create_mailing_list()
return super().save(*args, **kwargs)
@@ -309,18 +318,43 @@ class Tournament(models.Model):
default=timezone.now,
)
- syntheses_first_phase_limit = models.DateTimeField(
- verbose_name=_("limit date to upload the syntheses for the first phase"),
+ date_first_phase = models.DateField(
+ verbose_name=_("first phase date"),
+ default=date.today,
+ )
+
+ reviews_first_phase_limit = models.DateTimeField(
+ verbose_name=_("limit date to upload the written reviews for the first phase"),
default=timezone.now,
)
- solutions_available_second_phase = models.DateTimeField(
- verbose_name=_("date when the solutions for the second round become available"),
+ date_second_phase = models.DateField(
+ verbose_name=_("first second date"),
+ default=date.today,
+ )
+
+ solutions_available_second_phase = models.BooleanField(
+ verbose_name=_("check this case when solutions for the second round become available"),
+ default=False,
+ )
+
+ reviews_second_phase_limit = models.DateTimeField(
+ verbose_name=_("limit date to upload the written reviews for the second phase"),
default=timezone.now,
)
- syntheses_second_phase_limit = models.DateTimeField(
- verbose_name=_("limit date to upload the syntheses for the second phase"),
+ date_third_phase = models.DateField(
+ verbose_name=_("third phase date"),
+ default=date.today,
+ )
+
+ solutions_available_third_phase = models.BooleanField(
+ verbose_name=_("check this case when solutions for the third round become available"),
+ default=False,
+ )
+
+ reviews_third_phase_limit = models.DateTimeField(
+ verbose_name=_("limit date to upload the written reviews for the third phase"),
default=timezone.now,
)
@@ -408,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):
@@ -424,34 +458,39 @@ class Tournament(models.Model):
return self.notes_sheet_id
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
- spreadsheet = gc.create(f"Feuille de notes - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
+ spreadsheet = gc.create(_('Notation sheet') + f" - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
spreadsheet.update_locale("fr_FR")
spreadsheet.share(None, "anyone", "writer", with_link=True)
self.notes_sheet_id = spreadsheet.id
self.save()
def update_ranking_spreadsheet(self): # noqa: C901
- translation.activate('fr')
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.notes_sheet_id)
worksheets = spreadsheet.worksheets()
- if "Classement final" not in [ws.title for ws in worksheets]:
- worksheet = spreadsheet.add_worksheet("Classement final", 100, 26)
+ if str(_("Final ranking")) not in [ws.title for ws in worksheets]:
+ worksheet = spreadsheet.add_worksheet(str(_("Final ranking")), 30, 10)
else:
- worksheet = spreadsheet.worksheet("Classement final")
+ worksheet = spreadsheet.worksheet(str(_("Final ranking")))
if worksheet.index != self.pools.count():
worksheet.update_index(self.pools.count())
- header = [["Équipe", "Score jour 1", "Harmonisation 1", "Score jour 2", "Harmonisation 2", "Total", "Rang"]]
+ header = [[str(_("Team")), str(_("Scores day 1")), str(_("Tweaks day 1")),
+ str(_("Scores day 2")), str(_("Tweaks day 2"))]
+ + ([str(_("Total D1 + D2")), str(_("Scores day 3")), str(_("Tweaks day 3"))]
+ if settings.NB_ROUNDS >= 3 else [])
+ + [str(_("Total")), str(_("Rank"))]]
lines = []
participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all()
+ total_col, rank_col = ("F", "G") if settings.NB_ROUNDS == 2 else ("I", "J")
for i, participation in enumerate(participations):
line = [f"{participation.team.name} ({participation.team.trigram})"]
lines.append(line)
- 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
@@ -460,11 +499,11 @@ class Tournament(models.Model):
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
tweak1 = tweak1_qs.get() if tweak1_qs.exists() else None
- line.append(f"=SIERREUR('Poule {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
+ line.append(f"=SIERREUR('{_('Pool')} {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
line.append(tweak1.diff if tweak1 else 0)
- if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists():
- 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
@@ -474,23 +513,50 @@ class Tournament(models.Model):
tweak2 = tweak2_qs.get() if tweak2_qs.exists() else None
line.append(
- f"=SIERREUR('Poule {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)")
+ f"=SIERREUR('{_('Pool')} {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)")
line.append(tweak2.diff if tweak2 else 0)
+
+ if settings.NB_ROUNDS >= 3:
+ line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
+
+ if Passage.objects.filter(pool__tournament=self, pool__round=3, 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
+ else:
+ position3 = (passage3.position - 1) * 2 + pool3.room
+ tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation)
+ tweak3 = tweak3_qs.get() if tweak3_qs.exists() else None
+
+ line.append(
+ f"=SIERREUR('{_('Pool')} {pool3.short_name}'!$D{pool3.juries.count() + 10 + position3}; 0)")
+ line.append(tweak3.diff if tweak3 else 0)
+ else:
+ line.append(0)
+ line.append(0)
else:
- # User has no second pool yet
+ # There is no second pool yet
line.append(0)
line.append(0)
- line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
- line.append(f"=RANG($F{i + 2}; $F$2:$F${participations.count() + 1})")
+ if settings.NB_ROUNDS >= 3:
+ line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
+ line.append(0)
+ line.append(0)
- final_ranking = [["", "", "", ""], ["", "", "", ""], ["Équipe", "Score", "Rang", "Mention"],
+ line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}"
+ + (f" + (PI() - 2) * $G{i + 2} + $H{i + 2}" if settings.NB_ROUNDS >= 3 else ""))
+ line.append(f"=RANG(${total_col}{i + 2}; ${total_col}$2:${total_col}${participations.count() + 1})")
+
+ final_ranking = [["", "", "", ""], ["", "", "", ""],
+ [str(_("Team")), str(_("Score")), str(_("Rank")), str(_("Mention"))],
[f"=SORT($A$2:$A${participations.count() + 1}; "
- f"$F$2:$F${participations.count() + 1}; FALSE)",
- f"=SORT($F$2:$F${participations.count() + 1}; "
- f"$F$2:$F${participations.count() + 1}; FALSE)",
- f"=SORT($G$2:$G${participations.count() + 1}; "
- f"$F$2:$F${participations.count() + 1}; FALSE)", ]]
+ f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
+ f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; "
+ f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
+ f"=SORT(${rank_col}$2:${rank_col}${participations.count() + 1}; "
+ f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", ]]
final_ranking += [["", "", ""] for _i in range(participations.count() - 1)]
notes = dict()
@@ -504,12 +570,13 @@ class Tournament(models.Model):
final_ranking[i + 3].append(participation.mention if not self.final else participation.mention_final)
data = header + lines + final_ranking
- worksheet.update(data, f"A1:G{2 * participations.count() + 4}", raw=False)
+ worksheet.update(data, f"A1:{rank_col}{2 * participations.count() + 4}", raw=False)
format_requests = []
# Set the width of the columns
- column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150)]
+ column_widths = [("A", 350), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150),
+ ("H", 150), ("I", 150), ("J", 150)]
for column, width in column_widths:
grid_range = a1_range_to_grid_range(column, worksheet.id)
format_requests.append({
@@ -529,7 +596,7 @@ class Tournament(models.Model):
# Set borders
border_ranges = [("A1:Z", "0000"),
- (f"A1:G{participations.count() + 1}", "1111"),
+ (f"A1:{rank_col}{participations.count() + 1}", "1111"),
(f"A{participations.count() + 4}:D{2 * participations.count() + 4}", "1111")]
sides_names = ['top', 'bottom', 'left', 'right']
styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"]
@@ -551,7 +618,7 @@ class Tournament(models.Model):
})
# Make titles bold
- bold_ranges = [("A1:Z", False), ("A1:G1", True),
+ bold_ranges = [("A1:Z", False), (f"A1:{rank_col}1", True),
(f"A{participations.count() + 4}:D{participations.count() + 4}", True)]
for bold_range, bold in bold_ranges:
format_requests.append({
@@ -564,14 +631,18 @@ class Tournament(models.Model):
# Set background color for headers and footers
bg_colors = [("A1:Z", (1, 1, 1)),
- ("A1:G1", (0.8, 0.8, 0.8)),
+ (f"A1:{rank_col}1", (0.8, 0.8, 0.8)),
(f"A2:B{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"C2:C{participations.count() + 1}", (1, 1, 1)),
(f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"E2:E{participations.count() + 1}", (1, 1, 1)),
- (f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"A{participations.count() + 4}:D{participations.count() + 4}", (0.8, 0.8, 0.8)),
(f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),]
+ if settings.NB_ROUNDS >= 3:
+ bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
+ bg_colors.append((f"I2:J{participations.count() + 1}", (0.9, 0.9, 0.9)))
+ else:
+ bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
for bg_range, bg_color in bg_colors:
r, g, b = bg_color
format_requests.append({
@@ -588,9 +659,15 @@ class Tournament(models.Model):
(f"D2:D{participations.count() + 1}", "0.0"),
(f"E2:E{participations.count() + 1}", "0"),
(f"F2:F{participations.count() + 1}", "0.0"),
- (f"G2:G{participations.count() + 1}", "0"),
(f"B{participations.count() + 5}:B{2 * participations.count() + 5}", "0.0"),
(f"C{participations.count() + 5}:C{2 * participations.count() + 5}", "0"), ]
+ if settings.NB_ROUNDS >= 3:
+ number_format_ranges += [(f"G2:G{participations.count() + 1}", "0.0"),
+ (f"H2:H{participations.count() + 1}", "0"),
+ (f"I2:I{participations.count() + 1}", "0.0"),
+ (f"J2:J{participations.count() + 1}", "0"), ]
+ else:
+ number_format_ranges.append((f"G2:G{participations.count() + 1}", "0"))
for number_format_range, pattern in number_format_ranges:
format_requests.append({
"repeatCell": {
@@ -609,16 +686,16 @@ class Tournament(models.Model):
})
# Protect the header, the juries list, the footer and the ranking
- protected_ranges = ["A1:G1", f"A2:B{participations.count() + 1}",
+ protected_ranges = ["A1:J1", f"A2:B{participations.count() + 1}",
f"D2:D{participations.count() + 1}", f"F2:G{participations.count() + 1}",
+ f"I2:J{participations.count() + 1}",
f"A{participations.count() + 4}:C{2 * participations.count() + 4}", ]
for protected_range in protected_ranges:
format_requests.append({
"addProtectedRange": {
"protectedRange": {
"range": a1_range_to_grid_range(protected_range, worksheet.id),
- "description": "Structure du tableur à ne pas modifier "
- "pour une meilleure prise en charge automatisée",
+ "description": str(_("Don't update the table structure for a better automated integration.")),
"warningOnly": True,
},
}
@@ -632,19 +709,21 @@ class Tournament(models.Model):
# Draw has not been done yet
return
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
+
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.notes_sheet_id)
- worksheet = spreadsheet.worksheet("Classement final")
+ worksheet = spreadsheet.worksheet(str(_("Final ranking")))
- score_cell = worksheet.find("Score")
+ score_cell = worksheet.find(str(_("Score")))
max_row = score_cell.row - 3
if max_row == 1:
# There is no team
return
- data = worksheet.get_values(f"A2:E{max_row}")
+ data = worksheet.get_values(f"A2:H{max_row}")
for line in data:
- trigram = line[0][-4:-1]
+ trigram = line[0][-settings.TEAM_CODE_LENGTH - 1:-1]
participation = self.participations.get(team__trigram=trigram)
pool1 = self.pools.get(round=1, participations=participation, room=1)
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
@@ -667,6 +746,17 @@ class Tournament(models.Model):
create_defaults={'diff': tweak2_nb, 'pool': pool2,
'participation': participation})
+ if self.pools.filter(round=3, participations=participation).exists():
+ pool3 = self.pools.get(round=3, participations=participation, room=1)
+ tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation)
+ tweak3_nb = int(line[7])
+ if not tweak3_nb:
+ tweak3_qs.delete()
+ else:
+ tweak3_qs.update_or_create(defaults={'diff': tweak3_nb},
+ create_defaults={'diff': tweak3_nb, 'pool': pool3,
+ 'participation': participation})
+
nb_participations = self.participations.filter(valid=True).count()
mentions = worksheet.get_values(f"A{score_cell.row + 1}:D{score_cell.row + nb_participations}")
notes = dict()
@@ -821,86 +911,192 @@ 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)
- opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, opponent=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 = _("
The solutions draw is ended. You can check the result on "
+ reporter_text = _("
The solutions draw is ended. You can check the result on "
"this page.
"
+
+ content = reporter_content + opponent_content + reviewer_content + observer_content \
+ + reviews_templates_content
informations.append({
'title': _("Second round"),
'type': "info",
@@ -940,7 +1136,7 @@ class Pool(models.Model):
choices=[
(1, format_lazy(_("Round {round}"), round=1)),
(2, format_lazy(_("Round {round}"), round=2)),
- ]
+ ] + ([] if settings.NB_ROUNDS == 2 else [(3, format_lazy(_("Round {round}"), round=3))]),
)
letter = models.PositiveSmallIntegerField(
@@ -1008,14 +1204,18 @@ 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):
+ return 1 if self.round <= 2 else math.pi - 2
def average(self, participation):
- return sum(passage.average(participation) for passage in self.passages.all()) \
+ return self.coeff * sum(passage.average(participation) for passage in self.passages.all()) \
+ sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all())
async def aaverage(self, participation):
- return sum([passage.average(participation) async for passage in self.passages.all()]) \
+ return self.coeff * sum([passage.average(participation) async for passage in self.passages.all()]) \
+ sum([tweak.diff async for tweak in participation.tweaks.filter(pool=self).all()])
def get_absolute_url(self):
@@ -1027,7 +1227,12 @@ class Pool(models.Model):
return super().validate_constraints()
def update_spreadsheet(self): # noqa: C901
- translation.activate('fr')
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
+
+ pool_size = self.participations.count()
+ has_observer = settings.HAS_OBSERVER and pool_size >= 4
+ passage_width = 6 + (2 if has_observer else 0)
+ passages = self.passages.all()
# Create tournament sheet if it does not exist
self.tournament.create_spreadsheet()
@@ -1035,26 +1240,27 @@ class Pool(models.Model):
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
worksheets = spreadsheet.worksheets()
- if f"Poule {self.short_name}" not in [ws.title for ws in worksheets]:
- worksheet = spreadsheet.add_worksheet(f"Poule {self.short_name}", 100, 26)
+ if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]:
+ worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}",
+ 30, 2 + passages.count() * passage_width)
else:
- worksheet = spreadsheet.worksheet(f"Poule {self.short_name}")
+ worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
if any(ws.title == "Sheet1" for ws in worksheets):
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
- pool_size = self.participations.count()
- passage_width = 6
- passages = self.passages.all()
-
header = [
- sum(([f"Problème {passage.solution_number}"] + (passage_width - 1) * [""]
- for passage in passages), start=["Problème", ""]),
- sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "",
- f"Opposant⋅e ({passage.opponent.team.trigram})", "",
- f"Rapporteur⋅rice ({passage.reporter.team.trigram})", ""]
- for passage in passages), start=["Rôle", ""]),
- sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"]
- for _passage in passages), start=["Juré⋅e", ""]),
+ sum(([str(_("Problem #{problem}").format(problem=passage.solution_number))] + (passage_width - 1) * [""]
+ for passage in passages), start=[str(_("Problem")), ""]),
+ sum(([_('Reporter') + f" ({passage.reporter.team.trigram})", "",
+ _('Opponent') + f" ({passage.opponent.team.trigram})", "",
+ _('Reviewer') + f" ({passage.reviewer.team.trigram})", ""]
+ + ([_('Observer') + f" ({passage.observer.team.trigram})", ""] if has_observer else [])
+ for passage in passages), start=[str(_("Role")), ""]),
+ sum(([_('Writing') + f" (/{20 if settings.TFJM_APP == 'TFJM' else 10})",
+ _('Oral') + f" (/{20 if settings.TFJM_APP == 'TFJM' else 10})",
+ _('Writing') + " (/10)", _('Oral') + " (/10)", _('Writing') + " (/10)", _('Oral') + " (/10)"]
+ + ([_('Writing') + " (/10)", _('Oral') + " (/10)"] if has_observer else [])
+ for _passage in passages), start=[str(_("Juree")), ""]),
]
notes = [[]] # Begin with empty hidden line to ensure pretty design
@@ -1062,8 +1268,10 @@ 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,
- note.reporter_writing, note.reporter_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])
notes.append(line)
notes.append([]) # Add empty line to ensure pretty design
@@ -1075,11 +1283,15 @@ class Pool(models.Model):
return ''
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
- average = ["Moyenne", ""]
- coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] for passage in passages),
- start=["Coefficient", ""])
- subtotal = ["Sous-total", ""]
- footer = [average, coeffs, subtotal, 26 * [""]]
+ average = [str(_("Average")), ""]
+ 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 [])
+ for passage in passages),
+ start=[str(_("Coefficient")), ""])
+ subtotal = [str(_("Subtotal")), ""]
+ footer = [average, coeffs, subtotal, (2 + pool_size * passage_width) * [""]]
min_row = 5
max_row = min_row + self.juries.count()
@@ -1105,47 +1317,62 @@ class Pool(models.Model):
subtotal.extend([f"={rep_w_col}{max_row + 1} * {rep_w_col}{max_row + 2}"
f" + {rep_o_col}{max_row + 1} * {rep_o_col}{max_row + 2}", ""])
+ if has_observer:
+ obs_w_col = getcol(min_column + passage_width * i + 6)
+ obs_o_col = getcol(min_column + passage_width * i + 7)
+ subtotal.extend([f"={obs_w_col}{max_row + 1} * {obs_w_col}{max_row + 2}"
+ f" + {obs_o_col}{max_row + 1} * {obs_o_col}{max_row + 2}", ""])
+
ranking = [
- ["Équipe", "", "Problème", "Total", "Rang"],
+ [str(_("Team")), "", str(_("Problem")), str(_("Total")), str(_("Rank"))],
]
all_passages = Passage.objects.filter(pool__tournament=self.tournament,
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)
opponent_row = 5 + opponent_passage.pool.juries.count()
opponent_col = opponent_passage.position - 1
- reporter_passage = Passage.objects.get(reporter=participation,
+ reviewer_passage = Passage.objects.get(reviewer=participation,
pool__tournament=self.tournament, pool__round=self.round)
- reporter_row = 5 + reporter_passage.pool.juries.count()
- reporter_col = reporter_passage.position - 1
+ reviewer_row = 5 + reviewer_passage.pool.juries.count()
+ reviewer_col = reviewer_passage.position - 1
formula = "="
- formula += (f"'Poule {defender_passage.pool.short_name}'"
- f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender
- formula += (f" + 'Poule {opponent_passage.pool.short_name}'"
+ formula += (f"'{_('Pool')} {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" + 'Poule {reporter_passage.pool.short_name}'"
- f"!{getcol(min_column + reporter_col * passage_width + 4)}{reporter_row + 3}") # Reporter
+ formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'"
+ f"!{getcol(min_column + reviewer_col * passage_width + 4)}{reviewer_row + 3}") # reviewer
+ if has_observer:
+ observer_passage = Passage.objects.get(observer=participation,
+ pool__tournament=self.tournament, pool__round=self.round)
+ observer_row = 5 + observer_passage.pool.juries.count()
+ observer_col = observer_passage.position - 1
+ formula += (f" + '{_('Pool')} {observer_passage.pool.short_name}'"
+ f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}")
+
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
- f"='Poule {defender_passage.pool.short_name}'"
- f"!${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})"])
all_values = header + notes + footer + ranking
- worksheet.batch_clear([f"A1:Z{max_row + 5 + pool_size}"])
- worksheet.update("A1:Z", all_values, raw=False)
+ max_col = getcol(2 + pool_size * passage_width)
+ worksheet.batch_clear([f"A1:{max_col}{max_row + 5 + pool_size}"])
+ worksheet.update(all_values, f"A1:{max_col}", raw=False)
format_requests = []
@@ -1164,6 +1391,11 @@ class Pool(models.Model):
f":{getcol(6 + i * passage_width)}{max_row + 3}")
merge_cells.append(f"{getcol(7 + i * passage_width)}{max_row + 3}"
f":{getcol(8 + i * passage_width)}{max_row + 3}")
+
+ if has_observer:
+ merge_cells.append(f"{getcol(9 + i * passage_width)}2:{getcol(10 + i * passage_width)}2")
+ merge_cells.append(f"{getcol(9 + i * passage_width)}{max_row + 3}"
+ f":{getcol(10 + i * passage_width)}{max_row + 3}")
merge_cells.append(f"A{max_row + 1}:B{max_row + 1}")
merge_cells.append(f"A{max_row + 2}:B{max_row + 2}")
merge_cells.append(f"A{max_row + 3}:B{max_row + 3}")
@@ -1171,13 +1403,13 @@ class Pool(models.Model):
for i in range(pool_size + 1):
merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}")
- format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:Z", worksheet.id)}})
+ format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range(f"A1:{max_col}", worksheet.id)}})
for name in merge_cells:
grid_range = a1_range_to_grid_range(name, worksheet.id)
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
# Make titles bold
- bold_ranges = [("A1:Z", False), ("A1:Z3", True),
+ bold_ranges = [(f"A1:{max_col}", False), (f"A1:{max_col}3", True),
(f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)]
for bold_range, bold in bold_ranges:
format_requests.append({
@@ -1189,7 +1421,7 @@ class Pool(models.Model):
})
# Set background color for headers and footers
- bg_colors = [("A1:Z", (1, 1, 1)),
+ bg_colors = [(f"A1:{max_col}", (1, 1, 1)),
(f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)),
(f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)),
(f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)),
@@ -1198,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({
@@ -1224,10 +1456,10 @@ class Pool(models.Model):
})
# Set the width of the columns
- column_widths = [("A", 300), ("B", 30)]
+ column_widths = [("A", 350), ("B", 30)]
for passage in passages:
column_widths.append((f"{getcol(3 + passage_width * (passage.position - 1))}"
- f":{getcol(8 + passage_width * (passage.position - 1))}", 75))
+ f":{getcol(2 + passage_width * passage.position)}", 80))
for column, width in column_widths:
grid_range = a1_range_to_grid_range(column, worksheet.id)
format_requests.append({
@@ -1278,7 +1510,7 @@ class Pool(models.Model):
})
# Define borders
- border_ranges = [("A1:Z", "0000"),
+ border_ranges = [(f"A1:{max_col}", "0000"),
(f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"),
(f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"),
(f"A1:B{max_row + 3}", "1113"),
@@ -1313,8 +1545,8 @@ class Pool(models.Model):
for i in range(passages.count()):
for j in range(passage_width):
column = getcol(min_column + i * passage_width + j)
- min_note = 0
- max_note = 20 if j < 2 else 10
+ min_note = 0 if j < 7 else -10
+ max_note = 20 if j < 2 and settings.TFJM_APP == "TFJM" else 10
format_requests.append({
"setDataValidation": {
"range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id),
@@ -1324,8 +1556,8 @@ class Pool(models.Model):
"values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); '
f'{column}4>={min_note}; {column}4<={max_note})'},],
},
- "inputMessage": f"La saisie doit être un entier valide "
- f"compris entre {min_note} et {max_note}.",
+ "inputMessage": str(_("Input must be a valid integer between {min_note} and {max_note}.")
+ .format(min_note=min_note, max_note=max_note)),
"strict": True,
},
}
@@ -1353,16 +1585,15 @@ class Pool(models.Model):
})
# Protect the header, the juries list, the footer and the ranking
- protected_ranges = ["A1:Z4",
+ protected_ranges = [f"A1:{max_col}4",
f"A{min_row}:B{max_row}",
- f"A{max_row}:Z{max_row + 5 + pool_size}"]
+ f"A{max_row}:{max_col}{max_row + 5 + pool_size}"]
for protected_range in protected_ranges:
format_requests.append({
"addProtectedRange": {
"protectedRange": {
"range": a1_range_to_grid_range(protected_range, worksheet.id),
- "description": "Structure du tableur à ne pas modifier "
- "pour une meilleure prise en charge automatisée",
+ "description": str(_("Don't update the table structure for a better automated integration.")),
"warningOnly": True,
},
}
@@ -1372,13 +1603,13 @@ class Pool(models.Model):
worksheet.client.batch_update(spreadsheet.id, body)
def update_juries_lines_spreadsheet(self):
- translation.activate('fr')
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
- worksheet = spreadsheet.worksheet(f"Poule {self.short_name}")
+ worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
- average_cell = worksheet.find("Moyenne")
+ average_cell = worksheet.find(str(_("Average")))
min_row = 5
max_row = average_cell.row - 1
juries_visible = worksheet.get(f"A{min_row}:B{max_row}")
@@ -1393,21 +1624,22 @@ class Pool(models.Model):
max_row += 1
def parse_spreadsheet(self):
- translation.activate('fr')
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
self.tournament.create_spreadsheet()
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
- worksheet = spreadsheet.worksheet(f"Poule {self.short_name}")
+ worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
- average_cell = worksheet.find("Moyenne")
+ average_cell = worksheet.find(str(_("Average")))
min_row = 5
max_row = average_cell.row - 2
- data = worksheet.get_values(f"A{min_row}:Z{max_row}")
+ data = worksheet.get_values(f"A{min_row}:AH{max_row}")
if not data or not data[0]:
return
- passage_width = 6
+ has_observer = settings.HAS_OBSERVER and self.participations.count() >= 4
+ passage_width = 6 + (2 if has_observer else 0)
for line in data:
jury_name = line[0]
jury_id = line[1]
@@ -1452,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="+",
)
@@ -1472,24 +1704,34 @@ class Passage(models.Model):
related_name="+",
)
- reporter = models.ForeignKey(
+ reviewer = models.ForeignKey(
Participation,
on_delete=models.PROTECT,
- verbose_name=_("reporter"),
+ verbose_name=_("reviewer"),
related_name="+",
)
- defender_penalties = models.PositiveSmallIntegerField(
+ observer = models.ForeignKey(
+ Participation,
+ on_delete=models.SET_NULL,
+ verbose_name=_("observer"),
+ related_name="+",
+ null=True,
+ blank=True,
+ default=None,
+ )
+
+ 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)
@@ -1497,73 +1739,133 @@ class Passage(models.Model):
items = [i for i in iterator if i]
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())
-
- @property
- def average_defender_oral(self) -> float:
- return self.avg(note.defender_oral for note in self.notes.all())
-
- @property
- def average_defender(self) -> float:
- return self.average_defender_writing + (1.6 - 0.4 * self.defender_penalties) * self.average_defender_oral
-
- @property
- def average_opponent_writing(self) -> float:
- return self.avg(note.opponent_writing for note in self.notes.all())
-
- @property
- def average_opponent_oral(self) -> float:
- return self.avg(note.opponent_oral for note in self.notes.all())
-
- @property
- def average_opponent(self) -> float:
- return 0.9 * self.average_opponent_writing + 2 * self.average_opponent_oral
-
@property
def average_reporter_writing(self) -> float:
return self.avg(note.reporter_writing for note in self.notes.all())
+ @property
+ def coeff_reporter_writing(self) -> float:
+ return 1 if settings.TFJM_APP == "TFJM" else 2
+
@property
def average_reporter_oral(self) -> float:
return self.avg(note.reporter_oral for note in self.notes.all())
+ @property
+ def coeff_reporter_oral(self) -> float:
+ coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
+ coeff *= 1 - 0.25 * self.reporter_penalties
+ return coeff
+
@property
def average_reporter(self) -> float:
- return 0.9 * self.average_reporter_writing + self.average_reporter_oral
+ return (self.coeff_reporter_writing * self.average_reporter_writing
+ + self.coeff_reporter_oral * self.average_reporter_oral)
+
+ @property
+ def average_opponent_writing(self) -> float:
+ return self.avg(note.opponent_writing for note in self.notes.all())
+
+ @property
+ def coeff_opponent_writing(self) -> float:
+ return 0.9 if not self.observer else 0.6
+
+ @property
+ def average_opponent_oral(self) -> float:
+ return self.avg(note.opponent_oral for note in self.notes.all())
+
+ @property
+ def coeff_opponent_oral(self) -> float:
+ return 2
+
+ @property
+ def average_opponent(self) -> float:
+ return (self.coeff_opponent_writing * self.average_opponent_writing
+ + self.coeff_opponent_oral * self.average_opponent_oral)
+
+ @property
+ def average_reviewer_writing(self) -> float:
+ return self.avg(note.reviewer_writing for note in self.notes.all())
+
+ @property
+ def coeff_reviewer_writing(self):
+ return 0.9 if not self.observer else 0.6
+
+ @property
+ def average_reviewer_oral(self) -> float:
+ return self.avg(note.reviewer_oral for note in self.notes.all())
+
+ @property
+ def coeff_reviewer_oral(self):
+ return 1 if settings.TFJM_APP == "TFJM" else 1.2
+
+ @property
+ def average_reviewer(self) -> float:
+ return (self.coeff_reviewer_writing * self.average_reviewer_writing
+ + self.coeff_reviewer_oral * self.average_reviewer_oral)
+
+ @property
+ def average_observer_writing(self) -> float:
+ return self.avg(note.observer_writing for note in self.notes.all())
+
+ @property
+ def coeff_observer_writing(self):
+ return 0.6
+
+ @property
+ def average_observer_oral(self) -> float:
+ return self.avg(note.observer_oral for note in self.notes.all())
+
+ @property
+ def coeff_observer_oral(self):
+ return 0.5
+
+ @property
+ def average_observer(self) -> float:
+ return (self.coeff_observer_writing * self.average_observer_writing
+ + self.coeff_observer_oral * self.average_observer_oral)
@property
def averages(self):
- yield self.average_defender_writing
- yield self.average_defender_oral
- yield self.average_opponent_writing
- yield self.average_opponent_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
+ yield self.average_reviewer_oral
+ if self.observer:
+ yield self.average_observer_writing
+ yield self.average_observer_oral
def average(self, participation):
- return self.average_defender if participation == self.defender else self.average_opponent \
- if participation == self.opponent else self.average_reporter if participation == self.reporter else 0
+ 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
+
+ return avg
def get_absolute_url(self):
return reverse_lazy("participation:passage_detail", args=(self.pk,))
def clean(self):
- if self.defender not in self.pool.participations.all():
- raise ValidationError(_("Team {trigram} is not registered in the pool.")
- .format(trigram=self.defender.team.trigram))
- if self.opponent not in self.pool.participations.all():
- raise ValidationError(_("Team {trigram} is not registered in the pool.")
- .format(trigram=self.opponent.team.trigram))
if self.reporter not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.")
.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))
+ if self.reviewer not in self.pool.participations.all():
+ raise ValidationError(_("Team {trigram} is not registered in the pool.")
+ .format(trigram=self.reviewer.team.trigram))
+ if self.observer and self.observer not in self.pool.participations.all():
+ raise ValidationError(_("Team {trigram} is not registered in the pool.")
+ .format(trigram=self.observer.team.trigram))
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")
@@ -1603,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):
@@ -1649,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,
@@ -1659,14 +1965,15 @@ class Synthesis(models.Model):
passage = models.ForeignKey(
Passage,
on_delete=models.CASCADE,
- related_name="syntheses",
+ related_name="written_reviews",
verbose_name=_("passage"),
)
type = models.PositiveSmallIntegerField(
choices=[
(1, _("opponent"), ),
- (2, _("reporter"), ),
+ (2, _("reviewer"), ),
+ (3, _("observer"), ),
]
)
@@ -1677,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',)
@@ -1706,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,
)
@@ -1730,51 +2037,68 @@ class Note(models.Model):
default=0,
)
- reporter_writing = models.PositiveSmallIntegerField(
- verbose_name=_("reporter writing note"),
+ reviewer_writing = models.PositiveSmallIntegerField(
+ verbose_name=_("reviewer writing note"),
choices=[(i, i) for i in range(0, 11)],
default=0,
)
- reporter_oral = models.PositiveSmallIntegerField(
- verbose_name=_("reporter oral note"),
+ reviewer_oral = models.PositiveSmallIntegerField(
+ verbose_name=_("reviewer oral note"),
choices=[(i, i) for i in range(0, 11)],
default=0,
)
+ observer_writing = models.PositiveSmallIntegerField(
+ verbose_name=_("observer writing note"),
+ choices=[(i, i) for i in range(0, 11)],
+ default=0,
+ )
+
+ observer_oral = models.SmallIntegerField(
+ verbose_name=_("observer oral note"),
+ choices=[(i, i) for i in range(-10, 11)],
+ default=0,
+ )
+
def get_all(self):
- yield self.defender_writing
- yield self.defender_oral
- yield self.opponent_writing
- yield self.opponent_oral
yield self.reporter_writing
yield self.reporter_oral
+ yield self.opponent_writing
+ yield self.opponent_oral
+ yield self.reviewer_writing
+ yield self.reviewer_oral
+ if self.passage.observer:
+ yield self.observer_writing
+ yield self.observer_oral
- def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int,
- reporter_writing: int, reporter_oral: int):
- self.defender_writing = defender_writing
- self.defender_oral = defender_oral
- self.opponent_writing = opponent_writing
- self.opponent_oral = opponent_oral
+ 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.reporter_writing = reporter_writing
self.reporter_oral = reporter_oral
+ self.opponent_writing = opponent_writing
+ self.opponent_oral = opponent_oral
+ self.reviewer_writing = reviewer_writing
+ self.reviewer_oral = reviewer_oral
+ self.observer_writing = observer_writing
+ self.observer_oral = observer_oral
def update_spreadsheet(self):
if not self.has_any_note():
return
- translation.activate('fr')
+ translation.activate(settings.PREFERRED_LANGUAGE_CODE)
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk)
spreadsheet_id = passage.pool.tournament.notes_sheet_id
spreadsheet = gc.open_by_key(spreadsheet_id)
- worksheet = spreadsheet.worksheet(f"Poule {passage.pool.short_name}")
+ worksheet = spreadsheet.worksheet(f"{_('Pool')} {passage.pool.short_name}")
jury_id_cell = worksheet.find(str(self.jury_id), in_column=2)
if not jury_id_cell:
raise ValueError("The jury ID cell was not found in the spreadsheet.")
jury_row = jury_id_cell.row
- passage_width = 6
+ passage_width = 6 + (2 if passage.observer else 0)
def getcol(number: int) -> str:
if number == 0:
diff --git a/participation/signals.py b/participation/signals.py
index e7c041f..8c621ed 100644
--- a/participation/signals.py
+++ b/participation/signals.py
@@ -1,7 +1,9 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
+
from typing import Union
+from django.conf import settings
from participation.models import Note, Participation, Passage, Pool, Team, Tournament
from registration.models import Payment
from tfjm.lists import get_sympa_client
@@ -13,6 +15,8 @@ def create_team_participation(instance, created, raw, **_):
"""
if not raw:
participation = Participation.objects.get_or_create(team=instance)[0]
+ if settings.TFJM_APP == "ETEAM":
+ participation.tournament = Tournament.objects.first()
participation.save()
if not created:
participation.team.create_mailing_list()
@@ -22,7 +26,7 @@ def update_mailing_list(instance: Team, raw, **_):
"""
When a team name or trigram got updated, update mailing lists
"""
- if instance.pk and not raw:
+ if instance.pk and not raw and settings.ML_MANAGEMENT:
old_team = Team.objects.get(pk=instance.pk)
if old_team.trigram != instance.trigram:
# Delete old mailing list, create a new one
@@ -41,7 +45,7 @@ def create_payments(instance: Participation, created, raw, **_):
"""
When a participation got created, create an associated payment.
"""
- if instance.valid and not raw:
+ if instance.valid and not raw and settings.PAYMENT_MANAGEMENT:
for student in instance.team.students.all():
payment_qs = Payment.objects.filter(registrations=student, final=False)
if payment_qs.exists():
diff --git a/participation/tables.py b/participation/tables.py
index 936062d..a535d50 100644
--- a/participation/tables.py
+++ b/participation/tables.py
@@ -106,19 +106,24 @@ class PoolTable(tables.Table):
class PassageTable(tables.Table):
- defender = tables.LinkColumn(
+ # FIXME Ne pas afficher l'équipe observatrice si non nécessaire
+
+ 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):
return value.team.trigram
- def render_reporter(self, value):
+ def render_reviewer(self, value):
+ return value.team.trigram
+
+ def render_observer(self, value):
return value.team.trigram
class Meta:
@@ -126,7 +131,7 @@ class PassageTable(tables.Table):
'class': 'table table-condensed table-striped text-center',
}
model = Passage
- fields = ('defender', 'opponent', 'reporter', 'solution_number', )
+ fields = ('reporter', 'opponent', 'reviewer', 'observer', 'solution_number', )
class NoteTable(tables.Table):
@@ -154,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',
- 'reporter_writing', 'reporter_oral', 'update',)
+ fields = ('jury', 'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
+ 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', 'update',)
diff --git a/participation/templates/participation/mails/request_validation.html b/participation/templates/participation/mails/request_validation.html
index cbf75d1..29b96d6 100644
--- a/participation/templates/participation/mails/request_validation.html
+++ b/participation/templates/participation/mails/request_validation.html
@@ -2,28 +2,28 @@
- Demande de validation - TFJM²
+ Validation request - ETEAM
-Bonjour,
+Hi,
-L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer
-au {{ team.participation.get_problem_display }} du TFJM².
-Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe :
+The team "{{ team.name }}" ({{ team.trigram }}) has just asked to validate his team to take part
+in ETEAM.
+You can decide whether or not to accept the team by going to the team page:
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
-Cordialement,
+Sincerely yours,
-L'organisation du TFJM²
+The ETEAM team
diff --git a/participation/templates/participation/mails/request_validation.txt b/participation/templates/participation/mails/request_validation.txt
index d9ff5bf..fe84e37 100644
--- a/participation/templates/participation/mails/request_validation.txt
+++ b/participation/templates/participation/mails/request_validation.txt
@@ -1,10 +1,10 @@
-Bonjour {{ user }},
+Hi {{ user }},
-L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer
-au {{ team.participation.get_problem_display }} du TFJM².
-Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe :
+The team "{{ team.name }}" ({{ team.trigram }}) has just asked to validate his team to take part
+in ETEAM.
+You can decide whether or not to accept the team by going to the team page:
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
-Cordialement,
+Sincerely yours,
-L'organisation du TFJM²
+The ETEAM team
diff --git a/participation/templates/participation/mails/team_not_validated.html b/participation/templates/participation/mails/team_not_validated.html
index 915a003..3697650 100644
--- a/participation/templates/participation/mails/team_not_validated.html
+++ b/participation/templates/participation/mails/team_not_validated.html
@@ -2,21 +2,21 @@
- Équipe non validée – TFJM²
+ Team not validated – ETEAM
-Bonjour,
+Hi,
-Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos autorisations
-de droit à l'image sont correctes. Les organisateurs vous adressent ce message :
+Unfortunately, your team "{{ team.name }}" ({{ team.trigram }}) has not been validated.
+Please check that your authorisations are correctly filled in.
+The organisers are sending you this message:
{{ message }}
-N'hésitez pas à nous contacter à l'adresse contact@tfjm.org
-pour plus d'informations.
+Please contact us at eteam_moc@proton.me if you need further information.
-Cordialement,
+Sincerely yours,
-Le comité d'organisation du TFJM²
+The ETEAM team
diff --git a/participation/templates/participation/mails/team_not_validated.txt b/participation/templates/participation/mails/team_not_validated.txt
index 9479fba..8ed8a4e 100644
--- a/participation/templates/participation/mails/team_not_validated.txt
+++ b/participation/templates/participation/mails/team_not_validated.txt
@@ -1,12 +1,13 @@
-Bonjour,
+Hi,
-Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos
-autorisations de droit à l'image sont correctes. Les organisateurs vous adressent ce message :
+Unfortunately, your team "{{ team.name }}" ({{ team.trigram }}) has not been validated.
+Please check that your authorisations are correctly filled in.
+The organisers are sending you this message:
{{ message }}
-N'hésitez pas à nous contacter à l'adresse contact@tfjm.org pour plus d'informations.
+Please contact us at eteam_moc@proton.me if you need further information.
-Cordialement,
+Sincerely yours,
-Le comité d'organisation du TFJM²
+The ETEAM team
diff --git a/participation/templates/participation/mails/team_validated.html b/participation/templates/participation/mails/team_validated.html
index 9281279..025c64e 100644
--- a/participation/templates/participation/mails/team_validated.html
+++ b/participation/templates/participation/mails/team_validated.html
@@ -2,37 +2,36 @@
- Équipe validée – TFJM²
+ Team validated – ETEAM
- Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais
- apte à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
+ Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
+ to work on your problems. You can then upload your solutions to the platform.
{% if payment %}
- Vous devez désormais vous acquitter de vos frais de participation, de {{ payment.amount }} € par élève.
- Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
- sur la page de paiement.
- Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
- sur la même page.
+ You must now pay your participation fee of € {{ payment.amount }}.
+ You can pay by credit card or bank transfer. You'll find information
+ on the payment page which you can find on
+ your account.
+ If you have a scholarship, registration is free, but you must submit a justification on the same page.
{% elif registration.is_coach and team.participation.tournament.price %}
- Votre équipe doit désormais s'acquitter des frais de participation de {{ team.participation.tournament.price }} €
- par élève (les encadrant⋅es sont exonéré⋅es). Les élèves qui disposent d'une bourse sont exonéré⋅es de ces frais.
- Vous pouvez suivre l'état des paiements sur
- la page de votre équipe.
+ Your team must now pay a participation fee of {{ team.participation.tournament.price }} € per student (supervisors are exempt). Students with scholarships are exempt⋅es from these fees.
+ You can track the status of payments on
+ your team page.
{% endif %}
{% if message %}
- Les organisateur⋅ices vous adressent ce message :
+ The organisers send you this message:
{{ message }}
@@ -40,7 +39,7 @@
{% endif %}
- Le comité d'organisation du TFJM²
+ The ETEAM team
diff --git a/participation/templates/participation/mails/team_validated.txt b/participation/templates/participation/mails/team_validated.txt
index 20888c0..98b1814 100644
--- a/participation/templates/participation/mails/team_validated.txt
+++ b/participation/templates/participation/mails/team_validated.txt
@@ -1,23 +1,21 @@
-Bonjour {{ registration }},
+Hello {{registration }},
-Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte
-à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
-{% if team.participation.amount %}
-Vous devez désormais vous acquitter de vos frais de participation, de {{ team.participation.amount }} €.
-Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
-sur la page de paiement que vous pouvez retrouver sur votre compte :
+Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
+to work on your problems. You can then upload your solutions to the platform.
+{% if payment %}
+You must now pay your participation fee of € {{ payment.amount }}.
+You can pay by credit card or bank transfer. You'll find information
+on the payment page which you can find on your account:
https://{{ domain }}{% url 'registration:my_account_detail' %}
-Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
-sur la même page.
+If you have a scholarship, registration is free, but you must submit a justification on the same page.
{% elif registration.is_coach and team.participation.tournament.price %}
-Votre équipe doit désormais s'acquitter des frais de participation de {{ team.participation.tournament.price }} €
-par élève (les encadrant⋅es sont exonéré⋅es). Les élèves qui disposent d'une bourse sont exonéré⋅es de ces frais.
-Vous pouvez suivre l'état des paiements sur la page de votre équipe :
+Your team must now pay a participation fee of {{ team.participation.tournament.price }} € per student (supervisors are exempt). Students with scholarships are exempt⋅es from these fees.
+You can track the status of payments on your team page:
https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}
{% endif %}
{% if message %}
-Les organisateurices vous adressent ce message :
+The organisers send you this message:
{{ message }}
{% endif %}
-Le comité d'organisation du TFJM²
+The ETEAM team
diff --git a/participation/templates/participation/note_form.html b/participation/templates/participation/note_form.html
index 1b55439..d059915 100644
--- a/participation/templates/participation/note_form.html
+++ b/participation/templates/participation/note_form.html
@@ -6,7 +6,7 @@
@@ -74,16 +79,20 @@
- {% trans "Average points for the defender writing" %}
- ({{ passage.defender.team.trigram }}) :
+ {% trans "Average points for the reporter writing" %}
+ ({{ passage.reporter.team.trigram }}) :
- {% trans "Average points for the defender oral" %}
- ({{ passage.defender.team.trigram }}) :
+ {% trans "Average points for the reporter oral" %}
+ ({{ passage.reporter.team.trigram }}) :
- {% trans "Average points for the reporter writing" %}
- ({{ passage.reporter.team.trigram }}) :
+ {% trans "Average points for the reviewer writing" %}
+ ({{ passage.reviewer.team.trigram }}) :
- {% trans "Average points for the reporter oral" %}
- ({{ passage.reporter.team.trigram }}) :
+ {% trans "Average points for the reviewer oral" %}
+ ({{ passage.reviewer.team.trigram }}) :
diff --git a/participation/templates/participation/team_detail.html b/participation/templates/participation/team_detail.html
index 4e12bb0..7e6ad0f 100644
--- a/participation/templates/participation/team_detail.html
+++ b/participation/templates/participation/team_detail.html
@@ -73,32 +73,36 @@
{% endif %}
- {% if not team.participation.tournament.remote %}
-
{% trans "Health sheets:" %}
-
- {% for student in team.students.all %}
- {% if student.under_18 %}
- {% if student.health_sheet %}
- {{ student }}{% if not forloop.last %},{% endif %}
- {% else %}
- {{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
+ {% if not team.participation.tournament.remote %}
+ {% if TFJM.HEALTH_SHEET_REQUIRED %}
+
{% trans "Health sheets:" %}
+
+ {% for student in team.students.all %}
+ {% if student.under_18 %}
+ {% if student.health_sheet %}
+ {{ student }}{% if not forloop.last %},{% endif %}
+ {% else %}
+ {{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
+ {% endif %}
{% endif %}
- {% endif %}
- {% endfor %}
-
+ {% endfor %}
+
+ {% endif %}
-
{% trans "Vaccine sheets:" %}
-
- {% for student in team.students.all %}
- {% if student.under_18 %}
- {% if student.vaccine_sheet %}
- {{ student }}{% if not forloop.last %},{% endif %}
- {% else %}
- {{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
+ {% if TFJM.VACCINE_SHEET_REQUIRED %}
+
{% trans "Vaccine sheets:" %}
+
+ {% for student in team.students.all %}
+ {% if student.under_18 %}
+ {% if student.vaccine_sheet %}
+ {{ student }}{% if not forloop.last %},{% endif %}
+ {% else %}
+ {{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
+ {% endif %}
{% endif %}
- {% endif %}
- {% endfor %}
-
+ {% endfor %}
+
+ {% endif %}
{% trans "Parental authorizations:" %}
@@ -129,17 +133,19 @@
{% endif %}
{% endif %}
-
{% trans "Motivation letter:" %}
-
- {% if team.motivation_letter %}
- {% trans "Download" %}
- {% else %}
- {% trans "Not uploaded yet" %}
- {% endif %}
- {% if user.registration.team == team and not user.registration.team.participation.valid or user.registration.is_admin %}
-
- {% endif %}
-
+ {% if TFJM.MOTIVATION_LETTER_REQUIRED %}
+
{% trans "Motivation letter:" %}
+
+ {% if team.motivation_letter %}
+ {% trans "Download" %}
+ {% else %}
+ {% trans "Not uploaded yet" %}
+ {% endif %}
+ {% if user.registration.team == team and not user.registration.team.participation.valid or user.registration.is_admin %}
+
+ {% endif %}
+
+ {% endif %}
{% if user.registration.is_volunteer %}
{% if user.registration in self.team.participation.tournament.organizers or user.registration.is_admin %}
@@ -234,10 +240,12 @@
{% endif %}
{% endif %}
- {% trans "Upload motivation letter" as modal_title %}
- {% trans "Upload" as modal_button %}
- {% url "participation:upload_team_motivation_letter" pk=team.pk as modal_action %}
- {% include "base_modal.html" with modal_id="uploadMotivationLetter" modal_enctype="multipart/form-data" %}
+ {% if TFJM.MOTIVATION_LETTER_REQUIRED %}
+ {% trans "Upload motivation letter" as modal_title %}
+ {% trans "Upload" as modal_button %}
+ {% url "participation:upload_team_motivation_letter" pk=team.pk as modal_action %}
+ {% include "base_modal.html" with modal_id="uploadMotivationLetter" modal_enctype="multipart/form-data" %}
+ {% endif %}
{% trans "Update team" as modal_title %}
{% trans "Update" as modal_button %}
@@ -253,7 +261,9 @@
{% block extrajavascript %}
{# bootstrap-select for beautiful selects and JQuery dependency #}
- {% javascript 'bootstrap_select' %}
+
+
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
{% if form.media %}
@@ -84,8 +94,10 @@
{% javascript 'main' %}
+{{ TFJM|json_script:'TFJM_settings' }}
+