Add comments and linting

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2023-04-05 17:52:46 +02:00
parent 2840a15fd5
commit 7e212d011e
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
14 changed files with 166 additions and 147 deletions

View File

@ -4,7 +4,7 @@
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext_lazy as _ 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) @admin.register(Draw)

View File

@ -8,9 +8,8 @@ from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.conf import settings from django.conf import settings
from django.utils import translation from django.utils import translation
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from draw.models import Draw, Pool, Round, TeamDraw
from draw.models import Draw, Round, Pool, TeamDraw from participation.models import Participation, Tournament
from participation.models import Tournament, Participation
from registration.models import Registration from registration.models import Registration
@ -148,13 +147,13 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
try: try:
# Parse format from string # Parse format from string
fmt: list[int] = sorted(map(int, fmt.split('+')), reverse=True) fmt: list[int] = sorted(map(int, fmt.split('+')), reverse=True)
except ValueError as _ignored: except ValueError:
return await self.alert(_("Invalid format"), 'danger') return await self.alert(_("Invalid format"), 'danger')
# Ensure that the number of teams is good # Ensure that the number of teams is good
if sum(fmt) != len(self.participations): if sum(fmt) != len(self.participations):
return await self.alert( return await self.alert(
_("The sum must be equal to the number of teams: expected {len}, got {sum}")\ _("The sum must be equal to the number of teams: expected {len}, got {sum}")
.format(len=len(self.participations), sum=sum(fmt)), 'danger') .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² # The drawing system works with a maximum of 1 pool of 5 teams, which is already the case in the TFJM²
@ -207,7 +206,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
""" """
Send information to users that the draw has started. 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') .format(tournament=self.tournament.name), 'warning')
await self.send_json({'type': 'draw_start', 'fmt': content['fmt'], await self.send_json({'type': 'draw_start', 'fmt': content['fmt'],
'trigrams': [p.team.trigram for p in self.participations]}) '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. 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') .format(tournament=self.tournament.name), 'danger')
await self.send_json({'type': 'abort'}) await self.send_json({'type': 'abort'})
async def process_dice(self, trigram: str | None = None, **kwargs): async def process_dice(self, trigram: str | None = None, **kwargs):
""" """
Launch the dice for a team. Launch the dice for a team.
@ -332,12 +330,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Get concerned TeamDraw objects # Get concerned TeamDraw objects
if state == 'DICE_SELECT_POULES': if state == 'DICE_SELECT_POULES':
tds = [td async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id) \ tds = [td async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id)
.prefetch_related('participation__team')] .prefetch_related('participation__team')]
dices = {td: td.passage_dice for td in tds} dices = {td: td.passage_dice for td in tds}
else: else:
tds = [td async for td in TeamDraw.objects\ tds = [td async for td in TeamDraw.objects
.filter(pool_id=self.tournament.draw.current_round.current_pool_id)\ .filter(pool_id=self.tournament.draw.current_round.current_pool_id)
.prefetch_related('participation__team')] .prefetch_related('participation__team')]
dices = {td: td.choice_dice for td in tds} dices = {td: td.choice_dice for td in tds}
@ -408,7 +406,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# which is this specific pool since they are ordered by decreasing size. # which is this specific pool since they are ordered by decreasing size.
tds_copy = tds.copy() tds_copy = tds.copy()
round2 = await self.tournament.draw.round_set.filter(number=2).aget() 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) \ round2_pools = [p async for p in Pool.objects.filter(round__draw__tournament=self.tournament, round=round2)
.order_by('letter').all()] .order_by('letter').all()]
current_pool_id, current_passage_index = 0, 0 current_pool_id, current_passage_index = 0, 0
for i, td in enumerate(tds_copy): for i, td in enumerate(tds_copy):
@ -636,6 +634,21 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'body': "C'est à vous de tirer un nouveau problème !"}) 'body': "C'est à vous de tirer un nouveau problème !"})
else: else:
# Pool is ended # Pool is ended
await self.end_pool(pool)
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 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: if pool.size == 5:
# Maybe reorder teams if the same problem is presented twice # Maybe reorder teams if the same problem is presented twice
problems = OrderedDict() problems = OrderedDict()
@ -679,7 +692,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
r.current_pool = next_pool r.current_pool = next_pool
await r.asave() await r.asave()
async for td in next_pool.team_draws.prefetch_related('participation__team').all(): 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}", await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.dice_visibility', 'visible': True}) {'type': 'draw.dice_visibility', 'visible': True})
@ -692,6 +704,15 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
{'type': 'draw.dice_visibility', 'visible': True}) {'type': 'draw.dice_visibility', 'visible': True})
else: else:
# Round is ended # 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: if r.number == 1 and not self.tournament.final:
# Next round # Next round
r2 = await self.tournament.draw.round_set.filter(number=2).aget() r2 = await self.tournament.draw.round_set.filter(number=2).aget()
@ -735,11 +756,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.export_visibility', 'visible': True}) {'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): async def reject_problem(self, **kwargs):
""" """
Called when a team accepts a problem. Called when a team accepts a problem.
@ -822,7 +838,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
{'type': 'draw.notify', 'title': "À votre tour !", {'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 !"})
@ensure_orga @ensure_orga
async def export(self, **kwargs): async def export(self, **kwargs):
""" """
@ -867,7 +882,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
notes = dict() notes = dict()
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all(): async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
notes[participation] = sum([await pool.aaverage(participation) notes[participation] = sum([await pool.aaverage(participation)
async for pool in self.tournament.pools.filter(participations=participation)\ async for pool in self.tournament.pools.filter(participations=participation)
.prefetch_related('passages').prefetch_related('tweaks') .prefetch_related('passages').prefetch_related('tweaks')
if pool.results_available]) if pool.results_available])
# Sort notes in a decreasing order # Sort notes in a decreasing order
@ -981,7 +996,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'type': 'set_active', 'type': 'set_active',
'round': r.number, 'round': r.number,
'poule': r.current_pool.get_letter_display() if r.current_pool else None, 'poule': r.current_pool.get_letter_display() if r.current_pool else None,
'team': r.current_pool.current_team.participation.team.trigram \ 'team': r.current_pool.current_team.participation.team.trigram
if r.current_pool and r.current_pool.current_team else None, if r.current_pool and r.current_pool.current_team else None,
}) })

