Validate teams
This commit is contained in:
parent
4534a278d7
commit
4d9b6ad2c5
|
@ -77,10 +77,18 @@ class AddTeamView(LoginRequiredMixin, CreateView):
|
||||||
form_class = TeamForm
|
form_class = TeamForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
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
|
team = form.instance
|
||||||
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
code = ""
|
code = ""
|
||||||
for _ in range(6):
|
for i in range(6):
|
||||||
code += random.choice(alphabet)
|
code += random.choice(alphabet)
|
||||||
team.access_code = code
|
team.access_code = code
|
||||||
team.validation_status = "0invalid"
|
team.validation_status = "0invalid"
|
||||||
|
@ -104,6 +112,23 @@ class JoinTeamView(LoginRequiredMixin, FormView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
team = form.cleaned_data["team"]
|
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.team = team
|
||||||
self.request.user.save()
|
self.request.user.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
@ -7,6 +11,17 @@ from tournament.models import Tournament, Team
|
||||||
|
|
||||||
|
|
||||||
class TournamentForm(forms.ModelForm):
|
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:
|
class Meta:
|
||||||
model = Tournament
|
model = Tournament
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -25,6 +40,14 @@ class OrganizerForm(forms.ModelForm):
|
||||||
model = TFJMUser
|
model = TFJMUser
|
||||||
fields = ('last_name', 'first_name', 'email', 'is_superuser',)
|
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):
|
def save(self, commit=True):
|
||||||
user = self.instance
|
user = self.instance
|
||||||
user.role = '0admin' if user.is_superuser else '1organizer'
|
user.role = '0admin' if user.is_superuser else '1organizer'
|
||||||
|
@ -32,10 +55,34 @@ class OrganizerForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class TeamForm(forms.ModelForm):
|
class TeamForm(forms.ModelForm):
|
||||||
|
tournament = forms.ModelChoiceField(
|
||||||
|
Tournament.objects.filter(date_inscription__gte=datetime.today(), final=False),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Team
|
model = Team
|
||||||
fields = ('name', 'trigram', 'tournament',)
|
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):
|
class JoinTeam(forms.Form):
|
||||||
access_code = forms.CharField(
|
access_code = forms.CharField(
|
||||||
|
@ -46,10 +93,16 @@ class JoinTeam(forms.Form):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
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"])
|
team = Team.objects.filter(access_code=cleaned_data["access_code"])
|
||||||
if not team.exists():
|
if not team.exists():
|
||||||
self.add_error('access_code', _("This access code is invalid."))
|
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
|
return cleaned_data
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import os
|
import os
|
||||||
from datetime import date
|
from datetime import date, datetime
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
@ -36,23 +36,38 @@ class Tournament(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
date_start = models.DateField(
|
date_start = models.DateField(
|
||||||
|
default=datetime.today,
|
||||||
verbose_name=_("date start"),
|
verbose_name=_("date start"),
|
||||||
)
|
)
|
||||||
|
|
||||||
date_end = models.DateField(
|
date_end = models.DateField(
|
||||||
|
default=datetime.today,
|
||||||
verbose_name=_("date end"),
|
verbose_name=_("date end"),
|
||||||
)
|
)
|
||||||
|
|
||||||
date_inscription = models.DateTimeField(
|
date_inscription = models.DateTimeField(
|
||||||
|
default=datetime.now,
|
||||||
verbose_name=_("date of registration closing"),
|
verbose_name=_("date of registration closing"),
|
||||||
)
|
)
|
||||||
|
|
||||||
date_solutions = models.DateTimeField(
|
date_solutions = models.DateTimeField(
|
||||||
|
default=datetime.now,
|
||||||
verbose_name=_("date of maximal solution submission"),
|
verbose_name=_("date of maximal solution submission"),
|
||||||
)
|
)
|
||||||
|
|
||||||
date_syntheses = models.DateTimeField(
|
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(
|
final = models.BooleanField(
|
||||||
|
@ -172,6 +187,11 @@ class Team(models.Model):
|
||||||
return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
|
return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
|
||||||
for user in self.participants]
|
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:
|
class Meta:
|
||||||
verbose_name = _("team")
|
verbose_name = _("team")
|
||||||
verbose_name_plural = _("teams")
|
verbose_name_plural = _("teams")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import random
|
import random
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from datetime import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
@ -21,21 +22,21 @@ from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable
|
||||||
|
|
||||||
class AdminMixin(LoginRequiredMixin):
|
class AdminMixin(LoginRequiredMixin):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.admin:
|
if not request.user.is_authenticated or not request.user.admin:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class OrgaMixin(LoginRequiredMixin):
|
class OrgaMixin(LoginRequiredMixin):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.organizes:
|
if not request.user.is_authenticated or not request.user.organizes:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TeamMixin(LoginRequiredMixin):
|
class TeamMixin(LoginRequiredMixin):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.team:
|
if not request.user.is_authenticated or not request.user.team:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -105,12 +106,14 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
|
||||||
model = Team
|
model = Team
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()\
|
if not request.user.is_authenticated or \
|
||||||
and self.get_object() != request.user.team:
|
(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
|
raise PermissionDenied
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
print(request.POST)
|
||||||
team = self.get_object()
|
team = self.get_object()
|
||||||
if "zip" in request.POST:
|
if "zip" in request.POST:
|
||||||
solutions = team.solutions.all()
|
solutions = team.solutions.all()
|
||||||
|
@ -128,13 +131,28 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
|
||||||
.format(_("Solutions for team {team}.zip")
|
.format(_("Solutions for team {team}.zip")
|
||||||
.format(team=str(team)).replace(" ", "%20"))
|
.format(team=str(team)).replace(" ", "%20"))
|
||||||
return resp
|
return resp
|
||||||
elif "leave" in request.POST:
|
elif "leave" in request.POST and request.user.participates:
|
||||||
request.user.team = None
|
request.user.team = None
|
||||||
request.user.save()
|
request.user.save()
|
||||||
if not team.users.exists():
|
if not team.users.exists():
|
||||||
team.delete()
|
team.delete()
|
||||||
return redirect('tournament:detail', pk=team.tournament.pk)
|
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()
|
team.delete()
|
||||||
return redirect('tournament:detail', pk=team.tournament.pk)
|
return redirect('tournament:detail', pk=team.tournament.pk)
|
||||||
|
|
||||||
|
@ -204,12 +222,18 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
|
||||||
solution = form.instance
|
solution = form.instance
|
||||||
solution.team = self.request.user.team
|
solution.team = self.request.user.team
|
||||||
solution.final = solution.team.selected_for_final
|
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)
|
prev_sol = Solution.objects.filter(problem=solution.problem, team=solution.team, final=solution.final)
|
||||||
for sol in prev_sol.all():
|
for sol in prev_sol.all():
|
||||||
sol.delete()
|
sol.delete()
|
||||||
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
id = ""
|
id = ""
|
||||||
for _ in range(64):
|
for i in range(64):
|
||||||
id += random.choice(alphabet)
|
id += random.choice(alphabet)
|
||||||
solution.file.name = id
|
solution.file.name = id
|
||||||
solution.save()
|
solution.save()
|
||||||
|
@ -302,13 +326,26 @@ class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
|
||||||
synthesis = form.instance
|
synthesis = form.instance
|
||||||
synthesis.team = self.request.user.team
|
synthesis.team = self.request.user.team
|
||||||
synthesis.final = synthesis.team.selected_for_final
|
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,
|
prev_syn = Synthesis.objects.filter(team=synthesis.team, round=synthesis.round, source=synthesis.source,
|
||||||
final=synthesis.final)
|
final=synthesis.final)
|
||||||
for syn in prev_syn.all():
|
for syn in prev_syn.all():
|
||||||
syn.delete()
|
syn.delete()
|
||||||
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
id = ""
|
id = ""
|
||||||
for _ in range(64):
|
for i in range(64):
|
||||||
id += random.choice(alphabet)
|
id += random.choice(alphabet)
|
||||||
synthesis.file.name = id
|
synthesis.file.name = id
|
||||||
synthesis.save()
|
synthesis.save()
|
||||||
|
|
|
@ -51,6 +51,60 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if user.participates and team.invalid %}
|
||||||
|
<hr>
|
||||||
|
{% if team.can_validate %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="engage">Je m'engage à participer à l'intégralité du TFJM²*</label>
|
||||||
|
<input type="checkbox" name="engage" id="engage" required/>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Attention !</strong> Une fois votre équipe validée, vous ne pourrez plus modifier le nom
|
||||||
|
de l'équipe, le trigramme ou la composition de l'équipe.
|
||||||
|
</div>
|
||||||
|
<input class="btn btn-success btn-block" type="submit" name="request_validation"
|
||||||
|
value="Demander la validation"/>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Pour demander à valider votre équipe, vous devez avoir au moins un encadrant, quatre participants
|
||||||
|
et soumis une autorisation de droit à l'image, une fiche sanitaire et une autorisation
|
||||||
|
parentale (si besoin) par participant, ainsi qu'une lettre de motivation à transmettre aux organisateurs.
|
||||||
|
Les encadrants doivent également fournir une autorisation de droit à l'image.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
En raison du changement de format du TFJM² 2020, il n'y a plus de document obligatoire à envoyer. Les autorisations
|
||||||
|
précédemment envoyées ont été détruites. Seules les lettres de motivation ont été conservées, mais leur envoi
|
||||||
|
n'est plus obligatoire.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if team.waiting %}
|
||||||
|
<hr>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "The team is waiting about validation." %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if user.admin %}
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="message">{% trans "Message addressed to the team:" %}</label>
|
||||||
|
<textarea class="form-control" id="message" name="message" placeholder="{% trans "Message..." %}"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="btn-group btn-block">
|
||||||
|
<button class="btn btn-danger" name="invalidate">{% trans "Invalidate team" %}</button>
|
||||||
|
<button class="btn btn-success" name="validate">{% trans "Validate team" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h4>{% trans "Documents" %}</h4>
|
<h4>{% trans "Documents" %}</h4>
|
||||||
|
|
Loading…
Reference in New Issue