mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-02-26 21:06:27 +00:00
Compare commits
3 Commits
16c4376941
...
7e212d011e
Author | SHA1 | Date | |
---|---|---|---|
|
7e212d011e | ||
|
2840a15fd5 | ||
|
c1482d4802 |
@ -4,7 +4,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Draw, Round, Pool, TeamDraw
|
from .models import Draw, Pool, Round, TeamDraw
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Draw)
|
@admin.register(Draw)
|
||||||
|
@ -8,9 +8,8 @@ from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from draw.models import Draw, Pool, Round, TeamDraw
|
||||||
from draw.models import Draw, Round, Pool, TeamDraw
|
from participation.models import Participation, Tournament
|
||||||
from participation.models import Tournament, Participation
|
|
||||||
from registration.models import Registration
|
from registration.models import Registration
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
reg = await Registration.objects.aget(user=user)
|
reg = await Registration.objects.aget(user=user)
|
||||||
self.registration = reg
|
self.registration = reg
|
||||||
if reg.is_volunteer and not reg.is_admin and self.tournament not in reg.interesting_tournaments \
|
if reg.is_volunteer and not reg.is_admin and self.tournament not in reg.interesting_tournaments \
|
||||||
or not reg.is_volunteer and reg.team.participation.tournament != self.tournament:
|
or not reg.is_volunteer and reg.team.participation.tournament != self.tournament:
|
||||||
# This user may not have access to the drawing session
|
# This user may not have access to the drawing session
|
||||||
await self.close()
|
await self.close()
|
||||||
return
|
return
|
||||||
@ -148,14 +147,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
try:
|
try:
|
||||||
# Parse format from string
|
# Parse format from string
|
||||||
fmt: list[int] = sorted(map(int, fmt.split('+')), reverse=True)
|
fmt: list[int] = sorted(map(int, fmt.split('+')), reverse=True)
|
||||||
except ValueError as _ignored:
|
except ValueError:
|
||||||
return await self.alert(_("Invalid format"), 'danger')
|
return await self.alert(_("Invalid format"), 'danger')
|
||||||
|
|
||||||
# Ensure that the number of teams is good
|
# Ensure that the number of teams is good
|
||||||
if sum(fmt) != len(self.participations):
|
if sum(fmt) != len(self.participations):
|
||||||
return await self.alert(
|
return await self.alert(
|
||||||
_("The sum must be equal to the number of teams: expected {len}, got {sum}")\
|
_("The sum must be equal to the number of teams: expected {len}, got {sum}")
|
||||||
.format(len=len(self.participations), sum=sum(fmt)), 'danger')
|
.format(len=len(self.participations), sum=sum(fmt)), 'danger')
|
||||||
|
|
||||||
# The drawing system works with a maximum of 1 pool of 5 teams, which is already the case in the TFJM²
|
# The drawing system works with a maximum of 1 pool of 5 teams, which is already the case in the TFJM²
|
||||||
if fmt.count(5) > 1:
|
if fmt.count(5) > 1:
|
||||||
@ -191,9 +190,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
|
|
||||||
# Update user interface
|
# Update user interface
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.start', 'fmt': fmt, 'draw': draw})
|
{'type': 'draw.start', 'fmt': fmt, 'draw': draw})
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_info', 'draw': draw})
|
{'type': 'draw.set_info', 'draw': draw})
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
||||||
|
|
||||||
@ -207,7 +206,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
"""
|
"""
|
||||||
Send information to users that the draw has started.
|
Send information to users that the draw has started.
|
||||||
"""
|
"""
|
||||||
await self.alert(_("The draw for the tournament {tournament} will start.")\
|
await self.alert(_("The draw for the tournament {tournament} will start.")
|
||||||
.format(tournament=self.tournament.name), 'warning')
|
.format(tournament=self.tournament.name), 'warning')
|
||||||
await self.send_json({'type': 'draw_start', 'fmt': content['fmt'],
|
await self.send_json({'type': 'draw_start', 'fmt': content['fmt'],
|
||||||
'trigrams': [p.team.trigram for p in self.participations]})
|
'trigrams': [p.team.trigram for p in self.participations]})
|
||||||
@ -230,11 +229,10 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
"""
|
"""
|
||||||
Send information to users that the draw was aborted.
|
Send information to users that the draw was aborted.
|
||||||
"""
|
"""
|
||||||
await self.alert(_("The draw for the tournament {tournament} is aborted.")\
|
await self.alert(_("The draw for the tournament {tournament} is aborted.")
|
||||||
.format(tournament=self.tournament.name), 'danger')
|
.format(tournament=self.tournament.name), 'danger')
|
||||||
await self.send_json({'type': 'abort'})
|
await self.send_json({'type': 'abort'})
|
||||||
|
|
||||||
|
|
||||||
async def process_dice(self, trigram: str | None = None, **kwargs):
|
async def process_dice(self, trigram: str | None = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Launch the dice for a team.
|
Launch the dice for a team.
|
||||||
@ -332,13 +330,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
|
|
||||||
# Get concerned TeamDraw objects
|
# Get concerned TeamDraw objects
|
||||||
if state == 'DICE_SELECT_POULES':
|
if state == 'DICE_SELECT_POULES':
|
||||||
tds = [td async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id) \
|
tds = [td async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id)
|
||||||
.prefetch_related('participation__team')]
|
.prefetch_related('participation__team')]
|
||||||
dices = {td: td.passage_dice for td in tds}
|
dices = {td: td.passage_dice for td in tds}
|
||||||
else:
|
else:
|
||||||
tds = [td async for td in TeamDraw.objects\
|
tds = [td async for td in TeamDraw.objects
|
||||||
.filter(pool_id=self.tournament.draw.current_round.current_pool_id)\
|
.filter(pool_id=self.tournament.draw.current_round.current_pool_id)
|
||||||
.prefetch_related('participation__team')]
|
.prefetch_related('participation__team')]
|
||||||
dices = {td: td.choice_dice for td in tds}
|
dices = {td: td.choice_dice for td in tds}
|
||||||
|
|
||||||
values = list(dices.values())
|
values = list(dices.values())
|
||||||
@ -408,8 +406,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
# which is this specific pool since they are ordered by decreasing size.
|
# which is this specific pool since they are ordered by decreasing size.
|
||||||
tds_copy = tds.copy()
|
tds_copy = tds.copy()
|
||||||
round2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
round2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||||
round2_pools = [p async for p in Pool.objects.filter(round__draw__tournament=self.tournament, round=round2) \
|
round2_pools = [p async for p in Pool.objects.filter(round__draw__tournament=self.tournament, round=round2)
|
||||||
.order_by('letter').all()]
|
.order_by('letter').all()]
|
||||||
current_pool_id, current_passage_index = 0, 0
|
current_pool_id, current_passage_index = 0, 0
|
||||||
for i, td in enumerate(tds_copy):
|
for i, td in enumerate(tds_copy):
|
||||||
if i == len(tds) - 1 and round2_pools[0].size == 5:
|
if i == len(tds) - 1 and round2_pools[0].size == 5:
|
||||||
@ -511,7 +509,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
# Notify the team that it can draw a problem
|
# Notify the team that it can draw a problem
|
||||||
await self.channel_layer.group_send(f"team-{tds[0].participation.team.trigram}",
|
await self.channel_layer.group_send(f"team-{tds[0].participation.team.trigram}",
|
||||||
{'type': 'draw.notify', 'title': "À votre tour !",
|
{'type': 'draw.notify', 'title': "À votre tour !",
|
||||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||||
|
|
||||||
async def select_problem(self, **kwargs):
|
async def select_problem(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -566,7 +564,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
self.tournament.draw.last_message = ""
|
self.tournament.draw.last_message = ""
|
||||||
await self.tournament.draw.asave()
|
await self.tournament.draw.asave()
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
||||||
|
|
||||||
async def accept_problem(self, **kwargs):
|
async def accept_problem(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -636,109 +634,127 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||||
else:
|
else:
|
||||||
# Pool is ended
|
# Pool is ended
|
||||||
if pool.size == 5:
|
await self.end_pool(pool)
|
||||||
# Maybe reorder teams if the same problem is presented twice
|
|
||||||
problems = OrderedDict()
|
|
||||||
async for td in pool.team_draws:
|
|
||||||
problems.setdefault(td.accepted, [])
|
|
||||||
problems[td.accepted].append(td)
|
|
||||||
p_index = 0
|
|
||||||
for pb, tds in problems.items():
|
|
||||||
if len(tds) == 2:
|
|
||||||
# Le règlement demande à ce que l'ordre soit tiré au sort
|
|
||||||
shuffle(tds)
|
|
||||||
tds[0].passage_index = p_index
|
|
||||||
tds[1].passage_index = p_index + 1
|
|
||||||
p_index += 2
|
|
||||||
await tds[0].asave()
|
|
||||||
await tds[1].asave()
|
|
||||||
for pb, tds in problems.items():
|
|
||||||
if len(tds) == 1:
|
|
||||||
tds[0].passage_index = p_index
|
|
||||||
p_index += 1
|
|
||||||
await tds[0].asave()
|
|
||||||
|
|
||||||
# Send the reordered pool
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {
|
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
||||||
'type': 'draw.reorder_pool',
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
'round': r.number,
|
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
||||||
'pool': pool.get_letter_display(),
|
|
||||||
'teams': [td.participation.team.trigram
|
|
||||||
async for td in pool.team_draws.prefetch_related('participation__team')],
|
|
||||||
'problems': [td.accepted async for td in pool.team_draws],
|
|
||||||
})
|
|
||||||
|
|
||||||
msg += f"<br><br>Le tirage de la poule {pool.get_letter_display()}{r.number} est terminé. " \
|
async def end_pool(self, pool: Pool) -> None:
|
||||||
f"Le tableau récapitulatif est en bas."
|
"""
|
||||||
|
End the pool, and pass to the next one, or to the next round, or end the draw.
|
||||||
|
:param pool: The pool to end.
|
||||||
|
"""
|
||||||
|
msg = self.tournament.draw.last_message
|
||||||
|
r = pool.round
|
||||||
|
|
||||||
|
if pool.size == 5:
|
||||||
|
# Maybe reorder teams if the same problem is presented twice
|
||||||
|
problems = OrderedDict()
|
||||||
|
async for td in pool.team_draws:
|
||||||
|
problems.setdefault(td.accepted, [])
|
||||||
|
problems[td.accepted].append(td)
|
||||||
|
p_index = 0
|
||||||
|
for pb, tds in problems.items():
|
||||||
|
if len(tds) == 2:
|
||||||
|
# Le règlement demande à ce que l'ordre soit tiré au sort
|
||||||
|
shuffle(tds)
|
||||||
|
tds[0].passage_index = p_index
|
||||||
|
tds[1].passage_index = p_index + 1
|
||||||
|
p_index += 2
|
||||||
|
await tds[0].asave()
|
||||||
|
await tds[1].asave()
|
||||||
|
for pb, tds in problems.items():
|
||||||
|
if len(tds) == 1:
|
||||||
|
tds[0].passage_index = p_index
|
||||||
|
p_index += 1
|
||||||
|
await tds[0].asave()
|
||||||
|
|
||||||
|
# Send the reordered pool
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {
|
||||||
|
'type': 'draw.reorder_pool',
|
||||||
|
'round': r.number,
|
||||||
|
'pool': pool.get_letter_display(),
|
||||||
|
'teams': [td.participation.team.trigram
|
||||||
|
async for td in pool.team_draws.prefetch_related('participation__team')],
|
||||||
|
'problems': [td.accepted async for td in pool.team_draws],
|
||||||
|
})
|
||||||
|
|
||||||
|
msg += f"<br><br>Le tirage de la poule {pool.get_letter_display()}{r.number} est terminé. " \
|
||||||
|
f"Le tableau récapitulatif est en bas."
|
||||||
|
self.tournament.draw.last_message = msg
|
||||||
|
await self.tournament.draw.asave()
|
||||||
|
|
||||||
|
if await r.teamdraw_set.filter(accepted__isnull=True).aexists():
|
||||||
|
# There is a pool that does not have selected its problem, so we continue to the next pool
|
||||||
|
next_pool = await r.next_pool()
|
||||||
|
r.current_pool = next_pool
|
||||||
|
await r.asave()
|
||||||
|
|
||||||
|
async for td in next_pool.team_draws.prefetch_related('participation__team').all():
|
||||||
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
|
{'type': 'draw.dice_visibility', 'visible': True})
|
||||||
|
# Notify the team that it can draw a dice
|
||||||
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
|
{'type': 'draw.notify', 'title': "À votre tour !",
|
||||||
|
'body': "C'est à vous de lancer le dé !"})
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.dice_visibility', 'visible': True})
|
||||||
|
else:
|
||||||
|
# Round is ended
|
||||||
|
await self.end_round(r)
|
||||||
|
|
||||||
|
async def end_round(self, r: Round) -> None:
|
||||||
|
"""
|
||||||
|
End the round, and pass to the next one, or end the draw.
|
||||||
|
:param r: The current round.
|
||||||
|
"""
|
||||||
|
msg = self.tournament.draw.last_message
|
||||||
|
|
||||||
|
if r.number == 1 and not self.tournament.final:
|
||||||
|
# Next round
|
||||||
|
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||||
|
self.tournament.draw.current_round = r2
|
||||||
|
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
||||||
self.tournament.draw.last_message = msg
|
self.tournament.draw.last_message = msg
|
||||||
await self.tournament.draw.asave()
|
await self.tournament.draw.asave()
|
||||||
|
|
||||||
if await r.teamdraw_set.filter(accepted__isnull=True).aexists():
|
for participation in self.participations:
|
||||||
# There is a pool that does not have selected its problem, so we continue to the next pool
|
await self.channel_layer.group_send(
|
||||||
next_pool = await r.next_pool()
|
f"tournament-{self.tournament.id}",
|
||||||
r.current_pool = next_pool
|
{'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
|
||||||
await r.asave()
|
|
||||||
|
|
||||||
|
# Notify the team that it can draw a dice
|
||||||
|
await self.channel_layer.group_send(f"team-{participation.team.trigram}",
|
||||||
|
{'type': 'draw.notify', 'title': "À votre tour !",
|
||||||
|
'body': "C'est à vous de lancer le dé !"})
|
||||||
|
|
||||||
async for td in next_pool.team_draws.prefetch_related('participation__team').all():
|
# Reorder dices
|
||||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.dice_visibility', 'visible': True})
|
{'type': 'draw.send_poules',
|
||||||
# Notify the team that it can draw a dice
|
'round': r2})
|
||||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
|
||||||
{'type': 'draw.notify', 'title': "À votre tour !",
|
|
||||||
'body': "C'est à vous de lancer le dé !"})
|
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
# 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()
|
||||||
|
|
||||||
|
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
|
||||||
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
{'type': 'draw.dice_visibility', 'visible': True})
|
{'type': 'draw.dice_visibility', 'visible': True})
|
||||||
else:
|
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||||
# Round is ended
|
{'type': 'draw.dice_visibility', 'visible': True})
|
||||||
if r.number == 1 and not self.tournament.final:
|
elif r.number == 1 and self.tournament.final:
|
||||||
# Next round
|
# For the final tournament, we wait for a manual update between the two rounds.
|
||||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
||||||
self.tournament.draw.current_round = r2
|
self.tournament.draw.last_message = msg
|
||||||
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
await self.tournament.draw.asave()
|
||||||
self.tournament.draw.last_message = msg
|
|
||||||
await self.tournament.draw.asave()
|
|
||||||
|
|
||||||
for participation in self.participations:
|
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||||
await self.channel_layer.group_send(
|
{'type': 'draw.export_visibility', 'visible': True})
|
||||||
f"tournament-{self.tournament.id}",
|
|
||||||
{'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
|
|
||||||
|
|
||||||
# Notify the team that it can draw a dice
|
|
||||||
await self.channel_layer.group_send(f"team-{participation.team.trigram}",
|
|
||||||
{'type': 'draw.notify', 'title': "À votre tour !",
|
|
||||||
'body': "C'est à vous de lancer le dé !"})
|
|
||||||
|
|
||||||
# Reorder dices
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
|
||||||
{'type': 'draw.send_poules',
|
|
||||||
'round': r2})
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
|
|
||||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
|
||||||
{'type': 'draw.dice_visibility', 'visible': True})
|
|
||||||
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
|
||||||
{'type': 'draw.dice_visibility', 'visible': True})
|
|
||||||
elif r.number == 1 and self.tournament.final:
|
|
||||||
# For the final tournament, we wait for a manual update between the two rounds.
|
|
||||||
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
|
||||||
self.tournament.draw.last_message = msg
|
|
||||||
await self.tournament.draw.asave()
|
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
|
||||||
{'type': 'draw.export_visibility', 'visible': True})
|
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
|
||||||
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
|
||||||
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
|
||||||
|
|
||||||
async def reject_problem(self, **kwargs):
|
async def reject_problem(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -813,7 +829,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
{'type': 'draw.box_visibility', 'visible': True})
|
{'type': 'draw.box_visibility', 'visible': True})
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
||||||
|
|
||||||
@ -822,7 +838,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
{'type': 'draw.notify', 'title': "À votre tour !",
|
{'type': 'draw.notify', 'title': "À votre tour !",
|
||||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||||
|
|
||||||
|
|
||||||
@ensure_orga
|
@ensure_orga
|
||||||
async def export(self, **kwargs):
|
async def export(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -867,8 +882,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
notes = dict()
|
notes = dict()
|
||||||
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
|
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
|
||||||
notes[participation] = sum([await pool.aaverage(participation)
|
notes[participation] = sum([await pool.aaverage(participation)
|
||||||
async for pool in self.tournament.pools.filter(participations=participation)\
|
async for pool in self.tournament.pools.filter(participations=participation)
|
||||||
.prefetch_related('passages').prefetch_related('tweaks')
|
.prefetch_related('passages').prefetch_related('tweaks')
|
||||||
if pool.results_available])
|
if pool.results_available])
|
||||||
# Sort notes in a decreasing order
|
# Sort notes in a decreasing order
|
||||||
ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
|
ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
|
||||||
@ -906,7 +921,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
{'type': 'draw.continue_visibility', 'visible': False})
|
{'type': 'draw.continue_visibility', 'visible': False})
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
{'type': 'draw.set_active', 'draw': self.tournament.draw})
|
||||||
|
|
||||||
@ -981,8 +996,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'type': 'set_active',
|
'type': 'set_active',
|
||||||
'round': r.number,
|
'round': r.number,
|
||||||
'poule': r.current_pool.get_letter_display() if r.current_pool else None,
|
'poule': r.current_pool.get_letter_display() if r.current_pool else None,
|
||||||
'team': r.current_pool.current_team.participation.team.trigram \
|
'team': r.current_pool.current_team.participation.team.trigram
|
||||||
if r.current_pool and r.current_pool.current_team else None,
|
if r.current_pool and r.current_pool.current_team else None,
|
||||||
})
|
})
|
||||||
|
|
||||||
async def draw_set_problem(self, content):
|
async def draw_set_problem(self, content):
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.text import format_lazy, slugify
|
from django.utils.text import format_lazy, slugify
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from participation.models import Participation, Passage, Pool as PPool, Tournament
|
||||||
from participation.models import Passage, Participation, Pool as PPool, Tournament
|
|
||||||
|
|
||||||
|
|
||||||
class Draw(models.Model):
|
class Draw(models.Model):
|
||||||
@ -292,16 +291,16 @@ class Pool(models.Model):
|
|||||||
Returns a list of trigrams of the teams in this pool ordered by passage index.
|
Returns a list of trigrams of the teams in this pool ordered by passage index.
|
||||||
This property is synchronous.
|
This property is synchronous.
|
||||||
"""
|
"""
|
||||||
return [td.participation.team.trigram for td in self.teamdraw_set.order_by('passage_index')\
|
return [td.participation.team.trigram for td in self.teamdraw_set.order_by('passage_index')
|
||||||
.prefetch_related('participation__team').all()]
|
.prefetch_related('participation__team').all()]
|
||||||
|
|
||||||
async def atrigrams(self) -> list[str]:
|
async def atrigrams(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Returns a list of trigrams of the teams in this pool ordered by passage index.
|
Returns a list of trigrams of the teams in this pool ordered by passage index.
|
||||||
This property is asynchronous.
|
This property is asynchronous.
|
||||||
"""
|
"""
|
||||||
return [td.participation.team.trigram async for td in self.teamdraw_set.order_by('passage_index')\
|
return [td.participation.team.trigram async for td in self.teamdraw_set.order_by('passage_index')
|
||||||
.prefetch_related('participation__team').all()]
|
.prefetch_related('participation__team').all()]
|
||||||
|
|
||||||
async def next_td(self) -> "TeamDraw":
|
async def next_td(self) -> "TeamDraw":
|
||||||
"""
|
"""
|
||||||
@ -349,8 +348,8 @@ class Pool(models.Model):
|
|||||||
|
|
||||||
# Define the participations of the pool
|
# Define the participations of the pool
|
||||||
tds = [td async for td in self.team_draws.prefetch_related('participation')]
|
tds = [td async for td in self.team_draws.prefetch_related('participation')]
|
||||||
await self.associated_pool.participations.aset([td.participation async for td in self.team_draws\
|
await self.associated_pool.participations.aset([td.participation async for td in self.team_draws
|
||||||
.prefetch_related('participation')])
|
.prefetch_related('participation')])
|
||||||
await self.asave()
|
await self.asave()
|
||||||
|
|
||||||
# Define the passage matrix according to the number of teams
|
# Define the passage matrix according to the number of teams
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic import TemplateView, DetailView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from participation.models import Tournament
|
from participation.models import Tournament
|
||||||
|
|
||||||
|
|
||||||
@ -36,5 +35,4 @@ class DisplayView(LoginRequiredMixin, TemplateView):
|
|||||||
context['tournaments_simplified'] = [{'id': t.id, 'name': t.name} for t in tournaments]
|
context['tournaments_simplified'] = [{'id': t.id, 'name': t.name} for t in tournaments]
|
||||||
context['problems'] = settings.PROBLEMS
|
context['problems'] = settings.PROBLEMS
|
||||||
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: TFJM\n"
|
"Project-Id-Version: TFJM\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-04-05 10:43+0200\n"
|
"POT-Creation-Date: 2023-04-05 16:46+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
|
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -422,80 +422,94 @@ msgstr "rapporteur⋅e"
|
|||||||
msgid "problem"
|
msgid "problem"
|
||||||
msgstr "numéro de problème"
|
msgstr "numéro de problème"
|
||||||
|
|
||||||
#: participation/forms.py:27
|
#: participation/forms.py:30
|
||||||
msgid "This name is already used."
|
msgid "This name is already used."
|
||||||
msgstr "Ce nom est déjà utilisé."
|
msgstr "Ce nom est déjà utilisé."
|
||||||
|
|
||||||
#: participation/forms.py:34 participation/models.py:40
|
#: participation/forms.py:37 participation/models.py:40
|
||||||
msgid "The trigram must be composed of three uppercase letters."
|
msgid "The trigram must be composed of three uppercase letters."
|
||||||
msgstr "Le trigramme doit être composé de trois lettres majuscules."
|
msgstr "Le trigramme doit être composé de trois lettres majuscules."
|
||||||
|
|
||||||
#: participation/forms.py:37
|
#: participation/forms.py:40
|
||||||
msgid "This trigram is already used."
|
msgid "This trigram is already used."
|
||||||
msgstr "Ce trigramme est déjà utilisé."
|
msgstr "Ce trigramme est déjà utilisé."
|
||||||
|
|
||||||
#: participation/forms.py:52
|
#: participation/forms.py:55
|
||||||
msgid "No team was found with this access code."
|
msgid "No team was found with this access code."
|
||||||
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
|
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
|
||||||
|
|
||||||
#: participation/forms.py:81 participation/forms.py:289
|
#: participation/forms.py:84 participation/forms.py:334
|
||||||
#: registration/forms.py:113 registration/forms.py:135
|
#: registration/forms.py:113 registration/forms.py:135
|
||||||
#: registration/forms.py:157 registration/forms.py:179
|
#: registration/forms.py:157 registration/forms.py:179
|
||||||
#: registration/forms.py:224
|
#: registration/forms.py:224
|
||||||
msgid "The uploaded file size must be under 2 Mo."
|
msgid "The uploaded file size must be under 2 Mo."
|
||||||
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
|
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
|
||||||
|
|
||||||
#: participation/forms.py:83 registration/forms.py:115
|
#: participation/forms.py:86 registration/forms.py:115
|
||||||
#: registration/forms.py:137 registration/forms.py:159
|
#: registration/forms.py:137 registration/forms.py:159
|
||||||
#: registration/forms.py:181 registration/forms.py:226
|
#: registration/forms.py:181 registration/forms.py:226
|
||||||
msgid "The uploaded file must be a PDF, PNG of JPEG file."
|
msgid "The uploaded file must be a PDF, PNG of JPEG file."
|
||||||
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
|
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
|
||||||
|
|
||||||
#: participation/forms.py:101
|
#: participation/forms.py:104
|
||||||
msgid "I engage myself to participate to the whole TFJM²."
|
msgid "I engage myself to participate to the whole TFJM²."
|
||||||
msgstr "Je m'engage à participer à l'intégralité du TFJM²."
|
msgstr "Je m'engage à participer à l'intégralité du TFJM²."
|
||||||
|
|
||||||
#: participation/forms.py:116
|
#: participation/forms.py:119
|
||||||
msgid "Message to address to the team:"
|
msgid "Message to address to the team:"
|
||||||
msgstr "Message à adresser à l'équipe :"
|
msgstr "Message à adresser à l'équipe :"
|
||||||
|
|
||||||
#: participation/forms.py:151
|
#: participation/forms.py:154
|
||||||
msgid "The uploaded file size must be under 5 Mo."
|
msgid "The uploaded file size must be under 5 Mo."
|
||||||
msgstr "Le fichier envoyé doit peser moins de 5 Mo."
|
msgstr "Le fichier envoyé doit peser moins de 5 Mo."
|
||||||
|
|
||||||
#: participation/forms.py:153 participation/forms.py:291
|
#: participation/forms.py:156 participation/forms.py:336
|
||||||
msgid "The uploaded file must be a PDF file."
|
msgid "The uploaded file must be a PDF file."
|
||||||
msgstr "Le fichier envoyé doit être au format PDF."
|
msgstr "Le fichier envoyé doit être au format PDF."
|
||||||
|
|
||||||
#: participation/forms.py:157
|
#: participation/forms.py:160
|
||||||
msgid "The PDF file must not have more than 30 pages."
|
msgid "The PDF file must not have more than 30 pages."
|
||||||
msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages."
|
msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages."
|
||||||
|
|
||||||
#: participation/forms.py:203
|
#: participation/forms.py:209
|
||||||
|
msgid "Add new jury"
|
||||||
|
msgstr "Ajouter un⋅e nouvelleau juré⋅e"
|
||||||
|
|
||||||
|
#: participation/forms.py:224
|
||||||
|
#: participation/templates/participation/pool_detail.html:77
|
||||||
|
#: participation/templates/participation/tournament_detail.html:111
|
||||||
|
msgid "Add"
|
||||||
|
msgstr "Ajouter"
|
||||||
|
|
||||||
|
#: participation/forms.py:237 registration/forms.py:35 registration/forms.py:60
|
||||||
|
msgid "This email address is already used."
|
||||||
|
msgstr "Cette adresse e-mail est déjà utilisée."
|
||||||
|
|
||||||
|
#: participation/forms.py:248
|
||||||
msgid "CSV file:"
|
msgid "CSV file:"
|
||||||
msgstr "Tableur au format CSV :"
|
msgstr "Tableur au format CSV :"
|
||||||
|
|
||||||
#: participation/forms.py:220
|
#: participation/forms.py:265
|
||||||
msgid ""
|
msgid ""
|
||||||
"This file contains non-UTF-8 content. Please send your sheet as a CSV file."
|
"This file contains non-UTF-8 content. Please send your sheet as a CSV file."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Ce fichier contient des éléments non-UTF-8. Merci d'envoyer votre tableur au "
|
"Ce fichier contient des éléments non-UTF-8. Merci d'envoyer votre tableur au "
|
||||||
"format CSV."
|
"format CSV."
|
||||||
|
|
||||||
#: participation/forms.py:247
|
#: participation/forms.py:292
|
||||||
msgid "The following note is higher of the maximum expected value:"
|
msgid "The following note is higher of the maximum expected value:"
|
||||||
msgstr "La note suivante est supérieure au maximum attendu :"
|
msgstr "La note suivante est supérieure au maximum attendu :"
|
||||||
|
|
||||||
#: participation/forms.py:255
|
#: participation/forms.py:300
|
||||||
msgid "The following user was not found:"
|
msgid "The following user was not found:"
|
||||||
msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :"
|
msgstr "L'utilisateur⋅rice suivant n'a pas été trouvé :"
|
||||||
|
|
||||||
#: participation/forms.py:272
|
#: participation/forms.py:317
|
||||||
msgid "The defender, the opponent and the reporter must be different."
|
msgid "The defender, the opponent and the reporter must be different."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Læ défenseur⋅se, l'opposant⋅e et læ rapporteur⋅e doivent être différent⋅es."
|
"Læ défenseur⋅se, l'opposant⋅e et læ rapporteur⋅e doivent être différent⋅es."
|
||||||
|
|
||||||
#: participation/forms.py:276
|
#: participation/forms.py:321
|
||||||
msgid "This defender did not work on this problem."
|
msgid "This defender did not work on this problem."
|
||||||
msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème."
|
msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème."
|
||||||
|
|
||||||
@ -853,12 +867,12 @@ msgstr ""
|
|||||||
|
|
||||||
#: participation/templates/participation/create_team.html:11
|
#: participation/templates/participation/create_team.html:11
|
||||||
#: participation/templates/participation/tournament_form.html:14
|
#: participation/templates/participation/tournament_form.html:14
|
||||||
#: tfjm/templates/base.html:248
|
#: tfjm/templates/base.html:255
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "Créer"
|
msgstr "Créer"
|
||||||
|
|
||||||
#: participation/templates/participation/join_team.html:11
|
#: participation/templates/participation/join_team.html:11
|
||||||
#: tfjm/templates/base.html:243
|
#: tfjm/templates/base.html:250
|
||||||
msgid "Join"
|
msgid "Join"
|
||||||
msgstr "Rejoindre"
|
msgstr "Rejoindre"
|
||||||
|
|
||||||
@ -866,9 +880,10 @@ msgstr "Rejoindre"
|
|||||||
#: participation/templates/participation/passage_detail.html:46
|
#: participation/templates/participation/passage_detail.html:46
|
||||||
#: participation/templates/participation/passage_detail.html:102
|
#: participation/templates/participation/passage_detail.html:102
|
||||||
#: participation/templates/participation/passage_detail.html:108
|
#: participation/templates/participation/passage_detail.html:108
|
||||||
#: participation/templates/participation/pool_detail.html:58
|
#: participation/templates/participation/pool_add_jurys.html:35
|
||||||
#: participation/templates/participation/pool_detail.html:77
|
#: participation/templates/participation/pool_detail.html:63
|
||||||
#: participation/templates/participation/pool_detail.html:82
|
#: participation/templates/participation/pool_detail.html:82
|
||||||
|
#: participation/templates/participation/pool_detail.html:87
|
||||||
#: participation/templates/participation/team_detail.html:126
|
#: participation/templates/participation/team_detail.html:126
|
||||||
#: participation/templates/participation/team_detail.html:190
|
#: participation/templates/participation/team_detail.html:190
|
||||||
#: participation/templates/participation/tournament_form.html:12
|
#: participation/templates/participation/tournament_form.html:12
|
||||||
@ -932,7 +947,7 @@ msgstr "Envoyer une solution"
|
|||||||
|
|
||||||
#: participation/templates/participation/participation_detail.html:59
|
#: participation/templates/participation/participation_detail.html:59
|
||||||
#: participation/templates/participation/passage_detail.html:114
|
#: participation/templates/participation/passage_detail.html:114
|
||||||
#: participation/templates/participation/pool_detail.html:87
|
#: participation/templates/participation/pool_detail.html:92
|
||||||
#: participation/templates/participation/team_detail.html:185
|
#: participation/templates/participation/team_detail.html:185
|
||||||
#: participation/templates/participation/upload_motivation_letter.html:13
|
#: participation/templates/participation/upload_motivation_letter.html:13
|
||||||
#: participation/templates/participation/upload_notes.html:24
|
#: participation/templates/participation/upload_notes.html:24
|
||||||
@ -1037,6 +1052,37 @@ msgstr "Points de læ rapporteur⋅e :"
|
|||||||
msgid "Update passage"
|
msgid "Update passage"
|
||||||
msgstr "Modifier le passage"
|
msgstr "Modifier le passage"
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:9
|
||||||
|
msgid "You can here register juries for the pool."
|
||||||
|
msgstr "Vous pouvez inscrire ici les juré⋅es de la poule."
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:10
|
||||||
|
msgid ""
|
||||||
|
"Be careful: this form register new users. To add existing users into the "
|
||||||
|
"jury, please use this form:"
|
||||||
|
msgstr ""
|
||||||
|
"Attention : ce formulaire inscrit des nouvelleaux utilisateur⋅rices. "
|
||||||
|
"Pour ajouter des utilisateur⋅rices au jury, utilisez ce formulaire :"
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:11
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:34
|
||||||
|
#: participation/templates/participation/pool_detail.html:81
|
||||||
|
#: participation/templates/participation/pool_form.html:11
|
||||||
|
msgid "Update pool"
|
||||||
|
msgstr "Modifier la poule"
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:14
|
||||||
|
msgid "For now, the registered juries for the tournament are:"
|
||||||
|
msgstr "Pour l'instant, les juré⋅es inscrit⋅es au tournoi sont :"
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:19
|
||||||
|
msgid "There is no jury yet."
|
||||||
|
msgstr "Il n'y a pas de juré⋅e pour le moment."
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_add_jurys.html:30
|
||||||
|
msgid "Back to pool detail"
|
||||||
|
msgstr "Retour aux détails de la poule"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:15
|
#: participation/templates/participation/pool_detail.html:15
|
||||||
msgid "Round:"
|
msgid "Round:"
|
||||||
msgstr "Tour :"
|
msgstr "Tour :"
|
||||||
@ -1051,50 +1097,44 @@ msgstr "Équipes :"
|
|||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:28
|
#: participation/templates/participation/pool_detail.html:28
|
||||||
msgid "Juries:"
|
msgid "Juries:"
|
||||||
msgstr "Jurys :"
|
msgstr "Juré⋅es :"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:31
|
#: participation/templates/participation/pool_detail.html:32
|
||||||
|
msgid "Add jurys"
|
||||||
|
msgstr "Ajouter des juré⋅es"
|
||||||
|
|
||||||
|
#: participation/templates/participation/pool_detail.html:36
|
||||||
msgid "Defended solutions:"
|
msgid "Defended solutions:"
|
||||||
msgstr "Solutions défendues :"
|
msgstr "Solutions défendues :"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:38
|
#: participation/templates/participation/pool_detail.html:43
|
||||||
msgid "BigBlueButton link:"
|
msgid "BigBlueButton link:"
|
||||||
msgstr "Lien BigBlueButton :"
|
msgstr "Lien BigBlueButton :"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:44
|
#: participation/templates/participation/pool_detail.html:49
|
||||||
#: participation/templates/participation/tournament_detail.html:97
|
#: participation/templates/participation/tournament_detail.html:97
|
||||||
msgid "Ranking"
|
msgid "Ranking"
|
||||||
msgstr "Classement"
|
msgstr "Classement"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:57
|
#: participation/templates/participation/pool_detail.html:62
|
||||||
#: participation/templates/participation/pool_detail.html:71
|
#: participation/templates/participation/pool_detail.html:76
|
||||||
msgid "Add passage"
|
msgid "Add passage"
|
||||||
msgstr "Ajouter un passage"
|
msgstr "Ajouter un passage"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:59
|
#: participation/templates/participation/pool_detail.html:64
|
||||||
#: participation/templates/participation/pool_detail.html:81
|
#: participation/templates/participation/pool_detail.html:86
|
||||||
msgid "Update teams"
|
msgid "Update teams"
|
||||||
msgstr "Modifier les équipes"
|
msgstr "Modifier les équipes"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:60
|
#: participation/templates/participation/pool_detail.html:65
|
||||||
msgid "Upload notes from a CSV file"
|
msgid "Upload notes from a CSV file"
|
||||||
msgstr "Soumettre les notes à partir d'un fichier CSV"
|
msgstr "Soumettre les notes à partir d'un fichier CSV"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:67
|
#: participation/templates/participation/pool_detail.html:72
|
||||||
msgid "Passages"
|
msgid "Passages"
|
||||||
msgstr "Passages"
|
msgstr "Passages"
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:72
|
#: participation/templates/participation/pool_detail.html:91
|
||||||
#: participation/templates/participation/tournament_detail.html:111
|
|
||||||
msgid "Add"
|
|
||||||
msgstr "Ajouter"
|
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:76
|
|
||||||
#: participation/templates/participation/pool_form.html:11
|
|
||||||
msgid "Update pool"
|
|
||||||
msgstr "Modifier la poule"
|
|
||||||
|
|
||||||
#: participation/templates/participation/pool_detail.html:86
|
|
||||||
msgid "Upload notes"
|
msgid "Upload notes"
|
||||||
msgstr "Envoyer les notes"
|
msgstr "Envoyer les notes"
|
||||||
|
|
||||||
@ -1228,7 +1268,7 @@ msgid "Invalidate"
|
|||||||
msgstr "Invalider"
|
msgstr "Invalider"
|
||||||
|
|
||||||
#: participation/templates/participation/team_detail.html:184
|
#: participation/templates/participation/team_detail.html:184
|
||||||
#: participation/views.py:333
|
#: participation/views.py:335
|
||||||
msgid "Upload motivation letter"
|
msgid "Upload motivation letter"
|
||||||
msgstr "Envoyer la lettre de motivation"
|
msgstr "Envoyer la lettre de motivation"
|
||||||
|
|
||||||
@ -1237,7 +1277,7 @@ msgid "Update team"
|
|||||||
msgstr "Modifier l'équipe"
|
msgstr "Modifier l'équipe"
|
||||||
|
|
||||||
#: participation/templates/participation/team_detail.html:194
|
#: participation/templates/participation/team_detail.html:194
|
||||||
#: participation/views.py:442
|
#: participation/views.py:444
|
||||||
msgid "Leave team"
|
msgid "Leave team"
|
||||||
msgstr "Quitter l'équipe"
|
msgstr "Quitter l'équipe"
|
||||||
|
|
||||||
@ -1246,7 +1286,7 @@ msgid "Are you sure that you want to leave this team?"
|
|||||||
msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?"
|
msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?"
|
||||||
|
|
||||||
#: participation/templates/participation/team_list.html:6
|
#: participation/templates/participation/team_list.html:6
|
||||||
#: tfjm/templates/base.html:236
|
#: tfjm/templates/base.html:243
|
||||||
msgid "All teams"
|
msgid "All teams"
|
||||||
msgstr "Toutes les équipes"
|
msgstr "Toutes les équipes"
|
||||||
|
|
||||||
@ -1297,7 +1337,7 @@ msgstr "Pour contacter les organisateur⋅rices"
|
|||||||
|
|
||||||
#: participation/templates/participation/tournament_detail.html:54
|
#: participation/templates/participation/tournament_detail.html:54
|
||||||
msgid "To contact juries"
|
msgid "To contact juries"
|
||||||
msgstr "Pour contacter les jurys"
|
msgstr "Pour contacter les juré⋅es"
|
||||||
|
|
||||||
#: participation/templates/participation/tournament_detail.html:57
|
#: participation/templates/participation/tournament_detail.html:57
|
||||||
msgid "To contact valid teams"
|
msgid "To contact valid teams"
|
||||||
@ -1329,7 +1369,7 @@ msgid "Add pool"
|
|||||||
msgstr "Ajouter une poule"
|
msgstr "Ajouter une poule"
|
||||||
|
|
||||||
#: participation/templates/participation/tournament_list.html:6
|
#: participation/templates/participation/tournament_list.html:6
|
||||||
#: tfjm/templates/base.html:232
|
#: tfjm/templates/base.html:239
|
||||||
msgid "All tournaments"
|
msgid "All tournaments"
|
||||||
msgstr "Tous les tournois"
|
msgstr "Tous les tournois"
|
||||||
|
|
||||||
@ -1349,49 +1389,49 @@ msgstr "Télécharger la fiche de notation vierge"
|
|||||||
msgid "Templates:"
|
msgid "Templates:"
|
||||||
msgstr "Modèles :"
|
msgstr "Modèles :"
|
||||||
|
|
||||||
#: participation/views.py:44 tfjm/templates/base.html:73
|
#: participation/views.py:46 tfjm/templates/base.html:73
|
||||||
#: tfjm/templates/base.html:247
|
#: tfjm/templates/base.html:254
|
||||||
msgid "Create team"
|
msgid "Create team"
|
||||||
msgstr "Créer une équipe"
|
msgstr "Créer une équipe"
|
||||||
|
|
||||||
#: participation/views.py:53 participation/views.py:98
|
#: participation/views.py:55 participation/views.py:100
|
||||||
msgid "You don't participate, so you can't create a team."
|
msgid "You don't participate, so you can't create a team."
|
||||||
msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
|
msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
|
||||||
|
|
||||||
#: participation/views.py:55 participation/views.py:100
|
#: participation/views.py:57 participation/views.py:102
|
||||||
msgid "You are already in a team."
|
msgid "You are already in a team."
|
||||||
msgstr "Vous êtes déjà dans une équipe."
|
msgstr "Vous êtes déjà dans une équipe."
|
||||||
|
|
||||||
#: participation/views.py:89 tfjm/templates/base.html:78
|
#: participation/views.py:91 tfjm/templates/base.html:78
|
||||||
#: tfjm/templates/base.html:242
|
#: tfjm/templates/base.html:249
|
||||||
msgid "Join team"
|
msgid "Join team"
|
||||||
msgstr "Rejoindre une équipe"
|
msgstr "Rejoindre une équipe"
|
||||||
|
|
||||||
#: participation/views.py:151 participation/views.py:448
|
#: participation/views.py:153 participation/views.py:450
|
||||||
#: participation/views.py:482
|
#: participation/views.py:484
|
||||||
msgid "You are not in a team."
|
msgid "You are not in a team."
|
||||||
msgstr "Vous n'êtes pas dans une équipe."
|
msgstr "Vous n'êtes pas dans une équipe."
|
||||||
|
|
||||||
#: participation/views.py:152 participation/views.py:483
|
#: participation/views.py:154 participation/views.py:485
|
||||||
msgid "You don't participate, so you don't have any team."
|
msgid "You don't participate, so you don't have any team."
|
||||||
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
|
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
|
||||||
|
|
||||||
#: participation/views.py:178
|
#: participation/views.py:180
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Detail of team {trigram}"
|
msgid "Detail of team {trigram}"
|
||||||
msgstr "Détails de l'équipe {trigram}"
|
msgstr "Détails de l'équipe {trigram}"
|
||||||
|
|
||||||
#: participation/views.py:215
|
#: participation/views.py:217
|
||||||
msgid "You don't participate, so you can't request the validation of the team."
|
msgid "You don't participate, so you can't request the validation of the team."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous ne participez pas, vous ne pouvez pas demander la validation de "
|
"Vous ne participez pas, vous ne pouvez pas demander la validation de "
|
||||||
"l'équipe."
|
"l'équipe."
|
||||||
|
|
||||||
#: participation/views.py:218
|
#: participation/views.py:220
|
||||||
msgid "The validation of the team is already done or pending."
|
msgid "The validation of the team is already done or pending."
|
||||||
msgstr "La validation de l'équipe est déjà faite ou en cours."
|
msgstr "La validation de l'équipe est déjà faite ou en cours."
|
||||||
|
|
||||||
#: participation/views.py:221
|
#: participation/views.py:223
|
||||||
msgid ""
|
msgid ""
|
||||||
"The team can't be validated: missing email address confirmations, "
|
"The team can't be validated: missing email address confirmations, "
|
||||||
"authorizations, people, motivation letter or the tournament is not set."
|
"authorizations, people, motivation letter or the tournament is not set."
|
||||||
@ -1400,79 +1440,93 @@ msgstr ""
|
|||||||
"d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre "
|
"d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre "
|
||||||
"de motivation, soit le tournoi n'a pas été choisi."
|
"de motivation, soit le tournoi n'a pas été choisi."
|
||||||
|
|
||||||
#: participation/views.py:243
|
#: participation/views.py:245
|
||||||
msgid "You are not an organizer of the tournament."
|
msgid "You are not an organizer of the tournament."
|
||||||
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
|
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
|
||||||
|
|
||||||
#: participation/views.py:246
|
#: participation/views.py:248
|
||||||
msgid "This team has no pending validation."
|
msgid "This team has no pending validation."
|
||||||
msgstr "L'équipe n'a pas de validation en attente."
|
msgstr "L'équipe n'a pas de validation en attente."
|
||||||
|
|
||||||
#: participation/views.py:276
|
#: participation/views.py:278
|
||||||
msgid "You must specify if you validate the registration or not."
|
msgid "You must specify if you validate the registration or not."
|
||||||
msgstr "Vous devez spécifier si vous validez l'inscription ou non."
|
msgstr "Vous devez spécifier si vous validez l'inscription ou non."
|
||||||
|
|
||||||
#: participation/views.py:311
|
#: participation/views.py:313
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Update team {trigram}"
|
msgid "Update team {trigram}"
|
||||||
msgstr "Mise à jour de l'équipe {trigram}"
|
msgstr "Mise à jour de l'équipe {trigram}"
|
||||||
|
|
||||||
#: participation/views.py:372 participation/views.py:428
|
#: participation/views.py:374 participation/views.py:430
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Motivation letter of {team}.{ext}"
|
msgid "Motivation letter of {team}.{ext}"
|
||||||
msgstr "Lettre de motivation de {team}.{ext}"
|
msgstr "Lettre de motivation de {team}.{ext}"
|
||||||
|
|
||||||
#: participation/views.py:403
|
#: participation/views.py:405
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Photo authorization of {participant}.{ext}"
|
msgid "Photo authorization of {participant}.{ext}"
|
||||||
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
|
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
|
||||||
|
|
||||||
#: participation/views.py:409
|
#: participation/views.py:411
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Parental authorization of {participant}.{ext}"
|
msgid "Parental authorization of {participant}.{ext}"
|
||||||
msgstr "Autorisation parentale de {participant}.{ext}"
|
msgstr "Autorisation parentale de {participant}.{ext}"
|
||||||
|
|
||||||
#: participation/views.py:416
|
#: participation/views.py:418
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Health sheet of {participant}.{ext}"
|
msgid "Health sheet of {participant}.{ext}"
|
||||||
msgstr "Fiche sanitaire de {participant}.{ext}"
|
msgstr "Fiche sanitaire de {participant}.{ext}"
|
||||||
|
|
||||||
#: participation/views.py:422
|
#: participation/views.py:424
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Vaccine sheet of {participant}.{ext}"
|
msgid "Vaccine sheet of {participant}.{ext}"
|
||||||
msgstr "Carnet de vaccination de {participant}.{ext}"
|
msgstr "Carnet de vaccination de {participant}.{ext}"
|
||||||
|
|
||||||
#: participation/views.py:432
|
#: participation/views.py:434
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Photo authorizations of team {trigram}.zip"
|
msgid "Photo authorizations of team {trigram}.zip"
|
||||||
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
|
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
|
||||||
|
|
||||||
#: participation/views.py:450
|
#: participation/views.py:452
|
||||||
msgid "The team is already validated or the validation is pending."
|
msgid "The team is already validated or the validation is pending."
|
||||||
msgstr "La validation de l'équipe est déjà faite ou en cours."
|
msgstr "La validation de l'équipe est déjà faite ou en cours."
|
||||||
|
|
||||||
#: participation/views.py:497
|
#: participation/views.py:499
|
||||||
msgid "The team is not validated yet."
|
msgid "The team is not validated yet."
|
||||||
msgstr "L'équipe n'est pas encore validée."
|
msgstr "L'équipe n'est pas encore validée."
|
||||||
|
|
||||||
#: participation/views.py:511
|
#: participation/views.py:513
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Participation of team {trigram}"
|
msgid "Participation of team {trigram}"
|
||||||
msgstr "Participation de l'équipe {trigram}"
|
msgstr "Participation de l'équipe {trigram}"
|
||||||
|
|
||||||
#: participation/views.py:637
|
#: participation/views.py:639
|
||||||
msgid "You can't upload a solution after the deadline."
|
msgid "You can't upload a solution after the deadline."
|
||||||
msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
|
msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
|
||||||
|
|
||||||
#: participation/views.py:740
|
#: participation/views.py:727
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Jurys of {pool}"
|
||||||
|
msgstr "Juré⋅es de la {pool}"
|
||||||
|
|
||||||
|
#: participation/views.py:749
|
||||||
|
msgid "New TFJM² jury account"
|
||||||
|
msgstr "Nouveau compte de juré⋅e pour le TFJM²"
|
||||||
|
|
||||||
|
#: participation/views.py:761
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "The jury {name} has been successfully added!"
|
||||||
|
msgstr "Læ juré⋅e {name} a été ajouté⋅e avec succès !"
|
||||||
|
|
||||||
|
#: participation/views.py:796
|
||||||
msgid "The following user is not registered as a jury:"
|
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 :"
|
msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :"
|
||||||
|
|
||||||
#: participation/views.py:748
|
#: participation/views.py:804
|
||||||
msgid "Notes were successfully uploaded."
|
msgid "Notes were successfully uploaded."
|
||||||
msgstr "Les notes ont bien été envoyées."
|
msgstr "Les notes ont bien été envoyées."
|
||||||
|
|
||||||
#: participation/views.py:860
|
#: participation/views.py:916
|
||||||
msgid "You can't upload a synthesis after the deadline."
|
msgid "You can't upload a synthesis after the deadline."
|
||||||
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."
|
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."
|
||||||
|
|
||||||
@ -1502,10 +1556,6 @@ msgstr "participant⋅e"
|
|||||||
msgid "coach"
|
msgid "coach"
|
||||||
msgstr "encadrant⋅e"
|
msgstr "encadrant⋅e"
|
||||||
|
|
||||||
#: registration/forms.py:35 registration/forms.py:60
|
|
||||||
msgid "This email address is already used."
|
|
||||||
msgstr "Cette adresse e-mail est déjà utilisée."
|
|
||||||
|
|
||||||
#: registration/forms.py:218
|
#: registration/forms.py:218
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "En attente"
|
msgstr "En attente"
|
||||||
@ -1850,8 +1900,8 @@ msgid "Your password has been set. You may go ahead and log in now."
|
|||||||
msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter."
|
msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter."
|
||||||
|
|
||||||
#: registration/templates/registration/password_reset_complete.html:10
|
#: registration/templates/registration/password_reset_complete.html:10
|
||||||
#: tfjm/templates/base.html:133 tfjm/templates/base.html:252
|
#: tfjm/templates/base.html:133 tfjm/templates/base.html:259
|
||||||
#: tfjm/templates/base.html:253 tfjm/templates/registration/login.html:7
|
#: tfjm/templates/base.html:260 tfjm/templates/registration/login.html:7
|
||||||
#: tfjm/templates/registration/login.html:8
|
#: tfjm/templates/registration/login.html:8
|
||||||
#: tfjm/templates/registration/login.html:25
|
#: tfjm/templates/registration/login.html:25
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
@ -2305,15 +2355,15 @@ msgstr ""
|
|||||||
"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur <a "
|
"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur <a "
|
||||||
"href=\"%(send_email_url)s\">ce lien</a>."
|
"href=\"%(send_email_url)s\">ce lien</a>."
|
||||||
|
|
||||||
#: tfjm/templates/base.html:190
|
#: tfjm/templates/base.html:197
|
||||||
msgid "Contact us"
|
msgid "Contact us"
|
||||||
msgstr "Nous contacter"
|
msgstr "Nous contacter"
|
||||||
|
|
||||||
#: tfjm/templates/base.html:217
|
#: tfjm/templates/base.html:224
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "À propos"
|
msgstr "À propos"
|
||||||
|
|
||||||
#: tfjm/templates/base.html:239
|
#: tfjm/templates/base.html:246
|
||||||
msgid "Search results"
|
msgid "Search results"
|
||||||
msgstr "Résultats de la recherche"
|
msgstr "Résultats de la recherche"
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@ from io import StringIO
|
|||||||
import re
|
import re
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
|
from crispy_forms.bootstrap import InlineField
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
|
from crispy_forms.layout import Div, Fieldset, Submit
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -198,6 +201,48 @@ class PoolTeamsForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AddJuryForm(forms.ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_class = 'form-inline'
|
||||||
|
self.helper.layout = Fieldset(
|
||||||
|
_("Add new jury"),
|
||||||
|
Div(
|
||||||
|
Div(
|
||||||
|
InlineField('first_name', autofocus="autofocus"),
|
||||||
|
css_class='col-xl-3',
|
||||||
|
),
|
||||||
|
Div(
|
||||||
|
InlineField('last_name'),
|
||||||
|
css_class='col-xl-3',
|
||||||
|
),
|
||||||
|
Div(
|
||||||
|
InlineField('email'),
|
||||||
|
css_class='col-xl-5',
|
||||||
|
),
|
||||||
|
Div(
|
||||||
|
Submit('submit', _("Add")),
|
||||||
|
css_class='col-xl-1',
|
||||||
|
),
|
||||||
|
css_class='row',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_email(self):
|
||||||
|
"""
|
||||||
|
Ensure that the email address is unique.
|
||||||
|
"""
|
||||||
|
email = self.data["email"]
|
||||||
|
if User.objects.filter(email=email).exists():
|
||||||
|
self.add_error("email", _("This email address is already used."))
|
||||||
|
return email
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('first_name', 'last_name', 'email',)
|
||||||
|
|
||||||
|
|
||||||
class UploadNotesForm(forms.Form):
|
class UploadNotesForm(forms.Form):
|
||||||
file = forms.FileField(
|
file = forms.FileField(
|
||||||
label=_("CSV file:"),
|
label=_("CSV file:"),
|
||||||
|
@ -285,7 +285,6 @@ class Tournament(models.Model):
|
|||||||
fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3]
|
fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3]
|
||||||
return '+'.join(map(str, sorted(fmt, reverse=True)))
|
return '+'.join(map(str, sorted(fmt, reverse=True)))
|
||||||
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy("participation:tournament_detail", args=(self.pk,))
|
return reverse_lazy("participation:tournament_detail", args=(self.pk,))
|
||||||
|
|
||||||
|
47
participation/templates/participation/pool_add_jurys.html
Normal file
47
participation/templates/participation/pool_add_jurys.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<p>
|
||||||
|
{% trans "You can here register juries for the pool." %}
|
||||||
|
{% trans "Be careful: this form register new users. To add existing users into the jury, please use this form:" %}
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#updatePoolModal">{% trans "Update pool" %}</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% trans "For now, the registered juries for the tournament are:" %}
|
||||||
|
<ul>
|
||||||
|
{% for jury in pool.juries.all %}
|
||||||
|
<li>{{ jury.user.first_name }} {{ jury.user.last_name }} (<a class="alert-link" href="mailto:{{ jury.user.email }}">{{ jury.user.email }}</a>)</li>
|
||||||
|
{% empty %}
|
||||||
|
<li><i>{% trans "There is no jury yet." %}</i></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% crispy form %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row text-center">
|
||||||
|
<a href="{% url 'participation:pool_detail' pk=pool.pk %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left"></i> {% trans "Back to pool detail" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% trans "Update pool" as modal_title %}
|
||||||
|
{% trans "Update" as modal_button %}
|
||||||
|
{% url "participation:pool_update" pk=pool.pk as modal_action %}
|
||||||
|
{% include "base_modal.html" with modal_id="updatePool" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initModal("updatePool", "{% url "participation:pool_update" pk=pool.pk %}")
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -26,7 +26,12 @@
|
|||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Juries:" %}</dt>
|
<dt class="col-sm-3">{% trans "Juries:" %}</dt>
|
||||||
<dd class="col-sm-9">{{ pool.juries.all|join:", " }}</dd>
|
<dd class="col-sm-9">
|
||||||
|
{{ pool.juries.all|join:", " }}
|
||||||
|
<a class="badge rounded-pill text-bg-info" href="{% url 'participation:pool_add_jurys' pk=pool.pk %}">
|
||||||
|
<i class="fas fa-plus"></i> {% trans "Add jurys" %}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
|
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9">
|
||||||
|
@ -5,8 +5,8 @@ from django.urls import path
|
|||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTeamDetailView, NoteUpdateView, \
|
from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTeamDetailView, NoteUpdateView, \
|
||||||
ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \
|
ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolAddJurysView, PoolCreateView,\
|
||||||
PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, SolutionUploadView, SynthesisUploadView,\
|
PoolDetailView, PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, SolutionUploadView, SynthesisUploadView,\
|
||||||
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
||||||
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
||||||
TournamentListView, TournamentUpdateView
|
TournamentListView, TournamentUpdateView
|
||||||
@ -37,6 +37,7 @@ urlpatterns = [
|
|||||||
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
||||||
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
|
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
|
||||||
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
|
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
|
||||||
|
path("pools/<int:pk>/add-jurys/", PoolAddJurysView.as_view(), name="pool_add_jurys"),
|
||||||
path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"),
|
path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"),
|
||||||
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),
|
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),
|
||||||
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import os
|
import os
|
||||||
@ -17,18 +18,19 @@ from django.shortcuts import redirect
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View
|
from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View
|
||||||
from django.views.generic.edit import FormMixin, ProcessFormView
|
from django.views.generic.edit import FormMixin, ProcessFormView
|
||||||
from django_tables2 import SingleTableView
|
from django_tables2 import SingleTableView
|
||||||
from magic import Magic
|
from magic import Magic
|
||||||
from registration.models import StudentRegistration
|
from registration.models import StudentRegistration, VolunteerRegistration
|
||||||
from tfjm.lists import get_sympa_client
|
from tfjm.lists import get_sympa_client
|
||||||
from tfjm.matrix import Matrix
|
from tfjm.matrix import Matrix
|
||||||
from tfjm.views import AdminMixin, VolunteerMixin
|
from tfjm.views import AdminMixin, VolunteerMixin
|
||||||
|
|
||||||
from .forms import JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, PoolForm, \
|
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
|
||||||
PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
|
PoolForm, PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
|
||||||
UploadNotesForm, ValidateParticipationForm
|
UploadNotesForm, ValidateParticipationForm
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||||
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
||||||
@ -715,6 +717,70 @@ class PoolUpdateTeamsView(VolunteerMixin, UpdateView):
|
|||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
|
|
||||||
|
class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
|
||||||
|
"""
|
||||||
|
This view lets organizers set jurys for a pool, without multiplying clicks.
|
||||||
|
"""
|
||||||
|
model = Pool
|
||||||
|
form_class = AddJuryForm
|
||||||
|
template_name = 'participation/pool_add_jurys.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = _("Jurys of {pool}").format(pool=self.object)
|
||||||
|
return context
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = self.get_object()
|
||||||
|
|
||||||
|
# Save the user object first
|
||||||
|
form.save()
|
||||||
|
user = form.instance
|
||||||
|
# Create associated registration object to the new user
|
||||||
|
reg = VolunteerRegistration.objects.create(
|
||||||
|
user=user,
|
||||||
|
professional_activity="Juré⋅e du tournoi " + self.object.tournament.name,
|
||||||
|
)
|
||||||
|
# Add the user in the jury
|
||||||
|
self.object.juries.add(reg)
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
reg.send_email_validation_link()
|
||||||
|
|
||||||
|
# Generate new password for the user
|
||||||
|
password = get_random_string(16)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# Send welcome mail
|
||||||
|
subject = "[TFJM²] " + str(_("New TFJM² jury account"))
|
||||||
|
site = Site.objects.first()
|
||||||
|
message = render_to_string('registration/mails/add_organizer.txt', dict(user=user,
|
||||||
|
inviter=self.request.user,
|
||||||
|
password=password,
|
||||||
|
domain=site.domain))
|
||||||
|
html = render_to_string('registration/mails/add_organizer.html', dict(user=user,
|
||||||
|
inviter=self.request.user,
|
||||||
|
password=password,
|
||||||
|
domain=site.domain))
|
||||||
|
user.email_user(subject, message, html_message=html)
|
||||||
|
|
||||||
|
# Add notification
|
||||||
|
messages.success(self.request, _("The jury {name} has been successfully added!")
|
||||||
|
.format(name=f"{user.first_name} {user.last_name}"))
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
# This is useful since we have a FormView + a DetailView
|
||||||
|
self.object = self.get_object()
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('participation:pool_add_jurys', args=(self.kwargs['pk'],))
|
||||||
|
|
||||||
|
|
||||||
class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
||||||
model = Pool
|
model = Pool
|
||||||
form_class = UploadNotesForm
|
form_class = UploadNotesForm
|
||||||
|
@ -6,7 +6,7 @@ from django.contrib.admin import ModelAdmin
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
||||||
|
|
||||||
from .models import CoachRegistration, Payment, ParticipantRegistration, Registration, \
|
from .models import CoachRegistration, ParticipantRegistration, Payment, Registration, \
|
||||||
StudentRegistration, VolunteerRegistration
|
StudentRegistration, VolunteerRegistration
|
||||||
|
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ class RegistrationAdmin(PolymorphicParentModelAdmin):
|
|||||||
def last_name(self, record):
|
def last_name(self, record):
|
||||||
return record.user.last_name
|
return record.user.last_name
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ParticipantRegistration)
|
@admin.register(ParticipantRegistration)
|
||||||
class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin):
|
class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin):
|
||||||
list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed',)
|
list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed',)
|
||||||
|
@ -191,7 +191,6 @@ class ParticipantRegistration(Registration):
|
|||||||
def form_class(self): # pragma: no cover
|
def form_class(self): # pragma: no cover
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("participant registration")
|
verbose_name = _("participant registration")
|
||||||
verbose_name_plural = _("participant registrations")
|
verbose_name_plural = _("participant registrations")
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
||||||
<a href="https://{{ domain }}/">https://{{ domain }}/</a>. Vous disposez d'un compte d'organisateur.
|
<a href="https://{{ domain }}/">https://{{ domain }}/</a>. Vous disposez d'un compte de bénévole.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Bonjour {{ user.registration }},
|
Bonjour {{ user.registration }},
|
||||||
|
|
||||||
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
||||||
https://{{ domain }}/. Vous disposez d'un compte d'organisateur.
|
https://{{ domain }}/. Vous disposez d'un compte de bénévole.
|
||||||
|
|
||||||
Un mot de passe aléatoire a été défini : {{ password }}.
|
Un mot de passe aléatoire a été défini : {{ password }}.
|
||||||
Par sécurité, merci de le changer dès votre connexion.
|
Par sécurité, merci de le changer dès votre connexion.
|
||||||
|
@ -21,7 +21,8 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
|
|||||||
|
|
||||||
django_asgi_app = get_asgi_application()
|
django_asgi_app = get_asgi_application()
|
||||||
|
|
||||||
import draw.routing
|
# useful since the import must be done after the application initialization
|
||||||
|
import draw.routing # noqa: E402, I202
|
||||||
|
|
||||||
application = ProtocolTypeRouter(
|
application = ProtocolTypeRouter(
|
||||||
{
|
{
|
||||||
|
@ -194,6 +194,7 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
|||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
|
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
|
||||||
CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
||||||
|
|
||||||
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap5.html'
|
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap5.html'
|
||||||
|
@ -160,7 +160,7 @@
|
|||||||
<main class="mb-auto flex-shrink-0">
|
<main class="mb-auto flex-shrink-0">
|
||||||
{% block fullcontent %}
|
{% block fullcontent %}
|
||||||
<div class="{% block containertype %}container{% endblock %} my-3">
|
<div class="{% block containertype %}container{% endblock %} my-3">
|
||||||
{% block contenttitle %}{% endblock %}
|
{% block contenttitle %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
{% if user.is_authenticated and not user.registration.email_confirmed %}
|
{% if user.is_authenticated and not user.registration.email_confirmed %}
|
||||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
@ -171,7 +171,14 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="messages"></div>
|
<div id="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
{{ message | safe }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Default content...</p>
|
<p>Default content...</p>
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.handlers.asgi import ASGIHandler
|
|
||||||
from django.core.handlers.wsgi import WSGIHandler
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views.defaults import bad_request, page_not_found, permission_denied, server_error
|
from django.views.defaults import bad_request, page_not_found, permission_denied, server_error
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from participation.views import MotivationLetterView
|
from participation.views import MotivationLetterView
|
||||||
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
|
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
|
||||||
ScholarshipView, SolutionView, SynthesisView, VaccineSheetView
|
ScholarshipView, SolutionView, SynthesisView, VaccineSheetView
|
||||||
|
2
tox.ini
2
tox.ini
@ -54,7 +54,7 @@ exclude =
|
|||||||
.cache,
|
.cache,
|
||||||
.eggs,
|
.eggs,
|
||||||
*migrations*
|
*migrations*
|
||||||
max-complexity = 10
|
max-complexity = 15
|
||||||
max-line-length = 160
|
max-line-length = 160
|
||||||
import-order-style = google
|
import-order-style = google
|
||||||
application-import-names = flake8
|
application-import-names = flake8
|
||||||
|
Loading…
x
Reference in New Issue
Block a user