View File

@ -3,14 +3,13 @@
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from django.conf import settings 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 import models
from django.db.models import QuerySet from django.db.models import QuerySet
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.text import format_lazy, slugify from django.utils.text import format_lazy, slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from participation.models import Participation, Passage, Pool as PPool, Tournament
from participation.models import Passage, Participation, Pool as PPool, Tournament
class Draw(models.Model): class Draw(models.Model):
@ -292,7 +291,7 @@ class Pool(models.Model):
Returns a list of trigrams of the teams in this pool ordered by passage index. Returns a list of trigrams of the teams in this pool ordered by passage index.
This property is synchronous. This property is synchronous.
""" """
return [td.participation.team.trigram for td in self.teamdraw_set.order_by('passage_index')\ return [td.participation.team.trigram for td in self.teamdraw_set.order_by('passage_index')
.prefetch_related('participation__team').all()] .prefetch_related('participation__team').all()]
async def atrigrams(self) -> list[str]: async def atrigrams(self) -> list[str]:
@ -300,7 +299,7 @@ class Pool(models.Model):
Returns a list of trigrams of the teams in this pool ordered by passage index. Returns a list of trigrams of the teams in this pool ordered by passage index.
This property is asynchronous. This property is asynchronous.
""" """
return [td.participation.team.trigram async for td in self.teamdraw_set.order_by('passage_index')\ return [td.participation.team.trigram async for td in self.teamdraw_set.order_by('passage_index')
.prefetch_related('participation__team').all()] .prefetch_related('participation__team').all()]
async def next_td(self) -> "TeamDraw": async def next_td(self) -> "TeamDraw":
@ -349,7 +348,7 @@ class Pool(models.Model):
# Define the participations of the pool # Define the participations of the pool
tds = [td async for td in self.team_draws.prefetch_related('participation')] 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\ await self.associated_pool.participations.aset([td.participation async for td in self.team_draws
.prefetch_related('participation')]) .prefetch_related('participation')])
await self.asave() await self.asave()

View File

@ -3,8 +3,7 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin 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 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['tournaments_simplified'] = [{'id': t.id, 'name': t.name} for t in tournaments]
context['problems'] = settings.PROBLEMS context['problems'] = settings.PROBLEMS
return context return context

View File

@ -8,7 +8,7 @@ from typing import Iterable
from crispy_forms.bootstrap import InlineField from crispy_forms.bootstrap import InlineField
from crispy_forms.helper import FormHelper 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 import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -200,6 +200,7 @@ class PoolTeamsForm(forms.ModelForm):
}), }),
} }
class AddJuryForm(forms.ModelForm): class AddJuryForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -242,7 +243,6 @@ class AddJuryForm(forms.ModelForm):
fields = ('first_name', 'last_name', 'email',) fields = ('first_name', 'last_name', 'email',)
class UploadNotesForm(forms.Form): class UploadNotesForm(forms.Form):
file = forms.FileField( file = forms.FileField(
label=_("CSV file:"), label=_("CSV file:"),

View File

@ -285,7 +285,6 @@ class Tournament(models.Model):
fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3] fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3]
return '+'.join(map(str, sorted(fmt, reverse=True))) return '+'.join(map(str, sorted(fmt, reverse=True)))
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("participation:tournament_detail", args=(self.pk,)) return reverse_lazy("participation:tournament_detail", args=(self.pk,))

