diff --git a/draw/admin.py b/draw/admin.py index ce2362c..4d4b9e5 100644 --- a/draw/admin.py +++ b/draw/admin.py @@ -4,7 +4,7 @@ from django.contrib import admin from django.utils.translation import gettext_lazy as _ -from .models import Draw, Round, Pool, TeamDraw +from .models import Draw, Pool, Round, TeamDraw @admin.register(Draw) @@ -59,4 +59,4 @@ class TeamDrawAdmin(admin.ModelAdmin): @admin.display(ordering='round__number', description=_('round')) def view_round(self, record): - return record.round.get_number_display() \ No newline at end of file + return record.round.get_number_display() diff --git a/draw/consumers.py b/draw/consumers.py index a365db7..d6e93e7 100644 --- a/draw/consumers.py +++ b/draw/consumers.py @@ -8,9 +8,8 @@ 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 draw.models import Draw, Pool, Round, TeamDraw +from participation.models import Participation, Tournament from registration.models import Registration @@ -62,7 +61,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): 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: + 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 @@ -148,14 +147,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): try: # Parse format from string fmt: list[int] = sorted(map(int, fmt.split('+')), reverse=True) - except ValueError as _ignored: + except ValueError: return await self.alert(_("Invalid format"), 'danger') # Ensure that the number of teams is good 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') + _("The sum must be equal to the number of teams: expected {len}, got {sum}") + .format(len=len(self.participations), sum=sum(fmt)), 'danger') # The drawing system works with a maximum of 1 pool of 5 teams, which is already the case in the TFJM² if fmt.count(5) > 1: @@ -191,9 +190,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # Update user interface await self.channel_layer.group_send(f"tournament-{self.tournament.id}", - {'type': 'draw.start', 'fmt': fmt, 'draw': draw}) + {'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', 'draw': draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) @@ -207,7 +206,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): """ Send information to users that the draw has started. """ - await self.alert(_("The draw for the tournament {tournament} will start.")\ + 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]}) @@ -230,11 +229,10 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): """ Send information to users that the draw was aborted. """ - await self.alert(_("The draw for the tournament {tournament} is aborted.")\ + 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): """ Launch the dice for a team. @@ -332,13 +330,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # Get concerned TeamDraw objects if state == 'DICE_SELECT_POULES': - tds = [td async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id) \ - .prefetch_related('participation__team')] + tds = [td async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id) + .prefetch_related('participation__team')] dices = {td: td.passage_dice for td in tds} else: - tds = [td async for td in TeamDraw.objects\ - .filter(pool_id=self.tournament.draw.current_round.current_pool_id)\ - .prefetch_related('participation__team')] + tds = [td async for td in TeamDraw.objects + .filter(pool_id=self.tournament.draw.current_round.current_pool_id) + .prefetch_related('participation__team')] dices = {td: td.choice_dice for td in tds} values = list(dices.values()) @@ -408,8 +406,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # which is this specific pool since they are ordered by decreasing size. 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()] + 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: @@ -511,7 +509,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): # 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 !", - 'body': "C'est à vous de tirer un nouveau problème !"}) + 'body': "C'est à vous de tirer un nouveau problème !"}) async def select_problem(self, **kwargs): """ @@ -566,7 +564,7 @@ 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', 'draw': self.tournament.draw}) async def accept_problem(self, **kwargs): """ @@ -636,109 +634,127 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): 'body': "C'est à vous de tirer un nouveau problème !"}) 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() + await self.end_pool(pool) - # Send the reordered pool - 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], - }) + 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}) - msg += f"

