diff --git a/apps/member/views.py b/apps/member/views.py index a3bcf93..98aa7b5 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -77,10 +77,18 @@ class AddTeamView(LoginRequiredMixin, CreateView): form_class = TeamForm def form_valid(self, form): + if self.request.user.organizes: + form.add_error('name', _("You can't organize and participate at the same time.")) + return self.form_invalid(form) + + if self.request.user.team: + form.add_error('name', _("You are already in a team.")) + return self.form_invalid(form) + team = form.instance alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" code = "" - for _ in range(6): + for i in range(6): code += random.choice(alphabet) team.access_code = code team.validation_status = "0invalid" @@ -104,6 +112,23 @@ class JoinTeamView(LoginRequiredMixin, FormView): def form_valid(self, form): team = form.cleaned_data["team"] + + if self.request.user.organizes: + form.add_error('access_code', _("You can't organize and participate at the same time.")) + return self.form_invalid(form) + + if self.request.user.team: + form.add_error('access_code', _("You are already in a team.")) + return self.form_invalid(form) + + if self.request.user.role == '2coach' and team.encadrants.size() == 3: + form.add_error('access_code', _("This team is full of coachs.")) + return self.form_invalid(form) + + if self.request.user.role == '3participant' and team.participants.size() == 5: + form.add_error('access_code', _("This team is full of participants.")) + return self.form_invalid(form) + self.request.user.team = team self.request.user.save() return super().form_valid(form) diff --git a/apps/tournament/forms.py b/apps/tournament/forms.py index f5feba7..2e50b64 100644 --- a/apps/tournament/forms.py +++ b/apps/tournament/forms.py @@ -1,3 +1,7 @@ +import os +import re +from datetime import datetime + from django import forms from django.utils.translation import gettext_lazy as _ @@ -7,6 +11,17 @@ from tournament.models import Tournament, Team class TournamentForm(forms.ModelForm): + def clean(self): + cleaned_data = super().clean() + + if not self.instance.pk: + if Tournament.objects.filter(name=cleaned_data["data"], year=os.getenv("TFJM_YEAR")): + self.add_error("name", _("This tournament already exists.")) + if cleaned_data["final"] and Tournament.objects.filter(final=True, year=os.getenv("TFJM_YEAR")): + self.add_error("name", _("The final tournament was already defined.")) + + return cleaned_data + class Meta: model = Tournament fields = '__all__' @@ -25,6 +40,14 @@ class OrganizerForm(forms.ModelForm): model = TFJMUser fields = ('last_name', 'first_name', 'email', 'is_superuser',) + def clean(self): + cleaned_data = super().clean() + + if TFJMUser.objects.filter(email=cleaned_data["email"], year=os.getenv("TFJM_YEAR")).exists(): + self.add_error("trigram", _("This organizer already exist.")) + + return cleaned_data + def save(self, commit=True): user = self.instance user.role = '0admin' if user.is_superuser else '1organizer' @@ -32,10 +55,34 @@ class OrganizerForm(forms.ModelForm): class TeamForm(forms.ModelForm): + tournament = forms.ModelChoiceField( + Tournament.objects.filter(date_inscription__gte=datetime.today(), final=False), + ) + class Meta: model = Team fields = ('name', 'trigram', 'tournament',) + def clean(self): + cleaned_data = super().clean() + + cleaned_data["trigram"] = cleaned_data["trigram"].upper() + + if not re.match("[A-Z]{3}", cleaned_data["trigram"]): + self.add_error("trigram", _("The trigram must be composed of three upcase letters.")) + + if not self.instance.pk: + if Team.objects.filter(trigram=cleaned_data["trigram"], year=os.getenv("TFJM_YEAR")).exists(): + self.add_error("trigram", _("This trigram is already used.")) + + if Team.objects.filter(name=cleaned_data["name"], year=os.getenv("TFJM_YEAR")).exists(): + self.add_error("name", _("This name is already used.")) + + if cleaned_data["tournament"].date_inscription < datetime.today(): + self.add_error("tournament", _("This tournament is already closed.")) + + return cleaned_data + class JoinTeam(forms.Form): access_code = forms.CharField( @@ -46,10 +93,16 @@ class JoinTeam(forms.Form): def clean(self): cleaned_data = super().clean() + if not re.match("[a-z0-9]{6}", cleaned_data["access_code"]): + self.add_error('access_code', _("The access code must be composed of 6 alphanumeric characters.")) + team = Team.objects.filter(access_code=cleaned_data["access_code"]) if not team.exists(): self.add_error('access_code', _("This access code is invalid.")) - cleaned_data["team"] = team.get() + team = team.get() + if not team.invalid: + self.add_error('access_code', _("The team is already validated.")) + cleaned_data["team"] = team return cleaned_data diff --git a/apps/tournament/models.py b/apps/tournament/models.py index 0e432e1..bef6e5c 100644 --- a/apps/tournament/models.py +++ b/apps/tournament/models.py @@ -1,5 +1,5 @@ import os -from datetime import date +from datetime import date, datetime from django.db import models from django.urls import reverse_lazy @@ -36,23 +36,38 @@ class Tournament(models.Model): ) date_start = models.DateField( + default=datetime.today, verbose_name=_("date start"), ) date_end = models.DateField( + default=datetime.today, verbose_name=_("date end"), ) date_inscription = models.DateTimeField( + default=datetime.now, verbose_name=_("date of registration closing"), ) date_solutions = models.DateTimeField( + default=datetime.now, verbose_name=_("date of maximal solution submission"), ) date_syntheses = models.DateTimeField( - verbose_name=_("date of maximal syntheses submission"), + default=datetime.now, + verbose_name=_("date of maximal syntheses submission for the first round"), + ) + + date_solutions_2 = models.DateTimeField( + default=datetime.now, + verbose_name=_("date when solutions of round 2 are available"), + ) + + date_syntheses_2 = models.DateTimeField( + default=datetime.now, + verbose_name=_("date of maximal syntheses submission for the second round"), ) final = models.BooleanField( @@ -172,6 +187,11 @@ class Team(models.Model): return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '' for user in self.participants] + @property + def can_validate(self): + # TODO In a normal time, team needs a motivation letter and authorizations. + return self.encadrants.exists() and self.participants.count() >= 4 + class Meta: verbose_name = _("team") verbose_name_plural = _("teams") diff --git a/apps/tournament/views.py b/apps/tournament/views.py index d4499d4..c19cdbb 100644 --- a/apps/tournament/views.py +++ b/apps/tournament/views.py @@ -1,5 +1,6 @@ import random import zipfile +from datetime import datetime from io import BytesIO from django.contrib.auth.mixins import LoginRequiredMixin @@ -21,21 +22,21 @@ from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable class AdminMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): - if not request.user.admin: + if not request.user.is_authenticated or not request.user.admin: raise PermissionDenied return super().dispatch(request, *args, **kwargs) class OrgaMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): - if not request.user.organizes: + if not request.user.is_authenticated or not request.user.organizes: raise PermissionDenied return super().dispatch(request, *args, **kwargs) class TeamMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): - if not request.user.team: + if not request.user.is_authenticated or not request.user.team: raise PermissionDenied return super().dispatch(request, *args, **kwargs) @@ -105,12 +106,14 @@ class TeamDetailView(LoginRequiredMixin, DetailView): model = Team def dispatch(self, request, *args, **kwargs): - if not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()\ - and self.get_object() != request.user.team: + if not request.user.is_authenticated or \ + (not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all() + and self.get_object() != request.user.team): raise PermissionDenied return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): + print(request.POST) team = self.get_object() if "zip" in request.POST: solutions = team.solutions.all() @@ -128,13 +131,28 @@ class TeamDetailView(LoginRequiredMixin, DetailView): .format(_("Solutions for team {team}.zip") .format(team=str(team)).replace(" ", "%20")) return resp - elif "leave" in request.POST: + elif "leave" in request.POST and request.user.participates: request.user.team = None request.user.save() if not team.users.exists(): team.delete() return redirect('tournament:detail', pk=team.tournament.pk) - elif "delete" in request.POST: + elif "request_validation" in request.POST and request.user.participates: + team.validation_status = "1waiting" + team.save() + # TODO Send mail + return redirect('tournament:team_detail', pk=team.pk) + elif "validate" in request.POST and request.user.organizes: + team.validation_status = "2valid" + team.save() + # TODO Send mail + return redirect('tournament:team_detail', pk=team.pk) + elif "invalidate" in request.POST and request.user.organizes: + team.validation_status = "0invalid" + team.save() + # TODO Send mail + return redirect('tournament:team_detail', pk=team.pk) + elif "delete" in request.POST and request.user.organizes: team.delete() return redirect('tournament:detail', pk=team.tournament.pk) @@ -204,12 +222,18 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView): solution = form.instance solution.team = self.request.user.team solution.final = solution.team.selected_for_final + + if datetime.now() > solution.tournament.date_solutions: + form.add_error('file', _("You can't publish your solution anymore. Deadline: {date:%m-%d-%Y %h:%M}.") + .format(date=solution.tournament.date_solutions)) + return super().form_invalid(form) + prev_sol = Solution.objects.filter(problem=solution.problem, team=solution.team, final=solution.final) for sol in prev_sol.all(): sol.delete() alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" id = "" - for _ in range(64): + for i in range(64): id += random.choice(alphabet) solution.file.name = id solution.save() @@ -302,13 +326,26 @@ class SynthesesView(TeamMixin, BaseFormView, SingleTableView): synthesis = form.instance synthesis.team = self.request.user.team synthesis.final = synthesis.team.selected_for_final + + if synthesis.round == '1' and datetime.now() > synthesis.tournament.date_syntheses: + form.add_error('file', _("You can't publish your synthesis anymore for the first round." + " Deadline: {date:%m-%d-%Y %h:%M}.") + .format(date=synthesis.tournament.date_syntheses)) + return super().form_invalid(form) + + if synthesis.round == '2' and datetime.now() > synthesis.tournament.date_syntheses_2: + form.add_error('file', _("You can't publish your synthesis anymore for the second round." + " Deadline: {date:%m-%d-%Y %h:%M}.") + .format(date=synthesis.tournament.date_syntheses_2)) + return super().form_invalid(form) + prev_syn = Synthesis.objects.filter(team=synthesis.team, round=synthesis.round, source=synthesis.source, final=synthesis.final) for syn in prev_syn.all(): syn.delete() alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" id = "" - for _ in range(64): + for i in range(64): id += random.choice(alphabet) synthesis.file.name = id synthesis.save() diff --git a/templates/tournament/team_detail.html b/templates/tournament/team_detail.html index cd08882..9e23910 100644 --- a/templates/tournament/team_detail.html +++ b/templates/tournament/team_detail.html @@ -51,6 +51,60 @@ {% endif %} + {% if user.participates and team.invalid %} +