View File

@ -718,6 +718,9 @@ class PoolUpdateTeamsView(VolunteerMixin, UpdateView):
class PoolAddJurysView(VolunteerMixin, FormView, DetailView): class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
"""
This view lets organizers set jurys for a pool, without multiplying clicks.
"""
model = Pool model = Pool
form_class = AddJuryForm form_class = AddJuryForm
template_name = 'participation/pool_add_jurys.html' template_name = 'participation/pool_add_jurys.html'
@ -731,21 +734,26 @@ class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
def form_valid(self, form): def form_valid(self, form):
self.object = self.get_object() self.object = self.get_object()
# Save the user object first
form.save() form.save()
user = form.instance user = form.instance
# Create associated registration object to the new user
reg = VolunteerRegistration.objects.create( reg = VolunteerRegistration.objects.create(
user=user, user=user,
professional_activity="Juré⋅e du tournoi " + self.object.tournament.name, professional_activity="Juré⋅e du tournoi " + self.object.tournament.name,
) )
# Add the user in the jury
self.object.juries.add(reg) self.object.juries.add(reg)
self.object.save() self.object.save()
reg.send_email_validation_link() reg.send_email_validation_link()
# Generate new password for the user
password = get_random_string(16) password = get_random_string(16)
user.set_password(password) user.set_password(password)
user.save() user.save()
# Send welcome mail
subject = "[TFJM²] " + str(_("New TFJM² jury account")) subject = "[TFJM²] " + str(_("New TFJM² jury account"))
site = Site.objects.first() site = Site.objects.first()
message = render_to_string('registration/mails/add_organizer.txt', dict(user=user, message = render_to_string('registration/mails/add_organizer.txt', dict(user=user,
@ -758,12 +766,14 @@ class PoolAddJurysView(VolunteerMixin, FormView, DetailView):
domain=site.domain)) domain=site.domain))
user.email_user(subject, message, html_message=html) 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}")) .format(name=f"{user.first_name} {user.last_name}"))
return super().form_valid(form) return super().form_valid(form)
def form_invalid(self, form): def form_invalid(self, form):
# This is useful since we have a FormView + a DetailView
self.object = self.get_object() self.object = self.get_object()
return super().form_invalid(form) return super().form_invalid(form)

View File

@ -6,7 +6,7 @@ from django.contrib.admin import ModelAdmin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin
from .models import CoachRegistration, Payment, ParticipantRegistration, Registration, \ from .models import CoachRegistration, ParticipantRegistration, Payment, Registration, \
StudentRegistration, VolunteerRegistration StudentRegistration, VolunteerRegistration
@ -26,6 +26,7 @@ class RegistrationAdmin(PolymorphicParentModelAdmin):
def last_name(self, record): def last_name(self, record):
return record.user.last_name return record.user.last_name
@admin.register(ParticipantRegistration) @admin.register(ParticipantRegistration)
class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin): class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin):
list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed',) list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed',)

View File

@ -191,7 +191,6 @@ class ParticipantRegistration(Registration):
def form_class(self): # pragma: no cover def form_class(self): # pragma: no cover
raise NotImplementedError raise NotImplementedError
class Meta: class Meta:
verbose_name = _("participant registration") verbose_name = _("participant registration")
verbose_name_plural = _("participant registrations") verbose_name_plural = _("participant registrations")

View File

@ -21,7 +21,8 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
django_asgi_app = get_asgi_application() 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( application = ProtocolTypeRouter(
{ {

View File

@ -173,7 +173,7 @@
{% endif %} {% endif %}
<div id="messages"> <div id="messages">
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade" role="alert"> <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ message | safe }} {{ message | safe }}
</div> </div>

View File

@ -3,8 +3,6 @@
import os import os
from django.core.handlers.asgi import ASGIHandler
from django.core.handlers.wsgi import WSGIHandler
from django.test import TestCase from django.test import TestCase

View File

@ -21,7 +21,6 @@ from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.views.defaults import bad_request, page_not_found, permission_denied, server_error from django.views.defaults import bad_request, page_not_found, permission_denied, server_error
from django.views.generic import TemplateView from django.views.generic import TemplateView
from participation.views import MotivationLetterView from participation.views import MotivationLetterView
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \ from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
ScholarshipView, SolutionView, SynthesisView, VaccineSheetView ScholarshipView, SolutionView, SynthesisView, VaccineSheetView

View File

@ -54,7 +54,7 @@ exclude =
.cache, .cache,
.eggs, .eggs,
*migrations* *migrations*
max-complexity = 10 max-complexity = 15
max-line-length = 160 max-line-length = 160
import-order-style = google import-order-style = google
application-import-names = flake8 application-import-names = flake8