diff --git a/draw/consumers.py b/draw/consumers.py index 4843a9b..31f893f 100644 --- a/draw/consumers.py +++ b/draw/consumers.py @@ -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,37 +183,42 @@ 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', - 'round': r.number, - 'poules': [ - { - 'letter': pool.get_letter_display(), - 'teams': await pool.atrigrams(), - } - async for pool in r.pool_set.order_by('letter').all() - ]}) + { + 'tid': self.tournament_id, + 'type': 'draw.send_poules', + 'round': r.number, + 'poules': [ + { + 'letter': pool.get_letter_display(), + 'teams': await pool.atrigrams(), + } + async for pool in r.pool_set.order_by('letter').all() + ] + }) 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'}) @@ -452,23 +467,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 +496,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 +508,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 +539,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 +605,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 +666,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 +686,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 +704,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 +745,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 +767,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 +800,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 +829,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 += "

Le tirage au sort du tour 1 est terminé." @@ -803,7 +841,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 +893,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 +914,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 +948,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): @@ -951,7 +994,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 +1008,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 +1061,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 +1084,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 +1111,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 +1167,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 +1185,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 +1195,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 +1220,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 +1231,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 +1274,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 +1293,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 +1318,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 +1341,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 +1371,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 +1414,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 +1459,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 +1476,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 +1534,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 +1545,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']}) diff --git a/draw/routing.py b/draw/routing.py index c060dfa..8ce6085 100644 --- a/draw/routing.py +++ b/draw/routing.py @@ -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//", consumers.DrawConsumer.as_asgi()), + path("ws/draw/", consumers.DrawConsumer.as_asgi()), ] diff --git a/draw/static/draw.js b/draw/static/draw.js index efdf508..0f255f4 100644 --- a/draw/static/draw.js +++ b/draw/static/draw.js @@ -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,644 +108,671 @@ 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 + // Open a global websocket + socket = new WebSocket( + (document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + + '/ws/draw/' + ) - /** - * Add alert message on the top on the interface. - * @param message The content of the alert. - * @param type The alert type, which is a bootstrap color (success, info, warning, danger,…). - * @param timeout The time (in milliseconds) before the alert is auto-closing. 0 to infinitely, default to 5000 ms. - */ - function addMessage(message, type, timeout = 5000) { - const wrapper = document.createElement('div') - wrapper.innerHTML = [ - `