plateforme-tfjm2/apps/participation/forms.py

307 lines
11 KiB
Python
Raw Normal View History

2020-12-27 10:49:54 +00:00
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
2022-05-15 10:23:17 +00:00
import csv
from io import StringIO
import re
2022-05-15 10:23:17 +00:00
from typing import Iterable
2020-12-27 10:49:54 +00:00
from django import forms
2022-05-15 10:23:17 +00:00
from django.contrib.auth.models import User
2020-12-28 17:52:50 +00:00
from django.core.exceptions import ValidationError
2022-05-15 10:23:17 +00:00
from django.core.validators import FileExtensionValidator
2020-12-27 10:49:54 +00:00
from django.utils.translation import gettext_lazy as _
from pypdf import PdfFileReader
2021-01-17 15:23:48 +00:00
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
2020-12-27 10:49:54 +00:00
class TeamForm(forms.ModelForm):
"""
2021-01-12 14:42:32 +00:00
Form to create a team, with the name and the trigram,...
2020-12-27 10:49:54 +00:00
"""
2021-02-07 16:39:24 +00:00
def clean_name(self):
if "name" in self.cleaned_data:
2022-02-04 14:40:45 +00:00
name = self.cleaned_data["name"]
2021-02-07 16:39:24 +00:00
if not self.instance.pk and Team.objects.filter(name=name).exists():
raise ValidationError(_("This name is already used."))
return name
2020-12-27 10:49:54 +00:00
def clean_trigram(self):
2021-02-07 16:39:24 +00:00
if "trigram" in self.cleaned_data:
trigram = self.cleaned_data["trigram"].upper()
if not re.match("[A-Z]{3}", trigram):
raise ValidationError(_("The trigram must be composed of three uppercase letters."))
if not self.instance.pk and Team.objects.filter(trigram=trigram).exists():
raise ValidationError(_("This trigram is already used."))
return trigram
2020-12-27 10:49:54 +00:00
class Meta:
model = Team
2020-12-28 17:52:50 +00:00
fields = ('name', 'trigram',)
2020-12-27 10:49:54 +00:00
class JoinTeamForm(forms.ModelForm):
"""
Form to join a team by the access code.
"""
def clean_access_code(self):
access_code = self.cleaned_data["access_code"]
if not Team.objects.filter(access_code=access_code).exists():
raise ValidationError(_("No team was found with this access code."))
return access_code
def clean(self):
cleaned_data = super().clean()
if "access_code" in cleaned_data:
team = Team.objects.get(access_code=cleaned_data["access_code"])
self.instance = team
return cleaned_data
class Meta:
model = Team
fields = ('access_code',)
class ParticipationForm(forms.ModelForm):
"""
Form to update the problem of a team participation.
"""
class Meta:
model = Participation
fields = ('tournament', 'final',)
2020-12-27 10:49:54 +00:00
class MotivationLetterForm(forms.ModelForm):
def clean_file(self):
if "motivation_letter" in self.files:
file = self.files["motivation_letter"]
if file.size > 2e6:
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
return self.cleaned_data["motivation_letter"]
class Meta:
model = Team
fields = ('motivation_letter',)
2020-12-27 10:49:54 +00:00
class RequestValidationForm(forms.Form):
"""
Form to ask about validation.
"""
_form_type = forms.CharField(
initial="RequestValidationForm",
widget=forms.HiddenInput(),
)
engagement = forms.BooleanField(
2021-01-18 14:52:09 +00:00
label=_("I engage myself to participate to the whole TFJM²."),
2020-12-27 10:49:54 +00:00
required=True,
)
class ValidateParticipationForm(forms.Form):
"""
Form to let administrators to accept or refuse a team.
"""
_form_type = forms.CharField(
initial="ValidateParticipationForm",
widget=forms.HiddenInput(),
)
message = forms.CharField(
label=_("Message to address to the team:"),
widget=forms.Textarea(),
)
2020-12-31 11:13:42 +00:00
class TournamentForm(forms.ModelForm):
class Meta:
model = Tournament
fields = '__all__'
widgets = {
'date_start': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
'date_end': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
'inscription_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'solution_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'solutions_available_second_phase': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'organizers': forms.SelectMultiple(attrs={
'class': 'selectpicker',
'data-live-search': 'true',
'data-live-search-normalize': 'true',
'data-width': 'fit',
})
}
2021-01-12 16:24:46 +00:00
class SolutionForm(forms.ModelForm):
def clean_file(self):
if "file" in self.files:
file = self.files["file"]
2021-01-18 23:33:44 +00:00
if file.size > 5e6:
raise ValidationError(_("The uploaded file size must be under 5 Mo."))
if file.content_type != "application/pdf":
raise ValidationError(_("The uploaded file must be a PDF file."))
pdf_reader = PdfFileReader(file)
pages = len(pdf_reader.pages)
if pages > 30:
raise ValidationError(_("The PDF file must not have more than 30 pages."))
2021-04-03 20:02:53 +00:00
return self.cleaned_data["file"]
2021-01-12 16:24:46 +00:00
def save(self, commit=True):
"""
Don't save a solution with this way. Use a view instead
"""
class Meta:
model = Solution
fields = ('problem', 'file',)
2021-01-13 16:00:50 +00:00
class PoolForm(forms.ModelForm):
class Meta:
model = Pool
2021-04-10 08:02:49 +00:00
fields = ('tournament', 'round', 'bbb_url', 'results_available', 'juries',)
2021-01-14 13:44:12 +00:00
widgets = {
"juries": forms.SelectMultiple(attrs={
'class': 'selectpicker',
'data-live-search': 'true',
'data-live-search-normalize': 'true',
}),
2021-01-14 13:44:12 +00:00
}
class PoolTeamsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["participations"].queryset = self.instance.tournament.participations.all()
class Meta:
model = Pool
fields = ('participations',)
widgets = {
"participations": forms.SelectMultiple(attrs={
'class': 'selectpicker',
'data-live-search': 'true',
'data-live-search-normalize': 'true',
'data-width': 'fit',
}),
2021-01-14 13:44:12 +00:00
}
2021-01-14 14:59:11 +00:00
2022-05-15 10:23:17 +00:00
class UploadNotesForm(forms.Form):
file = forms.FileField(
label=_("CSV file:"),
validators=[FileExtensionValidator(allowed_extensions=["csv"])],
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['file'].widget.attrs['accept'] = 'text/csv'
def clean(self):
cleaned_data = super().clean()
if 'file' in cleaned_data:
file = cleaned_data['file']
with file:
try:
csvfile = csv.reader(StringIO(file.read().decode()))
except UnicodeDecodeError:
self.add_error('file', _("This file contains non-UTF-8 content. "
"Please send your sheet as a CSV file."))
self.process(csvfile, cleaned_data)
return cleaned_data
def process(self, csvfile: Iterable[str], cleaned_data: dict):
parsed_notes = {}
for line in csvfile:
line = [s for s in line if s]
if len(line) < 19:
continue
name = line[0]
notes = line[1:19]
if not all(s.isnumeric() for s in notes):
continue
notes = list(map(int, notes))
if max(notes) < 3 or min(notes) < 0:
continue
max_notes = 3 * [20, 16, 9, 10, 9, 10]
for n, max_n in zip(notes, max_notes):
if n > max_n:
self.add_error('file',
_("The following note is higher of the maximum expected value:")
+ str(n) + " > " + str(max_n))
first_name, last_name = tuple(name.split(' ', 1))
2022-05-15 14:20:43 +00:00
jury = User.objects.filter(first_name=first_name, last_name=last_name,
registration__volunteerregistration__isnull=False)
2022-05-15 10:23:17 +00:00
if jury.count() != 1:
2022-05-15 14:16:41 +00:00
self.add_error('file', _("The following user was not found:") + " " + name)
2022-05-15 10:23:17 +00:00
continue
jury = jury.get()
vr = jury.registration
parsed_notes[vr] = notes
cleaned_data['parsed_notes'] = parsed_notes
return cleaned_data
2021-01-14 14:59:11 +00:00
class PassageForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if "defender" in cleaned_data and "opponent" in cleaned_data and "reporter" in cleaned_data \
and len({cleaned_data["defender"], cleaned_data["opponent"], cleaned_data["reporter"]}) < 3:
self.add_error(None, _("The defender, the opponent and the reporter must be different."))
if "defender" in self.cleaned_data and "solution_number" in self.cleaned_data \
and not Solution.objects.filter(participation=cleaned_data["defender"],
problem=cleaned_data["solution_number"]).exists():
self.add_error("solution_number", _("This defender did not work on this problem."))
return cleaned_data
class Meta:
model = Passage
fields = ('solution_number', 'defender', 'opponent', 'reporter', 'defender_penalties',)
2021-01-14 16:26:08 +00:00
class SynthesisForm(forms.ModelForm):
2021-01-18 23:33:44 +00:00
def clean_file(self):
if "file" in self.files:
file = self.files["file"]
if file.size > 2e6:
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
if file.content_type != "application/pdf":
raise ValidationError(_("The uploaded file must be a PDF file."))
2021-04-03 20:02:53 +00:00
return self.cleaned_data["file"]
2021-01-18 23:33:44 +00:00
2021-01-14 16:26:08 +00:00
def save(self, commit=True):
"""
Don't save a synthesis with this way. Use a view instead
"""
class Meta:
model = Synthesis
2021-04-04 16:13:30 +00:00
fields = ('file',)
2021-01-14 17:21:22 +00:00
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ('defender_writing', 'defender_oral', 'opponent_writing',
'opponent_oral', 'reporter_writing', 'reporter_oral', )