Le tirage de la poule {pool.get_letter_display()}{r.number} est terminé. " \ - f"Le tableau récapitulatif est en bas." + async def end_pool(self, pool: Pool) -> None: + """ + End the pool, and pass to the next one, or to the next round, or end the draw. + :param pool: The pool to end. + """ + msg = self.tournament.draw.last_message + r = pool.round + + 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() + + # Send the reordered pool + 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(): + # There is a pool that does not have selected its problem, so we continue to the next pool + next_pool = await r.next_pool() + r.current_pool = next_pool + await r.asave() + + 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}) + # 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 !", + '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}) + else: + # Round is ended + await self.end_round(r) + + async def end_round(self, r: Round) -> None: + """ + End the round, and pass to the next one, or end the draw. + :param r: The current round. + """ + msg = self.tournament.draw.last_message + + 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() - if await r.teamdraw_set.filter(accepted__isnull=True).aexists(): - # There is a pool that does not have selected its problem, so we continue to the next pool - next_pool = await r.next_pool() - r.current_pool = next_pool - await r.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}) + # 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 !", + 'body': "C'est à vous de lancer le dé !"}) - 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}) - # 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 !", - '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', + 'round': r2}) - await self.channel_layer.group_send(f"tournament-{self.tournament.id}", + # 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}) - 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() + 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() - 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}) - - # 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 !", - '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', - '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}) + await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", + {'type': 'draw.export_visibility', 'visible': True}) async def reject_problem(self, **kwargs): """ @@ -813,7 +829,7 @@ 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', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) @@ -822,7 +838,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): {'type': 'draw.notify', 'title': "À votre tour !", 'body': "C'est à vous de tirer un nouveau problème !"}) - @ensure_orga async def export(self, **kwargs): """ @@ -867,8 +882,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): 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') + async for pool in self.tournament.pools.filter(participations=participation) + .prefetch_related('passages').prefetch_related('tweaks') if pool.results_available]) # Sort notes in a decreasing order ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x]) @@ -906,7 +921,7 @@ 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', 'draw': self.tournament.draw}) await self.channel_layer.group_send(f"tournament-{self.tournament.id}", {'type': 'draw.set_active', 'draw': self.tournament.draw}) @@ -981,8 +996,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): '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, + '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): diff --git a/draw/models.py b/draw/models.py index 3fb496c..b15c859 100644 --- a/draw/models.py +++ b/draw/models.py @@ -3,14 +3,13 @@ from asgiref.sync import sync_to_async from django.conf import settings -from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import QuerySet from django.urls import reverse_lazy from django.utils.text import format_lazy, slugify from django.utils.translation import gettext_lazy as _ - -from participation.models import Passage, Participation, Pool as PPool, Tournament +from participation.models import Participation, Passage, Pool as PPool, Tournament class Draw(models.Model): @@ -292,16 +291,16 @@ class Pool(models.Model): Returns a list of trigrams of the teams in this pool ordered by passage index. This property is synchronous. """ - return [td.participation.team.trigram for td in self.teamdraw_set.order_by('passage_index')\ - .prefetch_related('participation__team').all()] + return [td.participation.team.trigram for td in self.teamdraw_set.order_by('passage_index') + .prefetch_related('participation__team').all()] async def atrigrams(self) -> list[str]: """ Returns a list of trigrams of the teams in this pool ordered by passage index. This property is asynchronous. """ - return [td.participation.team.trigram async for td in self.teamdraw_set.order_by('passage_index')\ - .prefetch_related('participation__team').all()] + return [td.participation.team.trigram async for td in self.teamdraw_set.order_by('passage_index') + .prefetch_related('participation__team').all()] async def next_td(self) -> "TeamDraw": """ @@ -349,8 +348,8 @@ class Pool(models.Model): # Define the participations of the pool tds = [td async for td in self.team_draws.prefetch_related('participation')] - await self.associated_pool.participations.aset([td.participation async for td in self.team_draws\ - .prefetch_related('participation')]) + await self.associated_pool.participations.aset([td.participation async for td in self.team_draws + .prefetch_related('participation')]) await self.asave() # Define the passage matrix according to the number of teams diff --git a/draw/views.py b/draw/views.py index 37ef3fb..6e98e1c 100644 --- a/draw/views.py +++ b/draw/views.py @@ -3,8 +3,7 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin -from django.views.generic import TemplateView, DetailView - +from django.views.generic import TemplateView from participation.models import Tournament @@ -36,5 +35,4 @@ class DisplayView(LoginRequiredMixin, TemplateView): context['tournaments_simplified'] = [{'id': t.id, 'name': t.name} for t in tournaments] context['problems'] = settings.PROBLEMS - return context diff --git a/participation/forms.py b/participation/forms.py index 639469e..bc6c67a 100644 --- a/participation/forms.py +++ b/participation/forms.py @@ -8,7 +8,7 @@ from typing import Iterable from crispy_forms.bootstrap import InlineField from crispy_forms.helper import FormHelper -from crispy_forms.layout import Submit, Fieldset, Layout, Div +from crispy_forms.layout import Div, Fieldset, Submit from django import forms from django.contrib.auth.models import User from django.core.exceptions import ValidationError @@ -200,6 +200,7 @@ class PoolTeamsForm(forms.ModelForm): }), } + class AddJuryForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -242,7 +243,6 @@ class AddJuryForm(forms.ModelForm): fields = ('first_name', 'last_name', 'email',) - class UploadNotesForm(forms.Form): file = forms.FileField( label=_("CSV file:"), diff --git a/participation/models.py b/participation/models.py index 93031bc..29eec9e 100644 --- a/participation/models.py +++ b/participation/models.py @@ -285,7 +285,6 @@ class Tournament(models.Model): fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3] return '+'.join(map(str, sorted(fmt, reverse=True))) - def get_absolute_url(self): return reverse_lazy("participation:tournament_detail", args=(self.pk,)) diff --git a/participation/views.py b/participation/views.py index 77ca609..2636dae 100644 --- a/participation/views.py +++ b/participation/views.py @@ -718,6 +718,9 @@ class PoolUpdateTeamsView(VolunteerMixin, UpdateView): class PoolAddJurysView(VolunteerMixin, FormView, DetailView): + """ + This view lets organizers set jurys for a pool, without multiplying clicks. + """ model = Pool form_class = AddJuryForm template_name = 'participation/pool_add_jurys.html' @@ -731,21 +734,26 @@ class PoolAddJurysView(VolunteerMixin, FormView, DetailView): def form_valid(self, form): self.object = self.get_object() + # Save the user object first form.save() user = form.instance + # Create associated registration object to the new user reg = VolunteerRegistration.objects.create( user=user, professional_activity="Juré⋅e du tournoi " + self.object.tournament.name, ) + # Add the user in the jury self.object.juries.add(reg) self.object.save() reg.send_email_validation_link() + # Generate new password for the user password = get_random_string(16) user.set_password(password) user.save() + # Send welcome mail subject = "[TFJM²] " + str(_("New TFJM² jury account")) site = Site.objects.first() message = render_to_string('registration/mails/add_organizer.txt', dict(user=user, @@ -758,12 +766,14 @@ class PoolAddJurysView(VolunteerMixin, FormView, DetailView): domain=site.domain)) user.email_user(subject, message, html_message=html) - messages.success(self.request, _("The jury {name} has been successfully added!")\ + # Add notification + messages.success(self.request, _("The jury {name} has been successfully added!") .format(name=f"{user.first_name} {user.last_name}")) return super().form_valid(form) def form_invalid(self, form): + # This is useful since we have a FormView + a DetailView self.object = self.get_object() return super().form_invalid(form) diff --git a/registration/admin.py b/registration/admin.py index fe59a83..0ac81ef 100644 --- a/registration/admin.py +++ b/registration/admin.py @@ -6,7 +6,7 @@ from django.contrib.admin import ModelAdmin from django.utils.translation import gettext_lazy as _ from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin -from .models import CoachRegistration, Payment, ParticipantRegistration, Registration, \ +from .models import CoachRegistration, ParticipantRegistration, Payment, Registration, \ StudentRegistration, VolunteerRegistration @@ -26,6 +26,7 @@ class RegistrationAdmin(PolymorphicParentModelAdmin): def last_name(self, record): return record.user.last_name + @admin.register(ParticipantRegistration) class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin): list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed',) diff --git a/registration/models.py b/registration/models.py index 6af1ebc..d4dbea0 100644 --- a/registration/models.py +++ b/registration/models.py @@ -191,7 +191,6 @@ class ParticipantRegistration(Registration): def form_class(self): # pragma: no cover raise NotImplementedError - class Meta: verbose_name = _("participant registration") verbose_name_plural = _("participant registrations") diff --git a/tfjm/asgi.py b/tfjm/asgi.py index 126040c..3bec7e0 100644 --- a/tfjm/asgi.py +++ b/tfjm/asgi.py @@ -21,7 +21,8 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings') django_asgi_app = get_asgi_application() -import draw.routing +# useful since the import must be done after the application initialization +import draw.routing # noqa: E402, I202 application = ProtocolTypeRouter( { diff --git a/tfjm/templates/base.html b/tfjm/templates/base.html index b260bf6..7bc9212 100644 --- a/tfjm/templates/base.html +++ b/tfjm/templates/base.html @@ -173,7 +173,7 @@ {% endif %}
{% for message in messages %} -