1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-02-27 05:06:28 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Emmy D'Anello
b38302449c
Don't manage pools of the second day with the dices of the first day since we consider the scores of the first day
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-05-11 17:28:05 +02:00
Emmy D'Anello
feee5069b1
Add notification when the draw of the final is resumed
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-05-11 17:15:50 +02:00
Emmy D'Anello
6b962a74b3
Auto-restart the draw socket on close
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-05-11 17:13:52 +02:00
Emmy D'Anello
0c80385958
Use a unique socket for the drawing system
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-05-11 17:07:53 +02:00
5 changed files with 1006 additions and 843 deletions

View File

@ -42,35 +42,27 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
We accept only if this is a user of a team of the associated tournament, or a volunteer
of the tournament.
"""
# Get the tournament from the URL
self.tournament_id = self.scope['url_route']['kwargs']['tournament_id']
self.tournament = await Tournament.objects.filter(pk=self.tournament_id)\
.prefetch_related('draw__current_round__current_pool__current_team__participation__team').aget()
# Fetch participations from the tournament
self.participations = []
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team'):
self.participations.append(participation)
# Fetch the registration of the current user
user = self.scope['user']
reg = await Registration.objects.aget(user=user)
self.registration = reg
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:
# This user may not have access to the drawing session
await self.close()
return
# Accept the connection
await self.accept()
# Register to channel layers to get updates
await self.channel_layer.group_add(f"tournament-{self.tournament.id}", self.channel_name)
if not self.registration.is_volunteer:
if self.registration.participates:
await self.channel_layer.group_add(f"team-{self.registration.team.trigram}", self.channel_name)
participation = reg.team.participation
if participation.valid:
await self.channel_layer.group_add(f"tournament-{participation.tournament.id}", self.channel_name)
else:
await self.channel_layer.group_add(f"volunteer-{self.tournament.id}", self.channel_name)
tids = [t.id async for t in Tournament.objects.all()] \
if reg.is_admin else [t.id for t in reg.interesting_tournaments]
for tid in tids:
await self.channel_layer.group_add(f"tournament-{tid}", self.channel_name)
await self.channel_layer.group_add(f"volunteer-{tid}", self.channel_name)
async def disconnect(self, close_code) -> None:
"""
@ -78,25 +70,40 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
:param close_code: The error code.
"""
# Unregister from channel layers
await self.channel_layer.group_discard(f"tournament-{self.tournament.id}", self.channel_name)
if not self.registration.is_volunteer:
await self.channel_layer.group_discard(f"team-{self.registration.team.trigram}", self.channel_name)
participation = self.registration.team.participation
await self.channel_layer.group_discard(f"tournament-{participation.tournament.id}", self.channel_name)
else:
await self.channel_layer.group_discard(f"volunteer-{self.tournament.id}", self.channel_name)
async for tournament in Tournament.objects.all():
await self.channel_layer.group_discard(f"tournament-{tournament.id}", self.channel_name)
await self.channel_layer.group_discard(f"volunteer-{tournament.id}", self.channel_name)
async def alert(self, message: str, alert_type: str = 'info', **kwargs):
async def alert(self, message: str, alert_type: str = 'info', tid: int = -1, **kwargs):
"""
Send an alert message to the current user.
:param message: The body of the alert.
:param alert_type: The type of the alert, which is a bootstrap color (success, warning, info, danger,)
:param tid: The tournament id. Default to -1, the current tournament.
"""
return await self.send_json({'type': 'alert', 'alert_type': alert_type, 'message': str(message)})
tid = tid if tid > 0 else self.tournament_id
return await self.send_json({'tid': tid, 'type': 'alert', 'alert_type': alert_type, 'message': str(message)})
async def receive_json(self, content, **kwargs):
"""
Called when the client sends us some data, parsed as JSON.
:param content: The sent data, decoded from JSON text. Must content a `type` field.
"""
# Get the tournament from the message
self.tournament_id = content['tid']
self.tournament = await Tournament.objects.filter(pk=self.tournament_id) \
.prefetch_related('draw__current_round__current_pool__current_team__participation__team').aget()
# Fetch participations from the tournament
self.participations = []
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team'):
self.participations.append(participation)
# Refresh tournament
self.tournament = await Tournament.objects.filter(pk=self.tournament_id)\
.prefetch_related('draw__current_round__current_pool__current_team__participation__team').aget()
@ -176,7 +183,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await TeamDraw.objects.acreate(participation=participation, round=r)
# Send to clients the different pools
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.send_poules',
{
'tid': self.tournament_id,
'type': 'draw.send_poules',
'round': r.number,
'poules': [
{
@ -184,29 +193,32 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'teams': await pool.atrigrams(),
}
async for pool in r.pool_set.order_by('letter').all()
]})
]
})
draw.current_round = r1
await draw.asave()
# Make dice box visible
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
await self.alert(_("Draw started!"), 'success')
# Update user interface
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.start', 'fmt': fmt, 'draw': draw})
{'tid': self.tournament_id, 'type': 'draw.start', 'fmt': fmt, 'draw': draw})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active', 'round': 1})
{'tid': self.tournament_id, 'type': 'draw.set_active', 'round': 1})
# Send notification to everyone
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.notify', 'title': 'Tirage au sort du TFJM²',
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': 'Tirage au sort du TFJM²',
'body': "Le tirage au sort du tournoi de "
f"{self.tournament.name} a commencé !"})
@ -216,7 +228,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
"""
await self.alert(_("The draw for the tournament {tournament} will start.")
.format(tournament=self.tournament.name), 'warning')
await self.send_json({'type': 'draw_start', 'fmt': content['fmt'],
await self.send_json({'tid': content['tid'], 'type': 'draw_start', 'fmt': content['fmt'],
'trigrams': [p.team.trigram for p in self.participations]})
@ensure_orga
@ -231,7 +243,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# All associated data will be deleted by cascade
await self.tournament.draw.adelete()
# Send information to all users
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw_abort'})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw_abort'})
async def draw_abort(self, content) -> None:
"""
@ -239,7 +252,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
"""
await self.alert(_("The draw for the tournament {tournament} is aborted.")
.format(tournament=self.tournament.name), 'danger')
await self.send_json({'type': 'abort'})
await self.send_json({'tid': content['tid'], 'type': 'abort'})
async def process_dice(self, trigram: str | None = None, **kwargs):
"""
@ -310,7 +323,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Send the dice result to all users
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice', 'team': trigram, 'result': res})
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': trigram, 'result': res})
if state == 'DICE_SELECT_POULES' and \
not await TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id,
@ -364,19 +378,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await dup.asave()
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}",
{'type': 'draw.dice', 'team': dup.participation.team.trigram, 'result': None})
{'tid': self.tournament_id, 'type': 'draw.dice',
'team': dup.participation.team.trigram, 'result': None})
# Send notification to concerned teams
await self.channel_layer.group_send(
f"team-{dup.participation.team.trigram}",
{'type': 'draw.notify', 'title': 'Tirage au sort du TFJM²',
{'tid': self.tournament_id, 'type': 'draw.notify', 'title': 'Tirage au sort du TFJM²',
'body': 'Votre score de dé est identique à celui de une ou plusieurs équipes. '
'Veuillez le relancer.'}
)
# Alert the tournament
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}",
{'type': 'draw.alert',
{'tid': self.tournament_id, 'type': 'draw.alert',
'message': _('Dices from teams {teams} are identical. Please relaunch your dices.').format(
teams=', '.join(td.participation.team.trigram for td in dups)),
'alert_type': 'warning'})
@ -415,6 +430,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# This also determines the passage order, in the natural order this time.
# If there is a 5-teams pool, we force the last team to be in the first pool,
# which is this specific pool since they are ordered by decreasing size.
# This is not true for the final tournament, which considers the scores of the
# first round.
if not self.tournament.final:
tds_copy = tds.copy()
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)
@ -452,23 +470,26 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
for td in tds:
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}",
{'type': 'draw.dice', 'team': td.participation.team.trigram, 'result': None})
{'tid': self.tournament_id, 'type': 'draw.dice', 'team': td.participation.team.trigram, 'result': None})
# Hide dice interface
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': False})
# Display dice interface only for the teams in the first pool, and for volunteers
async for td in pool.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})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
# First send the second pool to have the good team order
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.send_poules',
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number,
'poules': [
{
@ -478,7 +499,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
async for pool in r2.pool_set.order_by('letter').all()
]})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.send_poules',
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r.number,
'poules': [
{
@ -490,10 +511,10 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Update information header and the active team on the recap menu
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active',
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r.number,
'pool': pool.get_letter_display()})
@ -521,28 +542,30 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Update information header
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active',
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r.number,
'pool': pool.get_letter_display(),
'team': pool.current_team.participation.team.trigram})
# Hide dice button to everyone
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': False})
# Display the box button to the first team and to volunteers
trigram = pool.current_team.participation.team.trigram
await self.channel_layer.group_send(f"team-{trigram}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility', 'visible': True})
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{tds[0].participation.team.trigram}",
{'type': 'draw.notify', 'title': "À votre tour !",
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': "À votre tour !",
'body': "C'est à vous de tirer un nouveau problème !"})
async def select_problem(self, **kwargs):
@ -585,20 +608,25 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Update interface
trigram = td.participation.team.trigram
await self.channel_layer.group_send(f"team-{trigram}",
{'type': 'draw.box_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.box_visibility',
'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.box_visibility',
'visible': False})
await self.channel_layer.group_send(f"team-{trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"team-{self.tournament.id}",
{'type': 'draw.draw_problem', 'team': trigram, 'problem': problem})
{'tid': self.tournament_id, 'type': 'draw.draw_problem', 'team': trigram,
'problem': problem})
self.tournament.draw.last_message = ""
await self.tournament.draw.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
async def accept_problem(self, **kwargs):
@ -641,11 +669,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Send the accepted problem to the users
await self.channel_layer.group_send(f"team-{trigram}",
{'type': 'draw.buttons_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': False})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
{'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r.number,
'team': trigram,
'problem': td.accepted})
@ -659,13 +689,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
new_trigram = next_td.participation.team.trigram
await self.channel_layer.group_send(f"team-{new_trigram}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility',
'visible': True})
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{new_trigram}",
{'type': 'draw.notify', 'title': "À votre tour !",
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': "À votre tour !",
'body': "C'est à vous de tirer un nouveau problème !"})
else:
# Pool is ended
@ -674,10 +707,10 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
pool = r.current_pool
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active',
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r.number,
'pool': pool.get_letter_display(),
'team': pool.current_team.participation.team.trigram
@ -715,6 +748,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Send the reordered pool
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {
'tid': self.tournament_id,
'type': 'draw.reorder_pool',
'round': r.number,
'pool': pool.get_letter_display(),
@ -736,14 +770,17 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
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})
{'tid': self.tournament_id, '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 !",
{'tid': self.tournament_id, '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})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
else:
# Round is ended
await self.end_round(r)
@ -766,16 +803,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
for participation in self.participations:
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}",
{'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
{'tid': 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 !",
{'tid': self.tournament_id, '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',
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number,
'poules': [
{
@ -793,9 +832,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
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})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': 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é."
@ -803,7 +844,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.tournament.draw.asave()
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.export_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.export_visibility',
'visible': True})
async def reject_problem(self, **kwargs):
"""
@ -854,11 +896,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Update interface
await self.channel_layer.group_send(f"team-{trigram}",
{'type': 'draw.buttons_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': False})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.reject_problem',
{'tid': self.tournament_id, 'type': 'draw.reject_problem',
'round': r.number, 'team': trigram, 'rejected': td.rejected})
if already_refused:
@ -873,22 +917,23 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
new_trigram = next_td.participation.team.trigram
await self.channel_layer.group_send(f"team-{new_trigram}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility', 'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active',
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r.number,
'pool': pool.get_letter_display(),
'team': new_trigram})
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{new_trigram}",
{'type': 'draw.notify', 'title': "À votre tour !",
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': "À votre tour !",
'body': "C'est à vous de tirer un nouveau problème !"})
@ensure_orga
@ -906,7 +951,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await pool.export()
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.export_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.export_visibility',
'visible': False})
@ensure_orga
async def continue_final(self, **kwargs):
@ -926,6 +972,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
# Send notification to everyone
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': 'Tirage au sort du TFJM²',
'body': "Le tirage au sort pour le second tour de la finale a commencé !"})
# Set the first pool of the second round as the active pool
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
r2.current_pool = pool
@ -951,7 +1003,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Send pools to users
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.send_poules',
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number,
'poules': [
{
@ -965,27 +1017,31 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
for participation in self.participations:
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}",
{'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
{'tid': self.tournament_id, 'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
async for td in r2.current_pool.team_draws.prefetch_related('participation__team'):
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
# Notify the team that it can draw a problem
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.notify', 'title': "À votre tour !",
{'tid': self.tournament_id, 'type': 'draw.notify',
'title': "À votre tour !",
'body': "C'est à vous de tirer un nouveau problème !"})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.continue_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.continue_visibility',
'visible': False})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active',
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r2.number,
'pool': r2.current_pool.get_letter_display()})
@ -1014,12 +1070,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.undo_order_dice()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info',
{'tid': self.tournament_id, 'type': 'draw.set_info',
'info': await self.tournament.draw.ainformation()})
r = self.tournament.draw.current_round
p = r.current_pool
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active',
{'tid': self.tournament_id, 'type': 'draw.set_active',
'round': r.number,
'pool': p.get_letter_display() if p else None,
'team': p.current_team.participation.team.trigram
@ -1037,15 +1093,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await td.asave()
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.continue_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.continue_visibility',
'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
{'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
@ -1061,13 +1120,15 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await td.asave()
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.box_visibility', 'visible': True})
async def undo_process_problem(self):
"""
@ -1115,7 +1176,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await last_td.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
{'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r.number,
'team': last_td.participation.team.trigram,
'problem': last_td.accepted})
@ -1133,7 +1194,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await last_td.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.reject_problem',
{'tid': self.tournament_id, 'type': 'draw.reject_problem',
'round': r.number,
'team': last_td.participation.team.trigram,
'rejected': last_td.rejected})
@ -1143,9 +1204,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await r.current_pool.asave()
await self.channel_layer.group_send(f"team-{last_td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
else:
# Return to the dice choice
pool_tds = {td.id: td async for td in p.team_draws.prefetch_related('participation__team')}
@ -1166,7 +1229,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
@ -1177,12 +1240,15 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Make dice box visible
for td in pool_tds.values():
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.box_visibility',
'visible': False})
async def undo_pool_dice(self):
"""
@ -1217,7 +1283,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
@ -1236,15 +1302,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await td.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
{'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
@ -1258,12 +1327,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
async for td in r1.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram,
'result': td.choice_dice})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.send_poules',
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r1.number,
'poules': [
{
@ -1281,15 +1350,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await td.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
{'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r1.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
@ -1308,14 +1380,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
async for td in r1.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram,
'result': td.choice_dice})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': False})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.continue_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.continue_visibility',
'visible': True})
else:
# Go to the dice order
async for r0 in self.tournament.draw.round_set.all():
@ -1349,19 +1423,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
async for td in r.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram,
'result': td.passage_dice})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
'visible': True})
async def undo_order_dice(self):
"""
@ -1393,7 +1468,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
@ -1410,55 +1485,57 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
"""
Send a notification (with title and body) to the current user.
"""
await self.send_json({'type': 'notification', 'title': content['title'], 'body': content['body']})
await self.send_json({'tid': content['tid'], 'type': 'notification',
'title': content['title'], 'body': content['body']})
async def draw_set_info(self, content):
"""
Set the information banner to the current user.
"""
await self.send_json({'type': 'set_info', 'information': content['info']})
await self.send_json({'tid': content['tid'], 'type': 'set_info', 'information': content['info']})
async def draw_dice(self, content):
"""
Update the dice of a given team for the current user interface.
"""
await self.send_json({'type': 'dice', 'team': content['team'], 'result': content['result']})
await self.send_json({'tid': content['tid'], 'type': 'dice',
'team': content['team'], 'result': content['result']})
async def draw_dice_visibility(self, content):
"""
Update the visibility of the dice button for the current user.
"""
await self.send_json({'type': 'dice_visibility', 'visible': content['visible']})
await self.send_json({'tid': content['tid'], 'type': 'dice_visibility', 'visible': content['visible']})
async def draw_box_visibility(self, content):
"""
Update the visibility of the box button for the current user.
"""
await self.send_json({'type': 'box_visibility', 'visible': content['visible']})
await self.send_json({'tid': content['tid'], 'type': 'box_visibility', 'visible': content['visible']})
async def draw_buttons_visibility(self, content):
"""
Update the visibility of the accept/reject buttons for the current user.
"""
await self.send_json({'type': 'buttons_visibility', 'visible': content['visible']})
await self.send_json({'tid': content['tid'], 'type': 'buttons_visibility', 'visible': content['visible']})
async def draw_export_visibility(self, content):
"""
Update the visibility of the export button for the current user.
"""
await self.send_json({'type': 'export_visibility', 'visible': content['visible']})
await self.send_json({'tid': content['tid'], 'type': 'export_visibility', 'visible': content['visible']})
async def draw_continue_visibility(self, content):
"""
Update the visibility of the continue button for the current user.
"""
await self.send_json({'type': 'continue_visibility', 'visible': content['visible']})
await self.send_json({'tid': content['tid'], 'type': 'continue_visibility', 'visible': content['visible']})
async def draw_send_poules(self, content):
"""
Send the pools and the teams to the current user to update the interface.
"""
await self.send_json({'type': 'set_poules', 'round': content['round'],
await self.send_json({'tid': content['tid'], 'type': 'set_poules', 'round': content['round'],
'poules': content['poules']})
async def draw_set_active(self, content):
@ -1466,6 +1543,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
Update the user interface to highlight the current team.
"""
await self.send_json({
'tid': content['tid'],
'type': 'set_active',
'round': content.get('round', None),
'poule': content.get('pool', None),
@ -1476,20 +1554,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
"""
Send the accepted problem of a team to the current user.
"""
await self.send_json({'type': 'set_problem', 'round': content['round'],
await self.send_json({'tid': content['tid'], 'type': 'set_problem', 'round': content['round'],
'team': content['team'], 'problem': content['problem']})
async def draw_reject_problem(self, content):
"""
Send the rejected problems of a team to the current user.
"""
await self.send_json({'type': 'reject_problem', 'round': content['round'],
await self.send_json({'tid': content['tid'], 'type': 'reject_problem', 'round': content['round'],
'team': content['team'], 'rejected': content['rejected']})
async def draw_reorder_pool(self, content):
"""
Send the new order of a pool to the current user.
"""
await self.send_json({'type': 'reorder_poule', 'round': content['round'],
await self.send_json({'tid': content['tid'], 'type': 'reorder_poule', 'round': content['round'],
'poule': content['pool'], 'teams': content['teams'],
'problems': content['problems']})

View File

@ -1,7 +1,10 @@
# Copyright (C) 2023 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path
from . import consumers
websocket_urlpatterns = [
path("ws/draw/<int:tournament_id>/", consumers.DrawConsumer.as_asgi()),
path("ws/draw/", consumers.DrawConsumer.as_asgi()),
]

View File

@ -7,7 +7,7 @@
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
const sockets = {}
let socket = null
const messages = document.getElementById('messages')
@ -17,7 +17,7 @@ const messages = document.getElementById('messages')
* @param tid The tournament id
*/
function abortDraw(tid) {
sockets[tid].send(JSON.stringify({'type': 'abort'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'abort'}))
}
/**
@ -26,7 +26,7 @@ function abortDraw(tid) {
* @param tid The tournament id
*/
function cancelLastStep(tid) {
sockets[tid].send(JSON.stringify({'type': 'cancel'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'cancel'}))
}
/**
@ -36,7 +36,7 @@ function cancelLastStep(tid) {
* @param trigram The trigram of the team that a volunteer wants to force the dice launch (default: null)
*/
function drawDice(tid, trigram = null) {
sockets[tid].send(JSON.stringify({'type': 'dice', 'trigram': trigram}))
socket.send(JSON.stringify({'tid': tid, 'type': 'dice', 'trigram': trigram}))
}
/**
@ -44,7 +44,7 @@ function drawDice(tid, trigram = null) {
* @param tid The tournament id
*/
function drawProblem(tid) {
sockets[tid].send(JSON.stringify({'type': 'draw_problem'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'draw_problem'}))
}
/**
@ -52,7 +52,7 @@ function drawProblem(tid) {
* @param tid The tournament id
*/
function acceptProblem(tid) {
sockets[tid].send(JSON.stringify({'type': 'accept'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'accept'}))
}
/**
@ -60,7 +60,7 @@ function acceptProblem(tid) {
* @param tid The tournament id
*/
function rejectProblem(tid) {
sockets[tid].send(JSON.stringify({'type': 'reject'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'reject'}))
}
/**
@ -68,7 +68,7 @@ function rejectProblem(tid) {
* @param tid The tournament id
*/
function exportDraw(tid) {
sockets[tid].send(JSON.stringify({'type': 'export'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'export'}))
}
/**
@ -76,7 +76,7 @@ function exportDraw(tid) {
* @param tid The tournament id
*/
function continueFinal(tid) {
sockets[tid].send(JSON.stringify({'type': 'continue_final'}))
socket.send(JSON.stringify({'tid': tid, 'type': 'continue_final'}))
}
/**
@ -108,14 +108,6 @@ document.addEventListener('DOMContentLoaded', () => {
elem => elem.addEventListener(
'click', () => document.location.hash = '#' + elem.innerText.toLowerCase()))
for (let tournament of tournaments) {
// Open a websocket per tournament
let socket = new WebSocket(
(document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host
+ '/ws/draw/' + tournament.id + '/'
)
sockets[tournament.id] = socket
/**
* Add alert message on the top on the interface.
* @param message The content of the alert.
@ -137,23 +129,25 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Update the information banner.
* @param tid The tournament id
* @param info The content to updated
*/
function setInfo(info) {
document.getElementById(`messages-${tournament.id}`).innerHTML = info
function setInfo(tid, info) {
document.getElementById(`messages-${tid}`).innerHTML = info
}
/**
* Open the draw interface, given the list of teams.
* @param tid The tournament id
* @param teams The list of teams (represented by their trigrams) that are present on this draw.
*/
function drawStart(teams) {
function drawStart(tid, teams) {
// Hide the not-started-banner
document.getElementById(`banner-not-started-${tournament.id}`).classList.add('d-none')
document.getElementById(`banner-not-started-${tid}`).classList.add('d-none')
// Display the full draw interface
document.getElementById(`draw-content-${tournament.id}`).classList.remove('d-none')
document.getElementById(`draw-content-${tid}`).classList.remove('d-none')
let dicesDiv = document.getElementById(`dices-${tournament.id}`)
let dicesDiv = document.getElementById(`dices-${tid}`)
for (let team of teams) {
// Add empty dice score badge for each team
let col = document.createElement('div')
@ -161,11 +155,11 @@ document.addEventListener('DOMContentLoaded', () => {
dicesDiv.append(col)
let diceDiv = document.createElement('div')
diceDiv.id = `dice-${tournament.id}-${team}`
diceDiv.id = `dice-${tid}-${team}`
diceDiv.classList.add('badge', 'rounded-pill', 'text-bg-warning')
if (document.getElementById(`abort-${tournament.id}`) !== null) {
// Check if this is a volunteer, who can launch a dice for a specific team
diceDiv.onclick = (e) => drawDice(tournament.id, team)
if (document.getElementById(`abort-${tid}`) !== null) {
// Check if this is a volunteer, who can launch a die for a specific team
diceDiv.onclick = (_) => drawDice(tid, team)
}
diceDiv.textContent = `${team} 🎲 ??`
col.append(diceDiv)
@ -174,28 +168,30 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Abort the current draw, and make all invisible, except the not-started-banner.
* @param tid The tournament id
*/
function drawAbort() {
document.getElementById(`banner-not-started-${tournament.id}`).classList.remove('d-none')
document.getElementById(`draw-content-${tournament.id}`).classList.add('d-none')
document.getElementById(`dices-${tournament.id}`).innerHTML = ""
document.getElementById(`recap-${tournament.id}-round-list`).innerHTML = ""
document.getElementById(`tables-${tournament.id}`).innerHTML = ""
updateDiceVisibility(false)
updateBoxVisibility(false)
updateButtonsVisibility(false)
updateExportVisibility(false)
updateContinueVisibility(false)
function drawAbort(tid) {
document.getElementById(`banner-not-started-${tid}`).classList.remove('d-none')
document.getElementById(`draw-content-${tid}`).classList.add('d-none')
document.getElementById(`dices-${tid}`).innerHTML = ""
document.getElementById(`recap-${tid}-round-list`).innerHTML = ""
document.getElementById(`tables-${tid}`).innerHTML = ""
updateDiceVisibility(tid, false)
updateBoxVisibility(tid, false)
updateButtonsVisibility(tid, false)
updateExportVisibility(tid, false)
updateContinueVisibility(tid, false)
}
/**
* This function is triggered after a new dice result. We update the score of the team.
* Can be resetted to empty values if the result is null.
* @param tid The tournament id
* @param trigram The trigram of the team that launched its dice
* @param result The result of the dice. null if it is a reset.
*/
function updateDiceInfo(trigram, result) {
let elem = document.getElementById(`dice-${tournament.id}-${trigram}`)
function updateDiceInfo(tid, trigram, result) {
let elem = document.getElementById(`dice-${tid}-${trigram}`)
if (result === null) {
elem.classList.remove('text-bg-success')
elem.classList.add('text-bg-warning')
@ -210,10 +206,11 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Display or hide the dice button.
* @param tid The tournament id
* @param visible The visibility status
*/
function updateDiceVisibility(visible) {
let div = document.getElementById(`launch-dice-${tournament.id}`)
function updateDiceVisibility(tid, visible) {
let div = document.getElementById(`launch-dice-${tid}`)
if (visible)
div.classList.remove('d-none')
else
@ -222,10 +219,11 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Display or hide the box button.
* @param tid The tournament id
* @param visible The visibility status
*/
function updateBoxVisibility(visible) {
let div = document.getElementById(`draw-problem-${tournament.id}`)
function updateBoxVisibility(tid, visible) {
let div = document.getElementById(`draw-problem-${tid}`)
if (visible)
div.classList.remove('d-none')
else
@ -234,10 +232,11 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Display or hide the accept and reject buttons.
* @param tid The tournament id
* @param visible The visibility status
*/
function updateButtonsVisibility(visible) {
let div = document.getElementById(`buttons-${tournament.id}`)
function updateButtonsVisibility(tid, visible) {
let div = document.getElementById(`buttons-${tid}`)
if (visible)
div.classList.remove('d-none')
else
@ -246,10 +245,11 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Display or hide the export button.
* @param tid The tournament id
* @param visible The visibility status
*/
function updateExportVisibility(visible) {
let div = document.getElementById(`export-${tournament.id}`)
function updateExportVisibility(tid, visible) {
let div = document.getElementById(`export-${tid}`)
if (visible)
div.classList.remove('d-none')
else
@ -258,32 +258,37 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Display or hide the continuation button.
* @param tid The tournament id
* @param visible The visibility status
*/
function updateContinueVisibility(visible) {
let div = document.getElementById(`continue-${tournament.id}`)
function updateContinueVisibility(tid, visible) {
let div = document.getElementById(`continue-${tid}`)
if (div !== null) {
// Only present during the final
if (visible)
div.classList.remove('d-none')
else
div.classList.add('d-none')
}
}
/**
* Set the different pools for the given round, and update the interface.
* @param tid The tournament id
* @param round The round number, as integer (1 or 2)
* @param poules The list of poules, which are represented with their letters and trigrams,
* [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
*/
function updatePoules(round, poules) {
let roundList = document.getElementById(`recap-${tournament.id}-round-list`)
let poolListId = `recap-${tournament.id}-round-${round}-pool-list`
function updatePoules(tid, round, poules) {
let roundList = document.getElementById(`recap-${tid}-round-list`)
let poolListId = `recap-${tid}-round-${round}-pool-list`
let poolList = document.getElementById(poolListId)
if (poolList === null) {
// Add a div for the round in the recap div
let div = document.createElement('div')
div.id = `recap-${tournament.id}-round-${round}`
div.id = `recap-${tid}-round-${round}`
div.classList.add('col-md-6', 'px-3', 'py-3')
div.setAttribute('data-tournament', tournament.id)
div.setAttribute('data-tournament', tid)
let title = document.createElement('strong')
title.textContent = 'Tour ' + round
@ -299,14 +304,14 @@ document.addEventListener('DOMContentLoaded', () => {
let c = 1
for (let poule of poules) {
let teamListId = `recap-${tournament.id}-round-${round}-pool-${poule.letter}-team-list`
let teamListId = `recap-${tid}-round-${round}-pool-${poule.letter}-team-list`
let teamList = document.getElementById(teamListId)
if (teamList === null) {
// Add a div for the pool in the recap div
let li = document.createElement('li')
li.id = `recap-${tournament.id}-round-${round}-pool-${poule.letter}`
li.id = `recap-${tid}-round-${round}-pool-${poule.letter}`
li.classList.add('list-group-item', 'px-3', 'py-3')
li.setAttribute('data-tournament', tournament.id)
li.setAttribute('data-tournament', tid)
let title = document.createElement('strong')
title.textContent = 'Poule ' + poule.letter + round
@ -323,11 +328,11 @@ document.addEventListener('DOMContentLoaded', () => {
// The pool is initialized
for (let team of poule.teams) {
// Reorder dices
let diceDiv = document.getElementById(`dice-${tournament.id}-${team}`)
let diceDiv = document.getElementById(`dice-${tid}-${team}`)
diceDiv.parentElement.style.order = c.toString()
c += 1
let teamLiId = `recap-${tournament.id}-round-${round}-team-${team}`
let teamLiId = `recap-${tid}-round-${round}-team-${team}`
let teamLi = document.getElementById(teamLiId)
if (teamLi === null) {
@ -335,13 +340,13 @@ document.addEventListener('DOMContentLoaded', () => {
teamLi = document.createElement('li')
teamLi.id = teamLiId
teamLi.classList.add('list-group-item')
teamLi.setAttribute('data-tournament', tournament.id)
teamLi.setAttribute('data-tournament', tid)
teamList.append(teamLi)
}
// Add the accepted problem div (empty for now)
let acceptedDivId = `recap-${tournament.id}-round-${round}-team-${team}-accepted`
let acceptedDivId = `recap-${tid}-round-${round}-team-${team}-accepted`
let acceptedDiv = document.getElementById(acceptedDivId)
if (acceptedDiv === null) {
acceptedDiv = document.createElement('div')
@ -352,7 +357,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
// Add the rejected problems div (empty for now)
let rejectedDivId = `recap-${tournament.id}-round-${round}-team-${team}-rejected`
let rejectedDivId = `recap-${tid}-round-${round}-team-${team}-rejected`
let rejectedDiv = document.getElementById(rejectedDivId)
if (rejectedDiv === null) {
rejectedDiv = document.createElement('div')
@ -365,8 +370,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
// Draw tables
let tablesDiv = document.getElementById(`tables-${tournament.id}`)
let tablesRoundDiv = document.getElementById(`tables-${tournament.id}-round-${round}`)
let tablesDiv = document.getElementById(`tables-${tid}`)
let tablesRoundDiv = document.getElementById(`tables-${tid}-round-${round}`)
if (tablesRoundDiv === null) {
// Add the tables div for the current round if necessary
let card = document.createElement('div')
@ -379,7 +384,7 @@ document.addEventListener('DOMContentLoaded', () => {
card.append(cardHeader)
tablesRoundDiv = document.createElement('div')
tablesRoundDiv.id = `tables-${tournament.id}-round-${round}`
tablesRoundDiv.id = `tables-${tid}-round-${round}`
tablesRoundDiv.classList.add('card-body', 'd-flex', 'flex-wrap')
card.append(tablesRoundDiv)
}
@ -389,20 +394,21 @@ document.addEventListener('DOMContentLoaded', () => {
continue
// Display the table for the pool
updatePouleTable(round, poule)
updatePouleTable(tid, round, poule)
}
}
}
/**
* Update the table for the given round and the given pool, where there will be the chosen problems.
* @param tid The tournament id
* @param round The round number, as integer (1 or 2)
* @param poule The current pool, which id represented with its letter and trigrams,
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
*/
function updatePouleTable(round, poule) {
let tablesRoundDiv = document.getElementById(`tables-${tournament.id}-round-${round}`)
let pouleTable = document.getElementById(`table-${tournament.id}-${round}-${poule.letter}`)
function updatePouleTable(tid, round, poule) {
let tablesRoundDiv = document.getElementById(`tables-${tid}-round-${round}`)
let pouleTable = document.getElementById(`table-${tid}-${round}-${poule.letter}`)
if (pouleTable === null) {
// Create table
let card = document.createElement('div')
@ -419,7 +425,7 @@ document.addEventListener('DOMContentLoaded', () => {
card.append(cardBody)
pouleTable = document.createElement('table')
pouleTable.id = `table-${tournament.id}-${round}-${poule.letter}`
pouleTable.id = `table-${tid}-${round}-${poule.letter}`
pouleTable.classList.add('table', 'table-stripped')
cardBody.append(pouleTable)
@ -464,7 +470,7 @@ document.addEventListener('DOMContentLoaded', () => {
let problemTh = document.createElement('th')
problemTh.classList.add('text-center')
// Problem is unknown for now
problemTh.innerHTML = `Pb. <span id="table-${tournament.id}-round-${round}-problem-${team}">?</span>`
problemTh.innerHTML = `Pb. <span id="table-${tid}-round-${round}-problem-${team}">?</span>`
problemTr.append(problemTh)
}
@ -553,45 +559,47 @@ document.addEventListener('DOMContentLoaded', () => {
}
/**
* Highligh the team that is currently choosing its problem.
* Highlight the team that is currently choosing its problem.
* @param tid The tournament id
* @param round The current round number, as integer (1 or 2)
* @param pool The current pool letter (A, B, C or D) (null if non-relevant)
* @param team The current team trigram (null if non-relevant)
*/
function updateActiveRecap(round, pool, team) {
function updateActiveRecap(tid, round, pool, team) {
// Remove the previous highlights
document.querySelectorAll(`div.text-bg-secondary[data-tournament="${tournament.id}"]`)
document.querySelectorAll(`div.text-bg-secondary[data-tournament="${tid}"]`)
.forEach(elem => elem.classList.remove('text-bg-secondary'))
document.querySelectorAll(`li.list-group-item-success[data-tournament="${tournament.id}"]`)
document.querySelectorAll(`li.list-group-item-success[data-tournament="${tid}"]`)
.forEach(elem => elem.classList.remove('list-group-item-success'))
document.querySelectorAll(`li.list-group-item-info[data-tournament="${tournament.id}"]`)
document.querySelectorAll(`li.list-group-item-info[data-tournament="${tid}"]`)
.forEach(elem => elem.classList.remove('list-group-item-info'))
// Highlight current round, if existing
let roundDiv = document.getElementById(`recap-${tournament.id}-round-${round}`)
let roundDiv = document.getElementById(`recap-${tid}-round-${round}`)
if (roundDiv !== null)
roundDiv.classList.add('text-bg-secondary')
// Highlight current pool, if existing
let poolLi = document.getElementById(`recap-${tournament.id}-round-${round}-pool-${pool}`)
let poolLi = document.getElementById(`recap-${tid}-round-${round}-pool-${pool}`)
if (poolLi !== null)
poolLi.classList.add('list-group-item-success')
// Highlight current team, if existing
let teamLi = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}`)
let teamLi = document.getElementById(`recap-${tid}-round-${round}-team-${team}`)
if (teamLi !== null)
teamLi.classList.add('list-group-item-info')
}
/**
* Update the recap and the table when a team accepts a problem.
* @param tid The tournament id
* @param round The current round, as integer (1 or 2)
* @param team The current team trigram
* @param problem The accepted problem, as integer
*/
function setProblemAccepted(round, team, problem) {
function setProblemAccepted(tid, round, team, problem) {
// Update recap
let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-accepted`)
let recapDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-accepted`)
if (problem !== null) {
recapDiv.classList.remove('text-bg-warning')
recapDiv.classList.add('text-bg-success')
@ -603,27 +611,28 @@ document.addEventListener('DOMContentLoaded', () => {
recapDiv.textContent = `${team} 📃 ${problem ? problem : '?'}`
// Update table
let tableSpan = document.getElementById(`table-${tournament.id}-round-${round}-problem-${team}`)
let tableSpan = document.getElementById(`table-${tid}-round-${round}-problem-${team}`)
tableSpan.textContent = problem ? problem : '?'
}
/**
* Update the recap when a team rejects a problem.
* @param tid The tournament id
* @param round The current round, as integer (1 or 2)
* @param team The current team trigram
* @param rejected The full list of rejected problems
*/
function setProblemRejected(round, team, rejected) {
function setProblemRejected(tid, round, team, rejected) {
// Update recap
let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-rejected`)
let recapDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-rejected`)
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
let penaltyDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-penalty`)
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
if (rejected.length > problems_count - 5) {
// If more than P - 5 problems were rejected, add a penalty of 0.5 of the coefficient of the oral defender
if (penaltyDiv === null) {
penaltyDiv = document.createElement('div')
penaltyDiv.id = `recap-${tournament.id}-round-${round}-team-${team}-penalty`
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
recapDiv.parentNode.append(penaltyDiv)
}
@ -639,32 +648,34 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* For a 5-teams pool, we may reorder the pool if two teams select the same problem.
* Then, we redraw the table and set the accepted problems.
* @param tid The tournament id
* @param round The current round, as integer (1 or 2)
* @param poule The pool represented by its letter
* @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"]
* @param problems The accepted problems in the same order than the teams, [1, 1, 2, 2, 3]
*/
function reorderPoule(round, poule, teams, problems) {
function reorderPoule(tid, round, poule, teams, problems) {
// Redraw the pool table
let table = document.getElementById(`table-${tournament.id}-${round}-${poule}`)
let table = document.getElementById(`table-${tid}-${round}-${poule}`)
table.parentElement.parentElement.remove()
updatePouleTable(round, {'letter': poule, 'teams': teams})
updatePouleTable(tid, round, {'letter': poule, 'teams': teams})
// Put the problems in the table
for (let i = 0; i < teams.length; ++i) {
let team = teams[i]
let problem = problems[i]
setProblemAccepted(round, team, problem)
setProblemAccepted(tid, round, team, problem)
}
}
// Listen on websockets and process messages from the server
socket.addEventListener('message', e => {
// Parse received data as JSON
const data = JSON.parse(e.data)
/**
* Process the received data from the server.
* @param tid The tournament id
* @param data The received message
*/
function processMessage(tid, data) {
switch (data.type) {
case 'alert':
// Add alert message
@ -676,76 +687,93 @@ document.addEventListener('DOMContentLoaded', () => {
break
case 'set_info':
// Update information banner
setInfo(data.information)
setInfo(tid, data.information)
break
case 'draw_start':
// Start the draw and update the interface
drawStart(data.trigrams)
drawStart(tid, data.trigrams)
break
case 'abort':
// Abort the current draw
drawAbort()
drawAbort(tid)
break
case 'dice':
// Update the interface after a dice launch
updateDiceInfo(data.team, data.result)
updateDiceInfo(tid, data.team, data.result)
break
case 'dice_visibility':
// Update the dice button visibility
updateDiceVisibility(data.visible)
updateDiceVisibility(tid, data.visible)
break
case 'box_visibility':
// Update the box button visibility
updateBoxVisibility(data.visible)
updateBoxVisibility(tid, data.visible)
break
case 'buttons_visibility':
// Update the accept/reject buttons visibility
updateButtonsVisibility(data.visible)
updateButtonsVisibility(tid, data.visible)
break
case 'export_visibility':
// Update the export button visibility
updateExportVisibility(data.visible)
updateExportVisibility(tid, data.visible)
break
case 'continue_visibility':
// Update the continue button visibility for the final tournament
updateContinueVisibility(data.visible)
updateContinueVisibility(tid, data.visible)
break
case 'set_poules':
// Set teams order and pools and update the interface
updatePoules(data.round, data.poules)
updatePoules(tid, data.round, data.poules)
break
case 'set_active':
// Highlight the team that is selecting a problem
updateActiveRecap(data.round, data.poule, data.team)
updateActiveRecap(tid, data.round, data.poule, data.team)
break
case 'set_problem':
// Mark a problem as accepted and update the interface
setProblemAccepted(data.round, data.team, data.problem)
setProblemAccepted(tid, data.round, data.team, data.problem)
break
case 'reject_problem':
// Mark a problem as rejected and update the interface
setProblemRejected(data.round, data.team, data.rejected)
setProblemRejected(tid, data.round, data.team, data.rejected)
break
case 'reorder_poule':
// Reorder a pool and redraw the associated table
reorderPoule(data.round, data.poule, data.teams, data.problems)
reorderPoule(tid, data.round, data.poule, data.teams, data.problems)
break
}
}
function setupSocket() {
// Open a global websocket
socket = new WebSocket(
(document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/ws/draw/'
)
// Listen on websockets and process messages from the server
socket.addEventListener('message', e => {
// Parse received data as JSON
const data = JSON.parse(e.data)
processMessage(data['tid'], data)
})
// Manage errors
socket.addEventListener('close', e => {
console.error('Chat socket closed unexpectedly')
console.error('Chat socket closed unexpectedly, restarting…')
setupSocket()
})
// When the socket is opened, set the language in order to receive alerts in the good language
socket.addEventListener('open', e => {
socket.send(JSON.stringify({
'tid': tournaments[0].id,
'type': 'set_language',
'language': document.getElementsByName('language')[0].value,
}))
})
for (let tournament of tournaments) {
// Manage the start form
let format_form = document.getElementById('format-form-' + tournament.id)
if (format_form !== null) {
@ -753,10 +781,14 @@ document.addEventListener('DOMContentLoaded', () => {
e.preventDefault()
socket.send(JSON.stringify({
'tid': tournament.id,
'type': 'start_draw',
'fmt': document.getElementById('format-' + tournament.id).value
}))
})
}
}
}
setupSocket()
})

View File

@ -351,7 +351,7 @@
{% trans "Are you sure you want to abort this draw?" %}
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" onclick="abortDraw({{ tournament.id }})">{% trans "Abort" %}</button>
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal" onclick="abortDraw({{ tournament.id }})">{% trans "Abort" %}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>

View File

@ -1,6 +1,6 @@
# Copyright (C) 2023 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import asyncio
from random import shuffle
from asgiref.sync import sync_to_async
@ -48,25 +48,26 @@ class TestDraw(TestCase):
"""
await sync_to_async(self.async_client.force_login)(self.superuser)
tid = self.tournament.id
resp = await self.async_client.get(reverse('draw:index'))
self.assertEqual(resp.status_code, 200)
# Connect to Websocket
headers = [(b'cookie', self.async_client.cookies.output(header='', sep='; ').encode())]
communicator = WebsocketCommunicator(AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
f"/ws/draw/{self.tournament.id}/",
headers)
"/ws/draw/", headers)
connected, subprotocol = await communicator.connect()
self.assertTrue(connected)
# Define language
await communicator.send_json_to({'type': 'set_language', 'language': 'en'})
await communicator.send_json_to({'tid': tid, 'type': 'set_language', 'language': 'en'})
# Ensure that Draw has not started
self.assertFalse(await Draw.objects.filter(tournament=self.tournament).aexists())
# Must be an error since 1+1+1 != 12
await communicator.send_json_to({'type': 'start_draw', 'fmt': '1+1+1'})
await communicator.send_json_to({'tid': tid, 'type': 'start_draw', 'fmt': '1+1+1'})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['alert_type'], 'danger')
@ -74,29 +75,30 @@ class TestDraw(TestCase):
self.assertFalse(await Draw.objects.filter(tournament=self.tournament).aexists())
# Now start the draw
await communicator.send_json_to({'type': 'start_draw', 'fmt': '3+4+5'})
await communicator.send_json_to({'tid': tid, 'type': 'start_draw', 'fmt': '3+4+5'})
# Receive data after the start
self.assertEqual((await communicator.receive_json_from())['type'], 'alert')
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_poules', 'round': 1,
{'tid': tid, 'type': 'set_poules', 'round': 1,
'poules': [{'letter': 'A', 'teams': []},
{'letter': 'B', 'teams': []},
{'letter': 'C', 'teams': []}]})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_poules', 'round': 2,
{'tid': tid, 'type': 'set_poules', 'round': 2,
'poules': [{'letter': 'A', 'teams': []},
{'letter': 'B', 'teams': []},
{'letter': 'C', 'teams': []}]})
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'alert')
self.assertEqual(await communicator.receive_json_from(),
{'type': 'draw_start', 'fmt': [5, 4, 3],
{'tid': tid, 'type': 'draw_start', 'fmt': [5, 4, 3],
'trigrams': ['AAA', 'BBB', 'CCC', 'DDD', 'EEE', 'FFF',
'GGG', 'HHH', 'III', 'JJJ', 'KKK', 'LLL']})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_active', 'round': 1, 'poule': None, 'team': None})
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': None, 'team': None})
self.assertEqual((await communicator.receive_json_from())['type'], 'notification')
# Ensure that now tournament has started
@ -107,7 +109,7 @@ class TestDraw(TestCase):
self.assertEqual(resp.status_code, 200)
# Try to relaunch the draw
await communicator.send_json_to({'type': 'start_draw', 'fmt': '3+4+5'})
await communicator.send_json_to({'tid': tid, 'type': 'start_draw', 'fmt': '3+4+5'})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['alert_type'], 'danger')
@ -119,7 +121,7 @@ class TestDraw(TestCase):
for i, team in enumerate(self.teams):
# Launch a new dice
await communicator.send_json_to({'type': "dice", 'trigram': team.trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': team.trigram})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], "dice")
self.assertEqual(resp['team'], team.trigram)
@ -130,7 +132,7 @@ class TestDraw(TestCase):
self.assertEqual(resp['result'], td.passage_dice)
# Try to relaunch the dice
await communicator.send_json_to({'type': "dice", 'trigram': team.trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': team.trigram})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['message'], "You've already launched the dice.")
@ -151,7 +153,7 @@ class TestDraw(TestCase):
self.assertEqual((await communicator.receive_json_from())['type'], 'alert')
for i in range(dup_count):
await communicator.send_json_to({'type': "dice", 'trigram': None})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': None})
await communicator.receive_json_from()
# Reset dices
@ -161,8 +163,10 @@ class TestDraw(TestCase):
self.assertIsNone(resp['result'])
# Hide and re-display the dice
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': True})
# Set pools for the two rounds
self.assertEqual((await communicator.receive_json_from())['type'], 'set_poules')
@ -172,7 +176,7 @@ class TestDraw(TestCase):
# Manage the first pool
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_active', 'round': 1, 'poule': 'A', 'team': None})
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'A', 'team': None})
r: Round = await Round.objects.prefetch_related('current_pool__current_team__participation__team')\
.aget(number=1, draw=draw)
p = r.current_pool
@ -188,7 +192,7 @@ class TestDraw(TestCase):
i = 0
async for td in p.teamdraw_set.prefetch_related('participation__team').all():
# Launch a new dice
await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': td.participation.team.trigram})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], "dice")
trigram = td.participation.team.trigram
@ -200,7 +204,7 @@ class TestDraw(TestCase):
self.assertEqual(resp['result'], td.choice_dice)
# Try to relaunch the dice
await communicator.send_json_to({'type': "dice", 'trigram': trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': trigram})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['message'], "You've already launched the dice.")
@ -222,7 +226,7 @@ class TestDraw(TestCase):
self.assertEqual((await communicator.receive_json_from())['type'], 'alert')
for i in range(dup_count):
await communicator.send_json_to({'type': "dice", 'trigram': None})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': None})
await communicator.receive_json_from()
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
@ -232,27 +236,32 @@ class TestDraw(TestCase):
td = p.current_team
trigram = td.participation.team.trigram
self.assertEqual(td.choose_index, 0)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A',
'team': td.participation.team.trigram})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1,
'poule': 'A', 'team': td.participation.team.trigram})
# Dice is hidden for everyone first
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': False})
# The draw box is displayed for the current team and for volunteers
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
self.assertEqual(resp.status_code, 200)
# Try to launch a dice while it is not the time
await communicator.send_json_to({'type': 'dice', 'trigram': None})
await communicator.send_json_to({'tid': tid, 'type': 'dice', 'trigram': None})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['message'], "This is not the time for this.")
# Draw a problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -265,17 +274,19 @@ class TestDraw(TestCase):
self.assertEqual(resp.status_code, 200)
# Try to redraw a problem while it is not the time
await communicator.send_json_to({'type': 'draw_problem'})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
resp = await communicator.receive_json_from()
self.assertEqual(resp['type'], 'alert')
self.assertEqual(resp['message'], "This is not the time for this.")
# Reject the first problem
await communicator.send_json_to({'type': 'reject'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'reject'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'reject_problem', 'round': 1, 'team': trigram, 'rejected': [purposed]})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'reject_problem', 'round': 1, 'team': trigram, 'rejected': [purposed]})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
@ -287,17 +298,19 @@ class TestDraw(TestCase):
td = p.current_team
trigram = td.participation.team.trigram
self.assertEqual(td.choose_index, i + 1)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A',
'team': trigram})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'A', 'team': trigram})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
self.assertEqual(resp.status_code, 200)
# Draw a problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -313,11 +326,13 @@ class TestDraw(TestCase):
self.assertEqual(resp.status_code, 200)
# Accept the problem
await communicator.send_json_to({'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': 1 + (i % 2)})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': 1 + (i % 2)})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
@ -332,15 +347,17 @@ class TestDraw(TestCase):
td = p.current_team
trigram = td.participation.team.trigram
self.assertEqual(td.choose_index, 0)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A',
'team': trigram})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'A', 'team': trigram})
# Draw and reject 100 times a problem
for _i in range(100):
# Draw a problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -349,10 +366,12 @@ class TestDraw(TestCase):
self.assertIn(td.purposed, range(3, len(settings.PROBLEMS) + 1))
# Reject
await communicator.send_json_to({'type': 'reject'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'reject'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual((await communicator.receive_json_from())['type'], 'reject_problem')
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
@ -361,8 +380,8 @@ class TestDraw(TestCase):
# Ensures that this is still the first team
p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=1)
self.assertEqual(p.current_team, td)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A',
'team': trigram})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'A', 'team': trigram})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
@ -372,9 +391,11 @@ class TestDraw(TestCase):
self.assertGreaterEqual(td.penalty, 1)
# Draw a last problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -382,19 +403,21 @@ class TestDraw(TestCase):
self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1))
# Accept the problem
await communicator.send_json_to({'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': td.purposed})
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': td.purposed})
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
# Reorder the pool since there are 5 teams
self.assertEqual((await communicator.receive_json_from())['type'], 'reorder_poule')
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_active', 'round': 1, 'poule': 'B', 'team': None})
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'B', 'team': None})
# Start pool 2
r: Round = await Round.objects.prefetch_related('current_pool__current_team__participation__team')\
@ -412,7 +435,7 @@ class TestDraw(TestCase):
i = 0
async for td in p.teamdraw_set.prefetch_related('participation__team').all():
# Launch a new dice
await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.receive_json_from()
await td.arefresh_from_db()
td.choice_dice = 101 + i # Avoid duplicates
@ -427,20 +450,24 @@ class TestDraw(TestCase):
td = p.current_team
trigram = td.participation.team.trigram
self.assertEqual(td.choose_index, i)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'B',
'team': trigram})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'B', 'team': trigram})
if i == 0:
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
self.assertEqual(resp.status_code, 200)
# Draw a problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -458,14 +485,17 @@ class TestDraw(TestCase):
self.assertEqual(resp.status_code, 200)
# Accept the problem
await communicator.send_json_to({'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': i + 1})
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': i + 1})
if i < 3:
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
else:
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
@ -483,8 +513,8 @@ class TestDraw(TestCase):
self.assertEqual(p.size, 3)
self.assertEqual(await p.teamdraw_set.acount(), 3)
self.assertEqual(p.current_team, None)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'C',
'team': None})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'C', 'team': None})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
@ -493,7 +523,7 @@ class TestDraw(TestCase):
i = 0
async for td in p.teamdraw_set.prefetch_related('participation__team').all():
# Launch a new dice
await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.receive_json_from()
await td.arefresh_from_db()
td.choice_dice = 101 + i # Avoid duplicates
@ -508,20 +538,24 @@ class TestDraw(TestCase):
td = p.current_team
trigram = td.participation.team.trigram
self.assertEqual(td.choose_index, i)
self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'C',
'team': trigram})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_active', 'round': 1, 'poule': 'C', 'team': trigram})
if i == 0:
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
self.assertEqual(resp.status_code, 200)
# Draw a problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -539,16 +573,18 @@ class TestDraw(TestCase):
self.assertEqual(resp.status_code, 200)
# Accept the problem
await communicator.send_json_to({'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': i + 1})
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': i + 1})
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
self.assertEqual(td.accepted, i + 1)
if i == 2:
break
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
# Render page
@ -572,8 +608,10 @@ class TestDraw(TestCase):
self.assertEqual(resp['type'], 'set_poules')
self.assertEqual(resp['round'], 2)
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(), {'type': 'export_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'export_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
# Render page
@ -589,12 +627,12 @@ class TestDraw(TestCase):
self.assertEqual(p.size, 5 - i)
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_active', 'round': 2, 'poule': chr(65 + i), 'team': None})
{'tid': tid, 'type': 'set_active', 'round': 2, 'poule': chr(65 + i), 'team': None})
j = 0
async for td in p.teamdraw_set.prefetch_related('participation__team').all():
# Launch a new dice
await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': td.participation.team.trigram})
await communicator.receive_json_from()
await td.arefresh_from_db()
td.choice_dice = 101 + j # Avoid duplicates
@ -612,23 +650,24 @@ class TestDraw(TestCase):
trigram = td.participation.team.trigram
self.assertEqual(td.choose_index, j)
self.assertEqual(await communicator.receive_json_from(),
{'type': 'set_active', 'round': 2, 'poule': chr(65 + i),
{'tid': tid, 'type': 'set_active', 'round': 2, 'poule': chr(65 + i),
'team': trigram})
if j == 0:
self.assertEqual(await communicator.receive_json_from(),
{'type': 'dice_visibility', 'visible': False})
{'tid': tid, 'type': 'dice_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'box_visibility', 'visible': True})
{'tid': tid, 'type': 'box_visibility', 'visible': True})
# Render page
resp = await self.async_client.get(reverse('draw:index'))
self.assertEqual(resp.status_code, 200)
# Draw a problem
await communicator.send_json_to({'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'buttons_visibility', 'visible': True})
{'tid': tid, 'type': 'box_visibility', 'visible': False})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'buttons_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
@ -640,44 +679,55 @@ class TestDraw(TestCase):
self.assertNotEqual(td.purposed, old_td.accepted)
# Accept the problem
await communicator.send_json_to({'type': 'accept'})
await communicator.send_json_to({'tid': tid, 'type': 'accept'})
self.assertEqual(await communicator.receive_json_from(),
{'type': 'buttons_visibility', 'visible': False})
{'tid': tid, 'type': 'buttons_visibility', 'visible': False})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_problem')
td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk)
self.assertIsNone(td.purposed)
if j == 4 - i:
break
self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'box_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
if i == 0:
# Reorder the pool since there are 5 teams
self.assertEqual((await communicator.receive_json_from())['type'], 'reorder_poule')
if i < 2:
self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'dice_visibility', 'visible': True})
else:
self.assertEqual(await communicator.receive_json_from(), {'type': 'export_visibility', 'visible': True})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'export_visibility', 'visible': True})
self.assertEqual((await communicator.receive_json_from())['type'], 'set_info')
self.assertEqual((await communicator.receive_json_from())['type'], 'set_active')
# Export the draw
await communicator.send_json_to({'type': 'export'})
self.assertEqual(await communicator.receive_json_from(), {'type': 'export_visibility', 'visible': False})
await communicator.send_json_to({'tid': tid, 'type': 'export'})
self.assertEqual(await communicator.receive_json_from(),
{'tid': tid, 'type': 'export_visibility', 'visible': False})
# Cancel all steps and reset all
for i in range(1000):
await communicator.send_json_to({'type': 'cancel'})
if not await Draw.objects.filter(tournament=self.tournament).aexists():
await communicator.send_json_to({'tid': tid, 'type': 'cancel'})
# Purge receive queue
while True:
try:
await communicator.receive_json_from()
except asyncio.TimeoutError:
break
else:
current_state = (await Draw.objects.filter(tournament=self.tournament).prefetch_related(
if await Draw.objects.filter(tournament_id=tid).aexists():
print((await Draw.objects.filter(tournament_id=tid).aexists()))
current_state = (await Draw.objects.filter(tournament_id=tid).prefetch_related(
'current_round__current_pool__current_team__participation__team').aget()).get_state()
raise AssertionError("Draw wasn't aborted after 1000 steps, current state: " + current_state)
# Abort while the tournament is already aborted
await communicator.send_json_to({'type': "abort"})
await communicator.send_json_to({'tid': tid, 'type': "abort"})
def test_admin_pages(self):
"""