# Copyright (C) 2023 by Animath # SPDX-License-Identifier: GPL-3.0-or-later from collections import OrderedDict from random import randint, shuffle from channels.generic.websocket import AsyncJsonWebsocketConsumer from django.conf import settings from django.utils import translation from django.utils.translation import gettext_lazy as _ from draw.models import Draw, Round, Pool, TeamDraw from participation.models import Tournament, Participation from registration.models import Registration def ensure_orga(f): async def func(self, *args, **kwargs): reg = self.registration if reg.is_volunteer and not reg.is_admin and self.tournament not in reg.interesting_tournaments \ or not reg.is_volunteer: return await self.alert(_("You are not an organizer."), 'danger') return await f(self, *args, **kwargs) return func class DrawConsumer(AsyncJsonWebsocketConsumer): async def connect(self): 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() self.participations = [] async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team'): self.participations.append(participation) 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 await self.accept() await self.channel_layer.group_add(f"tournament-{self.tournament.id}", self.channel_name) if not self.registration.is_volunteer: await self.channel_layer.group_add(f"team-{self.registration.team.trigram}", self.channel_name) else: await self.channel_layer.group_add(f"volunteer-{self.tournament.id}", self.channel_name) async def disconnect(self, close_code): 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) else: await self.channel_layer.group_discard(f"volunteer-{self.tournament.id}", self.channel_name) async def alert(self, message: str, alert_type: str = 'info', **kwargs): return await self.send_json({'type': 'alert', 'alert_type': alert_type, 'message': str(message)}) async def receive_json(self, content, **kwargs): print(content) # Refresh tournament self.tournament = await Tournament.objects.filter(pk=self.tournament_id)\ .prefetch_related('draw__current_round__current_pool__current_team__participation__team').aget() match content['type']: case 'set_language': translation.activate(content['language']) case 'start_draw': await self.start_draw(**content) case 'abort': await self.abort(**content) case 'dice': await self.process_dice(**content) case 'draw_problem': await self.select_problem(**content) case 'accept': await self.accept_problem(**content) case 'reject': await self.reject_problem(**content) case 'export': await self.export(**content) case 'continue_final': await self.continue_final(**content) @ensure_orga async def start_draw(self, fmt, **kwargs): try: fmt = sorted(map(int, fmt.split('+')), reverse=True) except ValueError as e: return await self.alert(_("Invalid format"), 'danger') if sum(fmt) != len(self.participations): return await self.alert( _("The sum must be equal to the number of teams: expected {len}, got {sum}")\ .format(len=len(self.participations), sum=sum(fmt)), 'danger') if fmt.count(5) > 1: return await self.alert(_("There can be at most one pool with 5 teams."), 'danger') draw = await Draw.objects.acreate(tournament=self.tournament) r1 = None for i in [1, 2]: r = await Round.objects.acreate(draw=draw, number=i) if i == 1: r1 = r for j, f in enumerate(fmt): await Pool.objects.acreate(round=r, letter=j + 1, size=f) for participation in self.participations: await TeamDraw.objects.acreate(participation=participation, round=r) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.send_poules', 'round': r}) draw.current_round = r1 await draw.asave() async for td in r1.teamdraw_set.prefetch_related('participation__team').all(): await self.channel_layer.group_send(f"team-{td.participation.team.trigram}", {'type': 'draw.dice_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': True}) await self.alert(_("Draw started!"), 'success') await self.channel_layer.group_send(f"tournament-{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', 'draw': draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) async def draw_start(self, content): 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'], 'trigrams': [p.team.trigram for p in self.participations]}) @ensure_orga async def abort(self, **kwargs): await self.tournament.draw.adelete() await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw_abort'}) async def draw_abort(self, content): await self.alert(_("The draw for the tournament {tournament} is aborted.")\ .format(tournament=self.tournament.name), 'danger') await self.send_json({'type': 'abort'}) async def process_dice(self, trigram: str | None = None, **kwargs): state = self.tournament.draw.get_state() if self.registration.is_volunteer: if trigram: participation = await Participation.objects.filter(team__trigram=trigram)\ .prefetch_related('team').aget() else: # First free team if state == 'DICE_ORDER_POULE': participation = await Participation.objects\ .filter(teamdraw__pool=self.tournament.draw.current_round.current_pool, teamdraw__choice_dice__isnull=True).prefetch_related('team').afirst() else: participation = await Participation.objects\ .filter(teamdraw__round=self.tournament.draw.current_round, teamdraw__passage_dice__isnull=True).prefetch_related('team').afirst() else: participation = await Participation.objects.filter(team__participants=self.registration)\ .prefetch_related('team').aget() if participation is None: return await self.alert(_("This is not the time for this."), 'danger') trigram = participation.team.trigram team_draw = await TeamDraw.objects.filter(participation=participation, round_id=self.tournament.draw.current_round_id).aget() match state: case 'DICE_SELECT_POULES': if team_draw.passage_dice is not None: return await self.alert(_("You've already launched the dice."), 'danger') case 'DICE_ORDER_POULE': if team_draw.choice_dice is not None: return await self.alert(_("You've already launched the dice."), 'danger') if not await self.tournament.draw.current_round.current_pool.teamdraw_set\ .filter(participation=participation).aexists(): return await self.alert(_("It is not your turn."), 'danger') case _: return await self.alert(_("This is not the time for this."), 'danger') res = randint(1, 100) if state == 'DICE_SELECT_POULES': team_draw.passage_dice = res else: team_draw.choice_dice = res await team_draw.asave() await self.channel_layer.group_send( f"tournament-{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, passage_dice__isnull=True).aexists(): tds = [] async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id)\ .prefetch_related('participation__team'): tds.append(td) dices = {td: td.passage_dice for td in tds} values = list(dices.values()) error = False for v in set(values): if values.count(v) > 1: dups = [td for td in tds if td.passage_dice == v] for dup in dups: dup.passage_dice = None await dup.asave() await self.channel_layer.group_send( f"tournament-{self.tournament.id}", {'type': 'draw.dice', 'team': dup.participation.team.trigram, 'result': None}) await self.channel_layer.group_send( f"tournament-{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'}) error = True if error: return tds.sort(key=lambda td: td.passage_dice) tds_copy = tds.copy() async for p in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).order_by('letter').all(): pool_tds = sorted(tds_copy[:p.size], key=lambda td: (td.passage_dice * 27) % 100) tds_copy = tds_copy[p.size:] for i, td in enumerate(pool_tds): td.pool = p td.passage_index = i await td.asave() 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)\ .order_by('letter').all()] current_pool_id, current_passage_index = 0, 0 for i, td in enumerate(tds_copy): if i == len(tds) - 1 and round2_pools[0].size == 5: current_pool_id = 0 current_passage_index = 4 td2 = await TeamDraw.objects.filter(participation=td.participation, round=round2).aget() td2.pool = round2_pools[current_pool_id] td2.passage_index = current_passage_index current_pool_id += 1 if current_pool_id == len(round2_pools): current_pool_id = 0 current_passage_index += 1 await td2.asave() pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget() self.tournament.draw.current_round.current_pool = pool await self.tournament.draw.current_round.asave() msg = "Les résultats des dés sont les suivants : " msg += ", ".join(f"{td.participation.team.trigram} ({td.passage_dice})" for td in tds) msg += ". L'ordre de passage et les compositions des différentes poules sont affiché⋅es sur le côté. " msg += "Attention : les ordres de passage sont déterminés à partir des scores des dés, mais ne sont pas " msg += "directement l'ordre croissant des dés, afin d'avoir des poules mélangées." self.tournament.draw.last_message = msg await self.tournament.draw.asave() 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}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': False}) 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}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': True}) # First send the second pool to have the good team order await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.send_poules', 'round': 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', 'round': self.tournament.draw.current_round}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_info', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) elif state == 'DICE_ORDER_POULE' and \ not await TeamDraw.objects.filter(pool=self.tournament.draw.current_round.current_pool, choice_dice__isnull=True).aexists(): pool = self.tournament.draw.current_round.current_pool tds = [] async for td in TeamDraw.objects.filter(pool=pool)\ .prefetch_related('participation__team'): tds.append(td) dices = {td: td.choice_dice for td in tds} values = list(dices.values()) error = False for v in set(values): if values.count(v) > 1: dups = [td for td in tds if td.choice_dice == v] for dup in dups: dup.choice_dice = None await dup.asave() await self.channel_layer.group_send( f"tournament-{self.tournament.id}", {'type': 'draw.dice', 'team': dup.participation.team.trigram, 'result': None}) await self.channel_layer.group_send( f"tournament-{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'}) error = True if error: return tds.sort(key=lambda x: -x.choice_dice) for i, td in enumerate(tds): td.choose_index = i await td.asave() pool.current_team = tds[0] await pool.asave() 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', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': False}) trigram = pool.current_team.participation.team.trigram await self.channel_layer.group_send(f"team-{trigram}", {'type': 'draw.box_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.box_visibility', 'visible': True}) async def select_problem(self, **kwargs): state = self.tournament.draw.get_state() if state != 'WAITING_DRAW_PROBLEM': return await self.alert(_("This is not the time for this."), 'danger') pool = self.tournament.draw.current_round.current_pool td = pool.current_team if not self.registration.is_volunteer: participation = await Participation.objects.filter(team__participants=self.registration)\ .prefetch_related('team').aget() if participation.id != td.participation_id: return await self.alert("This is not your turn.", 'danger') while True: problem = randint(1, len(settings.PROBLEMS)) if await TeamDraw.objects.filter(participation_id=td.participation_id, round__draw__tournament=self.tournament, round__number=1, purposed=problem).aexists(): continue if await pool.teamdraw_set.filter(accepted=problem).acount() < (2 if pool.size == 5 else 1): break td.purposed = problem await td.asave() trigram = td.participation.team.trigram await self.channel_layer.group_send(f"team-{trigram}", {'type': 'draw.box_visibility', 'visible': False}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.box_visibility', 'visible': False}) await self.channel_layer.group_send(f"team-{trigram}", {'type': 'draw.buttons_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{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}) 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', 'draw': self.tournament.draw}) async def accept_problem(self, **kwargs): state = self.tournament.draw.get_state() if state != 'WAITING_CHOOSE_PROBLEM': return await self.alert(_("This is not the time for this."), 'danger') r = self.tournament.draw.current_round pool = r.current_pool td = pool.current_team if not self.registration.is_volunteer: participation = await Participation.objects.filter(team__participants=self.registration)\ .prefetch_related('team').aget() if participation.id != td.participation_id: return await self.alert("This is not your turn.", 'danger') td.accepted = td.purposed td.purposed = None await td.asave() trigram = td.participation.team.trigram msg = f"L'équipe {trigram} a accepté le problème {td.accepted} : " \ f"{settings.PROBLEMS[td.accepted - 1]}. " if pool.size == 5 and await pool.teamdraw_set.filter(accepted=td.accepted).acount() < 2: msg += "Une équipe peut encore l'accepter." else: msg += "Plus personne ne peut l'accepter." self.tournament.draw.last_message = msg await self.tournament.draw.asave() await self.channel_layer.group_send(f"team-{trigram}", {'type': 'draw.buttons_visibility', 'visible': False}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.buttons_visibility', 'visible': False}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_problem', 'round': r.number, 'team': trigram, 'problem': td.accepted}) if await pool.teamdraw_set.filter(accepted__isnull=True).aexists(): # Continue next_td = await pool.next_td() pool.current_team = next_td await pool.asave() new_trigram = next_td.participation.team.trigram await self.channel_layer.group_send(f"team-{new_trigram}", {'type': 'draw.box_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.box_visibility', 'visible': True}) else: # Pool is ended if pool.size == 5: # Maybe reorder teams if the same problem is presented twice problems = OrderedDict() async for td in pool.team_draws: problems.setdefault(td.accepted, []) problems[td.accepted].append(td) p_index = 0 for pb, tds in problems.items(): if len(tds) == 2: # Le règlement demande à ce que l'ordre soit tiré au sort shuffle(tds) tds[0].passage_index = p_index tds[1].passage_index = p_index + 1 p_index += 2 await tds[0].asave() await tds[1].asave() for pb, tds in problems.items(): if len(tds) == 1: tds[0].passage_index = p_index p_index += 1 await tds[0].asave() print(p_index) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", { 'type': 'draw.reorder_pool', 'round': r.number, 'pool': pool.get_letter_display(), 'teams': [td.participation.team.trigram async for td in pool.team_draws.prefetch_related('participation__team')], 'problems': [td.accepted async for td in pool.team_draws], }) msg += f"

