Add comments and linting
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
2840a15fd5
commit
7e212d011e
|
@ -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
|
||||||
|
|
|
@ -8,7 +8,7 @@ from typing import Iterable
|
||||||
|
|
||||||
from crispy_forms.bootstrap import InlineField
|
from crispy_forms.bootstrap import InlineField
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit, Fieldset, Layout, Div
|
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
|
||||||
|
@ -200,6 +200,7 @@ class PoolTeamsForm(forms.ModelForm):
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AddJuryForm(forms.ModelForm):
|
class AddJuryForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -242,7 +243,6 @@ class AddJuryForm(forms.ModelForm):
|
||||||
fields = ('first_name', 'last_name', 'email',)
|
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,))
|
||||||
|
|
||||||
|
|
|
@ -718,6 +718,9 @@ class PoolUpdateTeamsView(VolunteerMixin, UpdateView):
|
||||||
|
|
||||||
|
|
||||||
class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
|
class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
|
||||||
|
"""
|
||||||
|
This view lets organizers set jurys for a pool, without multiplying clicks.
|
||||||
|
"""
|
||||||
model = Pool
|
model = Pool
|
||||||
form_class = AddJuryForm
|
form_class = AddJuryForm
|
||||||
template_name = 'participation/pool_add_jurys.html'
|
template_name = 'participation/pool_add_jurys.html'
|
||||||
|
@ -731,21 +734,26 @@ class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
|
|
||||||
|
# Save the user object first
|
||||||
form.save()
|
form.save()
|
||||||
user = form.instance
|
user = form.instance
|
||||||
|
# Create associated registration object to the new user
|
||||||
reg = VolunteerRegistration.objects.create(
|
reg = VolunteerRegistration.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
professional_activity="Juré⋅e du tournoi " + self.object.tournament.name,
|
professional_activity="Juré⋅e du tournoi " + self.object.tournament.name,
|
||||||
)
|
)
|
||||||
|
# Add the user in the jury
|
||||||
self.object.juries.add(reg)
|
self.object.juries.add(reg)
|
||||||
self.object.save()
|
self.object.save()
|
||||||
|
|
||||||
reg.send_email_validation_link()
|
reg.send_email_validation_link()
|
||||||
|
|
||||||
|
# Generate new password for the user
|
||||||
password = get_random_string(16)
|
password = get_random_string(16)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
# Send welcome mail
|
||||||
subject = "[TFJM²] " + str(_("New TFJM² jury account"))
|
subject = "[TFJM²] " + str(_("New TFJM² jury account"))
|
||||||
site = Site.objects.first()
|
site = Site.objects.first()
|
||||||
message = render_to_string('registration/mails/add_organizer.txt', dict(user=user,
|
message = render_to_string('registration/mails/add_organizer.txt', dict(user=user,
|
||||||
|
@ -758,12 +766,14 @@ class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
|
||||||
domain=site.domain))
|
domain=site.domain))
|
||||||
user.email_user(subject, message, html_message=html)
|
user.email_user(subject, message, html_message=html)
|
||||||
|
|
||||||
messages.success(self.request, _("The jury {name} has been successfully added!")\
|
# Add notification
|
||||||
|
messages.success(self.request, _("The jury {name} has been successfully added!")
|
||||||
.format(name=f"{user.first_name} {user.last_name}"))
|
.format(name=f"{user.first_name} {user.last_name}"))
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def form_invalid(self, form):
|
||||||
|
# This is useful since we have a FormView + a DetailView
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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(
|
||||||
{
|
{
|
||||||
|
|
|
@ -173,7 +173,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="messages">
|
<div id="messages">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade" role="alert">
|
<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>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
{{ message | safe }}
|
{{ message | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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…
Reference in New Issue