mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-10-31 06:09:52 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			328 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (C) 2023 by Animath
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from random import randint
 | |
| 
 | |
| from asgiref.sync import sync_to_async
 | |
| from channels.generic.websocket import AsyncJsonWebsocketConsumer
 | |
| 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):
 | |
|         tournament_id = self.scope['url_route']['kwargs']['tournament_id']
 | |
|         self.tournament = await Tournament.objects.filter(pk=tournament_id)\
 | |
|             .prefetch_related('draw__current_round__current_pool__current_team').aget()
 | |
| 
 | |
|         self.participations = []
 | |
|         async for participation in Participation.objects.filter(tournament=self.tournament, 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)
 | |
| 
 | |
|         match content['type']:
 | |
|             case 'start_draw':
 | |
|                 await self.start_draw(**content)
 | |
|             case 'dice':
 | |
|                 await self.process_dice(**content)
 | |
| 
 | |
|     @ensure_orga
 | |
|     async def start_draw(self, fmt, **kwargs):
 | |
|         print(fmt, kwargs)
 | |
|         try:
 | |
|             fmt = list(map(int, fmt.split('+')))
 | |
|         except ValueError as e:
 | |
|             return await self.alert(_("Invalid format"), 'danger')
 | |
| 
 | |
|         print(fmt, sum(fmt), len(self.participations))
 | |
| 
 | |
|         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')
 | |
| 
 | |
|         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 sync_to_async(draw.save)()
 | |
| 
 | |
|         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]})
 | |
| 
 | |
| 
 | |
|     async def process_dice(self, trigram: str | None = None, **kwargs):
 | |
|         if self.registration.is_volunteer:
 | |
|             participation = await Participation.objects.filter(team__trigram=trigram).prefetch_related('team').aget()
 | |
|         else:
 | |
|             participation = await Participation.objects.filter(team__participants=self.registration)\
 | |
|                 .prefetch_related('team').aget()
 | |
|             trigram = participation.team.trigram
 | |
| 
 | |
|         team_draw = await TeamDraw.objects.filter(participation=participation,
 | |
|                                                   round_id=self.tournament.draw.current_round_id).aget()
 | |
| 
 | |
|         state = await sync_to_async(self.tournament.draw.get_state)()
 | |
|         match state:
 | |
|             case 'DICE_SELECT_POULES':
 | |
|                 if team_draw.last_dice is not None:
 | |
|                     return await self.alert(_("You've already launched the dice."), 'danger')
 | |
|             case 'DICE_ORDER_POULE':
 | |
|                 if team_draw.last_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)
 | |
|         team_draw.last_dice = res
 | |
|         await sync_to_async(team_draw.save)()
 | |
| 
 | |
|         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,
 | |
|                                                   last_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.last_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.last_dice == v]
 | |
| 
 | |
|                     for dup in dups:
 | |
|                         dup.last_dice = None
 | |
|                         await sync_to_async(dup.save)()
 | |
|                         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.last_dice)
 | |
|             tds_copy = tds.copy()
 | |
| 
 | |
|             async for p in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).order_by('letter').all():
 | |
|                 while (c := await TeamDraw.objects.filter(pool=p).acount()) < p.size:
 | |
|                     td = tds_copy.pop(0)
 | |
|                     td.pool = p
 | |
|                     td.passage_index = c
 | |
|                     await sync_to_async(td.save)()
 | |
| 
 | |
|             if self.tournament.draw.current_round.number == 2 \
 | |
|                     and await self.tournament.draw.current_round.pool_set.acount() >= 2:
 | |
|                 # Check that we don't have a same pool as the first day
 | |
|                 async for p1 in Pool.objects.filter(round__draw=self.tournament.draw, number=1).all():
 | |
|                     async for p2 in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).all():
 | |
|                         if set(await p1.teamdraw_set.avalues('id')) \
 | |
|                                 == set(await p2.teamdraw_set.avalues('id')):
 | |
|                             await TeamDraw.objects.filter(round=self.tournament.draw.current_round)\
 | |
|                                 .aupdate(last_dice=None, pool=None, passage_index=None)
 | |
|                             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.alert',
 | |
|                                  'message': _('Two pools are identical. Please relaunch your dices.'),
 | |
|                                  'alert_type': 'warning'})
 | |
|                             return
 | |
| 
 | |
|             pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
 | |
|             self.tournament.draw.current_round.current_pool = pool
 | |
|             await sync_to_async(self.tournament.draw.current_round.save)()
 | |
| 
 | |
|             msg = "Les résultats des dés sont les suivants : "
 | |
|             msg += await sync_to_async(lambda: ", ".join(
 | |
|                 f"<strong>{td.participation.team.trigram}</strong> ({td.last_dice})"
 | |
|                 for td in self.tournament.draw.current_round.team_draws))()
 | |
|             msg += ". L'ordre de passage et les compositions des différentes poules sont affiché⋅es sur le côté."
 | |
|             self.tournament.draw.last_message = msg
 | |
|             await sync_to_async(self.tournament.draw.save)()
 | |
| 
 | |
|             await TeamDraw.objects.filter(round=self.tournament.draw.current_round).aupdate(last_dice=None)
 | |
|             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"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,
 | |
|                                                   last_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.last_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.last_dice == v]
 | |
| 
 | |
|                     for dup in dups:
 | |
|                         dup.last_dice = None
 | |
|                         await sync_to_async(dup.save)()
 | |
|                         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.last_dice)
 | |
|             for i, td in enumerate(tds):
 | |
|                 td.choose_index = i
 | |
|                 await sync_to_async(td.save)()
 | |
| 
 | |
|             pool.current_team = tds[0]
 | |
|             await sync_to_async(pool.save)()
 | |
| 
 | |
|             self.tournament.draw.last_message = ""
 | |
|             await sync_to_async(self.tournament.draw.save)()
 | |
| 
 | |
|             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_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(
 | |
|             await sync_to_async(lambda: {
 | |
|                 '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,
 | |
|             })())
 |