mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 15:22:26 +01:00 
			
		
		
		
	Validate teams
This commit is contained in:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
 | 
			
		||||
                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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,60 @@
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </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>
 | 
			
		||||
 | 
			
		||||
    <h4>{% trans "Documents" %}</h4>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user