Le tirage de la poule {pool.get_letter_display()}{r.number} est terminé. " \ f"Le tableau récapitulatif est en bas." self.tournament.draw.last_message = msg await self.tournament.draw.asave() if await r.teamdraw_set.filter(accepted__isnull=True).aexists(): # Next pool next_pool = await r.next_pool() r.current_pool = next_pool await r.asave() await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': True}) else: # Round is ended if r.number == 1 and not self.tournament.final: # Next round r2 = await self.tournament.draw.round_set.filter(number=2).aget() self.tournament.draw.current_round = r2 msg += "

Le tirage au sort du tour 1 est terminé." self.tournament.draw.last_message = msg await self.tournament.draw.asave() 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}) # Reorder dices await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.send_poules', 'round': r2}) # The passage order for the second round is already determined by the first round # Start the first pool of the second round p1: Pool = await r2.pool_set.filter(letter=1).aget() r2.current_pool = p1 await r2.asave() async for td in p1.teamdraw_set.prefetch_related('participation__team').all(): await self.channel_layer.group_send(f"team-{td.participation.team.trigram}", {'type': 'draw.dice_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': True}) elif r.number == 1 and self.tournament.final: # For the final tournament, we wait for a manual update between the two rounds. msg += "

Le tirage au sort du tour 1 est terminé." self.tournament.draw.last_message = msg await self.tournament.draw.asave() await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.export_visibility', 'visible': True}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_info', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) async def reject_problem(self, **kwargs): state = self.tournament.draw.get_state() if state != 'WAITING_CHOOSE_PROBLEM': return await self.alert(_("This is not the time for this."), 'danger') r = self.tournament.draw.current_round pool = r.current_pool td = pool.current_team if not self.registration.is_volunteer: participation = await Participation.objects.filter(team__participants=self.registration)\ .prefetch_related('team').aget() if participation.id != td.participation_id: return await self.alert("This is not your turn.", 'danger') problem = td.purposed already_refused = problem in td.rejected if not already_refused: td.rejected.append(problem) td.purposed = None await td.asave() remaining = len(settings.PROBLEMS) - 5 - len(td.rejected) trigram = td.participation.team.trigram msg = f"L'équipe {trigram} a refusé le problème {problem} : " \ f"{settings.PROBLEMS[problem - 1]}. " if remaining >= 0: msg += f"Il lui reste {remaining} refus sans pénalité." else: if already_refused: msg += "Cela n'ajoute pas de pénalité." else: msg += "Cela ajoute une pénalité de 0.5 sur le coefficient de l'oral de læ défenseur⋅se." self.tournament.draw.last_message = msg await self.tournament.draw.asave() await self.channel_layer.group_send(f"team-{trigram}", {'type': 'draw.buttons_visibility', 'visible': False}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.buttons_visibility', 'visible': False}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.reject_problem', 'round': r.number, 'team': trigram, 'rejected': td.rejected}) if already_refused: next_td = td else: next_td = await pool.next_td() pool.current_team = next_td await pool.asave() new_trigram = next_td.participation.team.trigram await self.channel_layer.group_send(f"team-{new_trigram}", {'type': 'draw.box_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.box_visibility', 'visible': True}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_info', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) async def export(self, **kwargs): async for r in self.tournament.draw.round_set.all(): async for pool in r.pool_set.all(): if await pool.is_exportable(): await pool.export() await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.export_visibility', 'visible': False}) @ensure_orga async def continue_final(self, **kwargs): if not self.tournament.final: return await self.alert(_("This is only available for the final tournament."), 'danger') r2 = await self.tournament.draw.round_set.filter(number=2).aget() self.tournament.draw.current_round = r2 msg = "Le tirage au sort pour le tour 2 va commencer. " \ "L'ordre de passage est déterminé à partir du classement du premier tour." self.tournament.draw.last_message = msg await self.tournament.draw.asave() pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget() r2.current_pool = pool await r2.asave() notes = dict() async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all(): notes[participation] = sum([await pool.aaverage(participation) async for pool in self.tournament.pools.filter(participations=participation)\ .prefetch_related('passages').prefetch_related('tweaks') if pool.results_available]) ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x]) async for pool in r2.pool_set.order_by('letter').all(): for i in range(pool.size): participation = ordered_participations.pop(0) td = await TeamDraw.objects.aget(round=r2, participation=participation) td.pool = pool td.passage_index = i await td.asave() await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.send_poules', 'round': r2}) 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}) await self.channel_layer.group_send(f"team-{participation.team.trigram}", {'type': 'draw.dice_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.dice_visibility', 'visible': True}) await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", {'type': 'draw.continue_visibility', 'visible': False}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_info', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) async def draw_alert(self, content): return await self.alert(**content) async def draw_notify(self, content): await self.send_json({'type': 'notification', 'title': content['title'], 'body': content['body']}) async def draw_set_info(self, content): await self.send_json({'type': 'set_info', 'information': await content['draw'].ainformation()}) async def draw_dice(self, content): await self.send_json({'type': 'dice', 'team': content['team'], 'result': content['result']}) async def draw_dice_visibility(self, content): await self.send_json({'type': 'dice_visibility', 'visible': content['visible']}) async def draw_box_visibility(self, content): await self.send_json({'type': 'box_visibility', 'visible': content['visible']}) async def draw_buttons_visibility(self, content): await self.send_json({'type': 'buttons_visibility', 'visible': content['visible']}) async def draw_export_visibility(self, content): await self.send_json({'type': 'export_visibility', 'visible': content['visible']}) async def draw_continue_visibility(self, content): await self.send_json({'type': 'continue_visibility', 'visible': content['visible']}) async def draw_send_poules(self, content): await self.send_json({'type': 'set_poules', 'round': content['round'].number, 'poules': [{'letter': pool.get_letter_display(), 'teams': await pool.atrigrams()} async for pool in content['round'].pool_set.order_by('letter').all()]}) async def draw_set_active(self, content): r = content['draw'].current_round await self.send_json({ 'type': 'set_active', 'round': r.number, 'poule': r.current_pool.get_letter_display() if r.current_pool else None, 'team': r.current_pool.current_team.participation.team.trigram \ if r.current_pool and r.current_pool.current_team else None, }) async def draw_set_problem(self, content): await self.send_json({'type': 'set_problem', 'round': content['round'], 'team': content['team'], 'problem': content['problem']}) async def draw_reject_problem(self, content): await self.send_json({'type': 'reject_problem', 'round': content['round'], 'team': content['team'], 'rejected': content['rejected']}) async def draw_reorder_pool(self, content): await self.send_json({'type': 'reorder_poule', 'round': content['round'], 'poule': content['pool'], 'teams': content['teams'], 'problems': content['problems']})