From bde3758c509c5fad4d29247cb50f54435a9e7df6 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Wed, 22 Mar 2023 18:44:49 +0100 Subject: [PATCH] First interface to start draws Signed-off-by: Emmy D'Anello --- draw/consumers.py | 76 +++++++++++++++---- draw/models.py | 27 ++++--- draw/static/draw.js | 82 +++++++++++++-------- draw/templates/draw/index.html | 12 +-- draw/templates/draw/tournament_content.html | 31 ++++++-- draw/urls.py | 3 +- draw/views.py | 15 ++-- participation/models.py | 7 ++ 8 files changed, 182 insertions(+), 71 deletions(-) diff --git a/draw/consumers.py b/draw/consumers.py index 0f6f6e8..597b6f8 100644 --- a/draw/consumers.py +++ b/draw/consumers.py @@ -2,8 +2,23 @@ import json from asgiref.sync import sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer +from django.utils.translation import gettext_lazy as _ -from participation.models import Tournament +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): @@ -11,8 +26,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): tournament_id = self.scope['url_route']['kwargs']['tournament_id'] self.tournament = await sync_to_async(Tournament.objects.get)(pk=tournament_id) + self.participations = await sync_to_async(lambda: list(Participation.objects\ + .filter(tournament=self.tournament, valid=True)\ + .prefetch_related('team').all()))() + user = self.scope['user'] - reg = user.registration + reg = await sync_to_async(Registration.objects.get)(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 @@ -25,21 +45,45 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): async def disconnect(self, close_code): await self.channel_layer.group_discard(f"tournament-{self.tournament.id}", self.channel_name) + async def alert(self, message: str, alert_type: str = 'info'): + return await self.send_json({'type': 'alert', 'alert_type': alert_type, 'message': str(message)}) + async def receive_json(self, content, **kwargs): - message = content["message"] + print(content) - print(self.scope) + match content['type']: + case 'start_draw': + await self.start_draw(**content) - # TODO: Implement drawing system, instead of making a simple chatbot - await self.channel_layer.group_send( - f"tournament-{self.tournament.id}", - { - "type": "draw.message", - "username": self.scope["user"].username, - "message": message, - } - ) + @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') - async def draw_message(self, event): - print(event) - await self.send_json({"message": event['message']}) + 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 sync_to_async(Draw.objects.create)(tournament=self.tournament) + r = await sync_to_async(Round.objects.create)(draw=draw, number=1) + for i, f in enumerate(fmt): + sync_to_async(Pool.objects.create)(round=r, letter=i + 1, size=f) + for participation in self.participations: + sync_to_async(TeamDraw.objects.create)(participation=participation) + + await self.alert(_("Draw started!"), 'success') + + await self.channel_layer.group_send(f"tournament-{self.tournament.id}", + {'type': 'draw.start', 'fmt': fmt, 'draw': draw, 'round': r}) + + 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]}) diff --git a/draw/models.py b/draw/models.py index 73b5d5b..dcf66b2 100644 --- a/draw/models.py +++ b/draw/models.py @@ -36,7 +36,7 @@ class Round(models.Model): verbose_name=_('draw'), ) - number = models.IntegerField( + number = models.PositiveSmallIntegerField( choices=[ (1, _('Round 1')), (2, _('Round 2')), @@ -67,16 +67,19 @@ class Pool(models.Model): on_delete=models.CASCADE, ) - letter = models.CharField( - max_length=1, + letter = models.PositiveSmallIntegerField( choices=[ - ('A', 'A'), - ('B', 'B'), - ('C', 'C'), + (1, 'A'), + (2, 'B'), + (3, 'C'), ], verbose_name=_('letter'), ) + size = models.PositiveSmallIntegerField( + verbose_name=_('size'), + ) + current_team = models.ForeignKey( 'TeamDraw', on_delete=models.CASCADE, @@ -104,15 +107,19 @@ class TeamDraw(models.Model): pool = models.ForeignKey( Pool, on_delete=models.CASCADE, + null=True, + default=None, verbose_name=_('pool'), ) index = models.PositiveSmallIntegerField( choices=zip(range(1, 6), range(1, 6)), + null=True, + default=None, verbose_name=_('index'), ) - accepted = models.IntegerField( + accepted = models.PositiveSmallIntegerField( choices=[ (i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, settings.PROBLEM_COUNT + 1) ], @@ -121,12 +128,14 @@ class TeamDraw(models.Model): verbose_name=_("accepted problem"), ) - last_dice = models.IntegerField( + last_dice = models.PositiveSmallIntegerField( choices=zip(range(1, 101), range(1, 101)), + null=True, + default=None, verbose_name=_("last dice"), ) - purposed = models.IntegerField( + purposed = models.PositiveSmallIntegerField( choices=[ (i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, settings.PROBLEM_COUNT + 1) ], diff --git a/draw/static/draw.js b/draw/static/draw.js index e52c715..0d624c7 100644 --- a/draw/static/draw.js +++ b/draw/static/draw.js @@ -1,37 +1,61 @@ const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent) const sockets = {} -for (let tournament of tournaments) { - let socket = new WebSocket( - 'ws://' + window.location.host + '/ws/draw/' + tournament.id + '/' - ) - sockets[tournament.id] = socket +const messages = document.getElementById('messages') - // TODO: For now, we only have a chatbot. Need to implementthe drawing interface - socket.onmessage = function(e) { - console.log(e.data) - const data = JSON.parse(e.data) - console.log(data) - document.querySelector('#chat-log-' + tournament.id).value += (data.message + '\n') - } +document.addEventListener('DOMContentLoaded', () => { + for (let tournament of tournaments) { + let socket = new WebSocket( + 'ws://' + window.location.host + '/ws/draw/' + tournament.id + '/' + ) + sockets[tournament.id] = socket - socket.onclose = function(e) { - console.error('Chat socket closed unexpectedly') - } + function addMessage(message, type, timeout = 0) { + const wrapper = document.createElement('div') + wrapper.innerHTML = [ + `