From 8245ba00630c1f50c71bdcfb35dd9cb7b6791a41 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Tue, 11 Apr 2023 23:05:58 +0200 Subject: [PATCH] Add Redis Channel Layer for the drawing system Signed-off-by: Emmy D'Anello --- draw/consumers.py | 136 +++++++++++++++++++++++++++++++---------- participation/views.py | 2 +- requirements.txt | 1 + tfjm/settings.py | 6 +- tfjm/settings_prod.py | 9 +++ 5 files changed, 121 insertions(+), 33 deletions(-) diff --git a/draw/consumers.py b/draw/consumers.py index e3b7af1..4843a9b 100644 --- a/draw/consumers.py +++ b/draw/consumers.py @@ -176,7 +176,15 @@ 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}) + {'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() @@ -191,9 +199,10 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): 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}) + {'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', 'draw': self.tournament.draw}) + {'type': 'draw.set_active', 'round': 1}) # Send notification to everyone await self.channel_layer.group_send(f"tournament-{self.tournament.id}", @@ -457,25 +466,44 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): {'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', - 'round': await self.tournament.draw.round_set.filter(number=2).aget()}) + 'round': r2.number, + 'poules': [ + { + 'letter': pool.get_letter_display(), + 'teams': await pool.atrigrams(), + } + 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', - 'round': self.tournament.draw.current_round}) + 'round': r.number, + 'poules': [ + { + 'letter': pool.get_letter_display(), + 'teams': await pool.atrigrams(), + } + async for pool in r.pool_set.order_by('letter').all() + ]}) # 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', 'draw': self.tournament.draw}) + {'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', 'draw': self.tournament.draw}) + {'type': 'draw.set_active', + 'round': r.number, + 'pool': pool.get_letter_display()}) async def process_dice_order_poule(self): """ Called when all teams of the current launched their dice to determine the choice order. Place teams into pools and order their passage. """ - pool = self.tournament.draw.current_round.current_pool + r = self.tournament.draw.current_round + pool = r.current_pool tds = [td async for td in TeamDraw.objects.filter(pool=pool).prefetch_related('participation__team')] # Order teams by decreasing dice score @@ -493,9 +521,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # Update information header await self.channel_layer.group_send(f"tournament-{self.tournament.id}", - {'type': 'draw.set_info', 'draw': self.tournament.draw}) + {'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', 'draw': self.tournament.draw}) + {'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}", @@ -566,7 +598,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): 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}) + {'type': 'draw.set_info', + 'info': await self.tournament.draw.ainformation()}) async def accept_problem(self, **kwargs): """ @@ -637,11 +670,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): else: # Pool is ended await self.end_pool(pool) + r = self.tournament.draw.current_round + pool = r.current_pool await self.channel_layer.group_send(f"tournament-{self.tournament.id}", - {'type': 'draw.set_info', 'draw': self.tournament.draw}) + {'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', 'draw': self.tournament.draw}) + {'type': 'draw.set_active', + 'round': r.number, + 'pool': pool.get_letter_display(), + 'team': pool.current_team.participation.team.trigram + if pool.current_team else None}) async def end_pool(self, pool: Pool) -> None: """ @@ -736,7 +776,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # Reorder dices await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.send_poules', - 'round': r2}) + 'round': r2.number, + 'poules': [ + { + 'letter': pool.get_letter_display(), + 'teams': await pool.atrigrams(), + } + async for pool in r2.pool_set.order_by('letter').all() + ]}) # The passage order for the second round is already determined by the first round # Start the first pool of the second round @@ -831,9 +878,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): {'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}) + {'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', 'draw': self.tournament.draw}) + {'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}", @@ -900,7 +951,15 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # Send pools to users await self.channel_layer.group_send(f"tournament-{self.tournament.id}", - {'type': 'draw.send_poules', 'round': r2}) + {'type': 'draw.send_poules', + 'round': r2.number, + 'poules': [ + { + 'letter': pool.get_letter_display(), + 'teams': await pool.atrigrams(), + } + async for pool in r2.pool_set.order_by('letter').all() + ]}) # Reset dices and update interface for participation in self.participations: @@ -923,9 +982,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): {'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}) + {'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', 'draw': self.tournament.draw}) + {'type': 'draw.set_active', + 'round': r2.number, + 'pool': r2.current_pool.get_letter_display()}) @ensure_orga async def cancel_last_step(self, **kwargs): @@ -952,9 +1014,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): await self.undo_order_dice() await self.channel_layer.group_send(f"tournament-{self.tournament.id}", - {'type': 'draw.set_info', 'draw': self.tournament.draw}) + {'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', 'draw': self.tournament.draw}) + {'type': 'draw.set_active', + 'round': r.number, + 'pool': p.get_letter_display() if p else None, + 'team': p.current_team.participation.team.trigram + if p and p.current_team else None}) async def undo_end_draw(self) -> None: """ @@ -1194,7 +1263,15 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): 'result': td.choice_dice}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", - {'type': 'draw.send_poules', 'round': r1}) + {'type': 'draw.send_poules', + 'round': r1.number, + 'poules': [ + { + 'letter': pool.get_letter_display(), + 'teams': await pool.atrigrams(), + } + async for pool in r1.pool_set.order_by('letter').all() + ]}) previous_pool = r1.current_pool @@ -1339,7 +1416,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): """ Set the information banner to the current user. """ - await self.send_json({'type': 'set_info', 'information': await content['draw'].ainformation()}) + await self.send_json({'type': 'set_info', 'information': content['info']}) async def draw_dice(self, content): """ @@ -1381,21 +1458,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): """ Send the pools and the teams to the current user to update the interface. """ - 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()]}) + await self.send_json({'type': 'set_poules', 'round': content['round'], + 'poules': content['poules']}) async def draw_set_active(self, content): """ Update the user interface to highlight the current team. """ - 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, + 'round': content.get('round', None), + 'poule': content.get('pool', None), + 'team': content.get('team', None), }) async def draw_set_problem(self, content): diff --git a/participation/views.py b/participation/views.py index 49b9c77..c1736c1 100644 --- a/participation/views.py +++ b/participation/views.py @@ -24,7 +24,7 @@ from django.utils.crypto import get_random_string from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View from django.views.generic.edit import FormMixin, ProcessFormView -from django_tables2 import SingleTableView, MultiTableMixin +from django_tables2 import MultiTableMixin, SingleTableView from magic import Magic from odf.opendocument import OpenDocumentSpreadsheet from odf.style import Style, TableCellProperties, TableColumnProperties, TextProperties diff --git a/requirements.txt b/requirements.txt index 51dfa6c..185e289 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ channels[daphne]~=4.0.0 +channels-redis~=4.0.0 crispy-bootstrap5~=0.7 Django>=4.1,<5.0 django-cas-server~=2.0 diff --git a/tfjm/settings.py b/tfjm/settings.py index b53c09c..ac94fab 100644 --- a/tfjm/settings.py +++ b/tfjm/settings.py @@ -79,6 +79,11 @@ if "test" not in sys.argv: # pragma: no cover 'mailer', ] +if os.getenv("TFJM_STAGE", "dev") == "prod": # pragma: no cover + INSTALLED_APPS += [ + 'channels_redis', + ] + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -268,7 +273,6 @@ FORBIDDEN_TRIGRAMS = [ "SEX", ] -# TODO: Use a redis server in production CHANNEL_LAYERS = { "default": { "BACKEND": "channels.layers.InMemoryChannelLayer" diff --git a/tfjm/settings_prod.py b/tfjm/settings_prod.py index 6a0a601..455a685 100644 --- a/tfjm/settings_prod.py +++ b/tfjm/settings_prod.py @@ -30,3 +30,12 @@ CSRF_COOKIE_SECURE = False CSRF_COOKIE_HTTPONLY = False X_FRAME_OPTIONS = 'DENY' SESSION_COOKIE_AGE = 60 * 60 * 3 + +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels_redis.core.RedisChannelLayer", + "CONFIG": { + "hosts": [(os.getenv('REDIS_SERVER_HOST', 'localhost'), os.getenv('REDIS_SERVER_PORT', 6379))], + }, + }, +}