mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 03:42:11 +01:00 
			
		
		
		
	Comment code, fix minor issues
This commit is contained in:
		@@ -1,23 +1,31 @@
 | 
			
		||||
from django.contrib.auth.admin import admin
 | 
			
		||||
 | 
			
		||||
from tournament.models import Team, Tournament, Pool, Payment
 | 
			
		||||
from .models import Team, Tournament, Pool, Payment
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Team)
 | 
			
		||||
class TeamAdmin(admin.ModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    Django admin page for teams.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Tournament)
 | 
			
		||||
class TournamentAdmin(admin.ModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    Django admin page for tournaments.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Pool)
 | 
			
		||||
class PoolAdmin(admin.ModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    Django admin page for pools.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Payment)
 | 
			
		||||
class PaymentAdmin(admin.ModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    Django admin page for payments.
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -3,5 +3,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentConfig(AppConfig):
 | 
			
		||||
    """
 | 
			
		||||
    The tournament app handles all that is related to the tournaments.
 | 
			
		||||
    """
 | 
			
		||||
    name = 'tournament'
 | 
			
		||||
    verbose_name = _('tournament')
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,11 @@ from tournament.models import Tournament, Team, Pool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Create and update tournaments.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Only organizers can organize tournaments. Well, that's pretty normal...
 | 
			
		||||
    organizers = forms.ModelMultipleChoiceField(
 | 
			
		||||
        TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer")).order_by('role'),
 | 
			
		||||
        label=_("Organizers"),
 | 
			
		||||
@@ -44,6 +49,10 @@ class TournamentForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OrganizerForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Register an organizer in the website.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = TFJMUser
 | 
			
		||||
        fields = ('last_name', 'first_name', 'email', 'is_superuser',)
 | 
			
		||||
@@ -64,6 +73,9 @@ class OrganizerForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TeamForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Add and update a team.
 | 
			
		||||
    """
 | 
			
		||||
    tournament = forms.ModelChoiceField(
 | 
			
		||||
        Tournament.objects.filter(date_inscription__gte=timezone.now(), final=False),
 | 
			
		||||
    )
 | 
			
		||||
@@ -94,6 +106,10 @@ class TeamForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JoinTeam(forms.Form):
 | 
			
		||||
    """
 | 
			
		||||
    Form to join a team with an access code.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    access_code = forms.CharField(
 | 
			
		||||
        label=_("Access code"),
 | 
			
		||||
        max_length=6,
 | 
			
		||||
@@ -117,6 +133,10 @@ class JoinTeam(forms.Form):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SolutionForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Form to upload a solution.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    problem = forms.ChoiceField(
 | 
			
		||||
        label=_("Problem"),
 | 
			
		||||
        choices=[(str(i), _("Problem #{problem:d}").format(problem=i)) for i in range(1, 9)],
 | 
			
		||||
@@ -128,12 +148,21 @@ class SolutionForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SynthesisForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Form to upload a synthesis.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Synthesis
 | 
			
		||||
        fields = ('file', 'source', 'round',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PoolForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Form to add a pool.
 | 
			
		||||
    Should not be used: prefer to pass by API and auto-add pools with the results of the draw.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    team1 = forms.ModelChoiceField(
 | 
			
		||||
        Team.objects.filter(validation_status="2valid").all(),
 | 
			
		||||
        empty_label=_("Choose a team..."),
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tournament(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    Store the information of a tournament.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        verbose_name=_("name"),
 | 
			
		||||
@@ -18,10 +22,12 @@ class Tournament(models.Model):
 | 
			
		||||
        'member.TFJMUser',
 | 
			
		||||
        related_name="organized_tournaments",
 | 
			
		||||
        verbose_name=_("organizers"),
 | 
			
		||||
        help_text=_("List of all organizers that can see and manipulate data of the tournament and the teams."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    size = models.PositiveSmallIntegerField(
 | 
			
		||||
        verbose_name=_("size"),
 | 
			
		||||
        help_text=_("Number of teams that are allowed to join the tournament."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    place = models.CharField(
 | 
			
		||||
@@ -31,6 +37,7 @@ class Tournament(models.Model):
 | 
			
		||||
 | 
			
		||||
    price = models.PositiveSmallIntegerField(
 | 
			
		||||
        verbose_name=_("price"),
 | 
			
		||||
        help_text=_("Price asked to participants. Free with a scholarship."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    description = models.TextField(
 | 
			
		||||
@@ -74,6 +81,7 @@ class Tournament(models.Model):
 | 
			
		||||
 | 
			
		||||
    final = models.BooleanField(
 | 
			
		||||
        verbose_name=_("final tournament"),
 | 
			
		||||
        help_text=_("It should be only one final tournament."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    year = models.PositiveIntegerField(
 | 
			
		||||
@@ -83,27 +91,43 @@ class Tournament(models.Model):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def teams(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get all teams that are registered to this tournament, with a distinction for the final tournament.
 | 
			
		||||
        """
 | 
			
		||||
        return self._teams if not self.final else Team.objects.filter(selected_for_final=True)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def linked_organizers(self):
 | 
			
		||||
        """
 | 
			
		||||
        Display a list of the organizers with links to their personal page.
 | 
			
		||||
        """
 | 
			
		||||
        return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
 | 
			
		||||
                for user in self.organizers.all()]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def solutions(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get all sent solutions for this tournament.
 | 
			
		||||
        """
 | 
			
		||||
        from member.models import Solution
 | 
			
		||||
        return Solution.objects.filter(final=self.final) if self.final \
 | 
			
		||||
            else Solution.objects.filter(team__tournament=self)
 | 
			
		||||
            else Solution.objects.filter(team__tournament=self, final=False)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def syntheses(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get all sent syntheses for this tournament.
 | 
			
		||||
        """
 | 
			
		||||
        from member.models import Synthesis
 | 
			
		||||
        return Synthesis.objects.filter(final=self.final) if self.final \
 | 
			
		||||
            else Synthesis.objects.filter(team__tournament=self)
 | 
			
		||||
            else Synthesis.objects.filter(team__tournament=self, final=False)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_final(cls):
 | 
			
		||||
        """
 | 
			
		||||
        Get the final tournament.
 | 
			
		||||
        This should exist and be unique.
 | 
			
		||||
        """
 | 
			
		||||
        return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
@@ -111,6 +135,12 @@ class Tournament(models.Model):
 | 
			
		||||
        verbose_name_plural = _("tournaments")
 | 
			
		||||
 | 
			
		||||
    def send_mail_to_organizers(self, template_name, subject="Contact TFJM²", **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Send a mail to all organizers of the tournament.
 | 
			
		||||
        The template of the mail should be found either in templates/mail_templates/<template_name>.html for the HTML
 | 
			
		||||
        version and in templates/mail_templates/<template_name>.txt for the plain text version.
 | 
			
		||||
        The context of the template contains the tournament and the user. Extra context can be given through the kwargs.
 | 
			
		||||
        """
 | 
			
		||||
        context = kwargs
 | 
			
		||||
        context["tournament"] = self
 | 
			
		||||
        for user in self.organizers.all():
 | 
			
		||||
@@ -130,6 +160,10 @@ class Tournament(models.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Team(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    Store information about a registered team.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        verbose_name=_("name"),
 | 
			
		||||
@@ -138,6 +172,7 @@ class Team(models.Model):
 | 
			
		||||
    trigram = models.CharField(
 | 
			
		||||
        max_length=3,
 | 
			
		||||
        verbose_name=_("trigram"),
 | 
			
		||||
        help_text=_("The trigram should be composed of 3 capitalize letters, that is a funny acronym for the team."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    tournament = models.ForeignKey(
 | 
			
		||||
@@ -145,6 +180,7 @@ class Team(models.Model):
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        related_name="_teams",
 | 
			
		||||
        verbose_name=_("tournament"),
 | 
			
		||||
        help_text=_("The tournament where the team is registered."),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    inscription_date = models.DateTimeField(
 | 
			
		||||
@@ -191,31 +227,59 @@ class Team(models.Model):
 | 
			
		||||
        return self.validation_status == "0invalid"
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def encadrants(self):
 | 
			
		||||
    def coaches(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get all coaches of a team.
 | 
			
		||||
        """
 | 
			
		||||
        return self.users.all().filter(role="2coach")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def linked_encadrants(self):
 | 
			
		||||
    def linked_coaches(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get a list of the coaches of a team with html links to their pages.
 | 
			
		||||
        """
 | 
			
		||||
        return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
 | 
			
		||||
                for user in self.encadrants]
 | 
			
		||||
                for user in self.coaches]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def participants(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get all particpants of a team, coaches excluded.
 | 
			
		||||
        """
 | 
			
		||||
        return self.users.all().filter(role="3participant")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def linked_participants(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get a list of the participants of a team with html links to their pages.
 | 
			
		||||
        """
 | 
			
		||||
        return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
 | 
			
		||||
                for user in self.participants]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def future_tournament(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the last tournament where the team is registered.
 | 
			
		||||
        Only matters if the team is selected for final: if this is the case, we return the final tournament.
 | 
			
		||||
        Useful for deadlines.
 | 
			
		||||
        """
 | 
			
		||||
        return Tournament.get_final() if self.selected_for_final else self.tournament
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def can_validate(self):
 | 
			
		||||
        """
 | 
			
		||||
        Check if a given team is able to ask for validation.
 | 
			
		||||
        A team can validate if:
 | 
			
		||||
        * All participants filled the photo consent
 | 
			
		||||
        * Minor participants filled the parental consent
 | 
			
		||||
        * Minor participants filled the sanitary plug
 | 
			
		||||
        * Teams sent their motivation letter
 | 
			
		||||
        * The team contains at least 4 participants
 | 
			
		||||
        * The team contains at least 1 coach
 | 
			
		||||
        """
 | 
			
		||||
        # TODO In a normal time, team needs a motivation letter and authorizations.
 | 
			
		||||
        return self.encadrants.exists() and self.participants.count() >= 4
 | 
			
		||||
        return self.coaches.exists() and self.participants.count() >= 4\
 | 
			
		||||
            and self.tournament.date_inscription <= timezone.now()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("team")
 | 
			
		||||
@@ -223,6 +287,12 @@ class Team(models.Model):
 | 
			
		||||
        unique_together = (('name', 'year',), ('trigram', 'year',),)
 | 
			
		||||
 | 
			
		||||
    def send_mail(self, template_name, subject="Contact TFJM²", **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Send a mail to all members of a team with a given template.
 | 
			
		||||
        The template of the mail should be found either in templates/mail_templates/<template_name>.html for the HTML
 | 
			
		||||
        version and in templates/mail_templates/<template_name>.txt for the plain text version.
 | 
			
		||||
        The context of the template contains the team and the user. Extra context can be given through the kwargs.
 | 
			
		||||
        """
 | 
			
		||||
        context = kwargs
 | 
			
		||||
        context["team"] = self
 | 
			
		||||
        for user in self.users.all():
 | 
			
		||||
@@ -236,6 +306,12 @@ class Team(models.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Pool(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    Store information of a pool.
 | 
			
		||||
    A pool is only a list of accessible solutions to some teams and some juries.
 | 
			
		||||
    TODO: check that the set of teams is equal to the set of the teams that have a solution in this set.
 | 
			
		||||
    TODO: Moreover, a team should send only one solution.
 | 
			
		||||
    """
 | 
			
		||||
    teams = models.ManyToManyField(
 | 
			
		||||
        Team,
 | 
			
		||||
        related_name="pools",
 | 
			
		||||
@@ -264,14 +340,24 @@ class Pool(models.Model):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def problems(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get problem numbers of the sent solutions as a list of integers.
 | 
			
		||||
        """
 | 
			
		||||
        return list(d["problem"] for d in self.solutions.values("problem").all())
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def tournament(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the concerned tournament.
 | 
			
		||||
        We assume that the pool is correct, so all solutions belong to the same tournament.
 | 
			
		||||
        """
 | 
			
		||||
        return self.solutions.first().tournament
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def syntheses(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the syntheses of the teams that are in this pool, for the correct round.
 | 
			
		||||
        """
 | 
			
		||||
        from member.models import Synthesis
 | 
			
		||||
        return Synthesis.objects.filter(team__in=self.teams.all(), round=self.round, final=self.tournament.final)
 | 
			
		||||
 | 
			
		||||
@@ -281,6 +367,10 @@ class Pool(models.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Payment(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    Store some information about payments, to recover data.
 | 
			
		||||
    TODO: handle it...
 | 
			
		||||
    """
 | 
			
		||||
    user = models.OneToOneField(
 | 
			
		||||
        'member.TFJMUser',
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@ from .models import Tournament, Team, Pool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentTable(tables.Table):
 | 
			
		||||
    """
 | 
			
		||||
    List all tournaments.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = tables.LinkColumn(
 | 
			
		||||
        "tournament:detail",
 | 
			
		||||
        args=[A("pk")],
 | 
			
		||||
@@ -31,6 +35,10 @@ class TournamentTable(tables.Table):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TeamTable(tables.Table):
 | 
			
		||||
    """
 | 
			
		||||
    Table of some teams. Can be filtered with a queryset (for example, teams of a tournament)
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = tables.LinkColumn(
 | 
			
		||||
        "tournament:team_detail",
 | 
			
		||||
        args=[A("pk")],
 | 
			
		||||
@@ -46,6 +54,10 @@ class TeamTable(tables.Table):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SolutionTable(tables.Table):
 | 
			
		||||
    """
 | 
			
		||||
    Display a table of some solutions.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    team = tables.LinkColumn(
 | 
			
		||||
        "tournament:team_detail",
 | 
			
		||||
        args=[A("team.pk")],
 | 
			
		||||
@@ -81,6 +93,10 @@ class SolutionTable(tables.Table):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SynthesisTable(tables.Table):
 | 
			
		||||
    """
 | 
			
		||||
    Display a table of some syntheses.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    team = tables.LinkColumn(
 | 
			
		||||
        "tournament:team_detail",
 | 
			
		||||
        args=[A("team.pk")],
 | 
			
		||||
@@ -116,6 +132,10 @@ class SynthesisTable(tables.Table):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PoolTable(tables.Table):
 | 
			
		||||
    """
 | 
			
		||||
    Display a table of some pools.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    problems = tables.Column(
 | 
			
		||||
        verbose_name=_("Problems"),
 | 
			
		||||
        orderable=False,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, P
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminMixin(LoginRequiredMixin):
 | 
			
		||||
    """
 | 
			
		||||
    If a view extends this mixin, then the view will be only accessible to administrators.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        if not request.user.is_authenticated or not request.user.admin:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
@@ -31,6 +35,10 @@ class AdminMixin(LoginRequiredMixin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OrgaMixin(LoginRequiredMixin):
 | 
			
		||||
    """
 | 
			
		||||
    If a view extends this mixin, then the view will be only accessible to administrators or organizers.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        if not request.user.is_authenticated or not request.user.organizes:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
@@ -38,6 +46,10 @@ class OrgaMixin(LoginRequiredMixin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TeamMixin(LoginRequiredMixin):
 | 
			
		||||
    """
 | 
			
		||||
    If a view extends this mixin, then the view will be only accessible to users that are registered in a team.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        if not request.user.is_authenticated or not request.user.team:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
@@ -45,6 +57,10 @@ class TeamMixin(LoginRequiredMixin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentListView(SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    Display the list of all tournaments, ordered by start date then name.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = Tournament
 | 
			
		||||
    table_class = TournamentTable
 | 
			
		||||
    extra_context = dict(title=_("Tournaments list"),)
 | 
			
		||||
@@ -64,6 +80,10 @@ class TournamentListView(SingleTableView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentCreateView(AdminMixin, CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Create a tournament. Only accessible to admins.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = Tournament
 | 
			
		||||
    form_class = TournamentForm
 | 
			
		||||
    extra_context = dict(title=_("Add tournament"),)
 | 
			
		||||
@@ -73,6 +93,11 @@ class TournamentCreateView(AdminMixin, CreateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentDetailView(DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    Display the detail of a tournament.
 | 
			
		||||
    Accessible to all, including not authenticated users.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = Tournament
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
@@ -96,7 +121,20 @@ class TournamentDetailView(DetailView):
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TournamentUpdateView(AdminMixin, UpdateView):
 | 
			
		||||
class TournamentUpdateView(OrgaMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    Update the data of a tournament.
 | 
			
		||||
    Reserved to admins and organizers of the tournament.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Restrict the view to organizers of tournaments, then process the request.
 | 
			
		||||
        """
 | 
			
		||||
        if self.request.user.role == "1volunteer" and self.request.user not in self.get_object().organizers.all():
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    model = Tournament
 | 
			
		||||
    form_class = TournamentForm
 | 
			
		||||
    extra_context = dict(title=_("Update tournament"),)
 | 
			
		||||
@@ -106,9 +144,16 @@ class TournamentUpdateView(AdminMixin, UpdateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    View the detail of a team.
 | 
			
		||||
    Restricted to this team, admins and organizers of its tournament.
 | 
			
		||||
    """
 | 
			
		||||
    model = Team
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Protect the page and process the request.
 | 
			
		||||
        """
 | 
			
		||||
        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):
 | 
			
		||||
@@ -116,7 +161,15 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def post(self, request, *args, **kwargs):
 | 
			
		||||
        print(request.POST)
 | 
			
		||||
        """
 | 
			
		||||
        Process POST requests. Supported requests:
 | 
			
		||||
        - get the solutions of the team as a ZIP archive
 | 
			
		||||
        - a user leaves its team (if the composition is not validated yet)
 | 
			
		||||
        - the team requests the validation
 | 
			
		||||
        - Organizers can validate or invalidate the request
 | 
			
		||||
        - Admins can delete teams
 | 
			
		||||
        - Admins can select teams for the final tournament
 | 
			
		||||
        """
 | 
			
		||||
        team = self.get_object()
 | 
			
		||||
        if "zip" in request.POST:
 | 
			
		||||
            solutions = team.solutions.all()
 | 
			
		||||
@@ -140,7 +193,7 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
            if not team.users.exists():
 | 
			
		||||
                team.delete()
 | 
			
		||||
            return redirect('tournament:detail', pk=team.tournament.pk)
 | 
			
		||||
        elif "request_validation" in request.POST and request.user.participates:
 | 
			
		||||
        elif "request_validation" in request.POST and request.user.participates and team.can_validate:
 | 
			
		||||
            team.validation_status = "1waiting"
 | 
			
		||||
            team.save()
 | 
			
		||||
            team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team)
 | 
			
		||||
@@ -159,6 +212,7 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
            team.delete()
 | 
			
		||||
            return redirect('tournament:detail', pk=team.tournament.pk)
 | 
			
		||||
        elif "select_final" in request.POST and request.user.admin and not team.selected_for_final and team.pools:
 | 
			
		||||
            # We copy all solutions for solutions for the final
 | 
			
		||||
            for solution in team.solutions.all():
 | 
			
		||||
                alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
 | 
			
		||||
                id = ""
 | 
			
		||||
@@ -194,6 +248,11 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TeamUpdateView(LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    Update the information about a team.
 | 
			
		||||
    Team members, admins and organizers are allowed to do this.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = Team
 | 
			
		||||
    form_class = TeamForm
 | 
			
		||||
    extra_context = dict(title=_("Update team"),)
 | 
			
		||||
@@ -206,6 +265,12 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddOrganizerView(AdminMixin, CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Add a new organizer account. No password is created, the user should reset its password using the link
 | 
			
		||||
    sent by mail. Only name and email are requested.
 | 
			
		||||
    Only admins are granted to do this.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = TFJMUser
 | 
			
		||||
    form_class = OrganizerForm
 | 
			
		||||
    extra_context = dict(title=_("Add organizer"),)
 | 
			
		||||
@@ -223,6 +288,10 @@ class AddOrganizerView(AdminMixin, CreateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    Upload and view solutions for a team.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = Solution
 | 
			
		||||
    table_class = SolutionTable
 | 
			
		||||
    form_class = SolutionForm
 | 
			
		||||
@@ -288,6 +357,11 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SolutionsOrgaListView(OrgaMixin, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    View all solutions sent by teams for the organized tournaments. Juries can view solutions of their pools.
 | 
			
		||||
    Organizers can download a ZIP archive for each organized tournament.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = Solution
 | 
			
		||||
    table_class = SolutionTable
 | 
			
		||||
    template_name = "tournament/solutions_orga_list.html"
 | 
			
		||||
@@ -333,6 +407,9 @@ class SolutionsOrgaListView(OrgaMixin, SingleTableView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    Upload and view syntheses for a team.
 | 
			
		||||
    """
 | 
			
		||||
    model = Synthesis
 | 
			
		||||
    table_class = SynthesisTable
 | 
			
		||||
    form_class = SynthesisForm
 | 
			
		||||
@@ -407,6 +484,10 @@ class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SynthesesOrgaListView(OrgaMixin, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    View all syntheses sent by teams for the organized tournaments. Juries can view syntheses of their pools.
 | 
			
		||||
    Organizers can download a ZIP archive for each organized tournament.
 | 
			
		||||
    """
 | 
			
		||||
    model = Synthesis
 | 
			
		||||
    table_class = SynthesisTable
 | 
			
		||||
    template_name = "tournament/syntheses_orga_list.html"
 | 
			
		||||
@@ -452,6 +533,10 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PoolListView(LoginRequiredMixin, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    View the list of visible pools.
 | 
			
		||||
    Admins see all, juries see their own pools, organizers see the pools of their tournaments.
 | 
			
		||||
    """
 | 
			
		||||
    model = Pool
 | 
			
		||||
    table_class = PoolTable
 | 
			
		||||
    extra_context = dict(title=_("Pools"))
 | 
			
		||||
@@ -469,6 +554,10 @@ class PoolListView(LoginRequiredMixin, SingleTableView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PoolCreateView(AdminMixin, CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Create a pool manually.
 | 
			
		||||
    This page should not be used: prefer send automatically data from the drawing bot.
 | 
			
		||||
    """
 | 
			
		||||
    model = Pool
 | 
			
		||||
    form_class = PoolForm
 | 
			
		||||
    extra_context = dict(title=_("Create pool"))
 | 
			
		||||
@@ -478,6 +567,15 @@ class PoolCreateView(AdminMixin, CreateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PoolDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    See the detail of a pool.
 | 
			
		||||
    Teams and juries can download here defended solutions of the pool.
 | 
			
		||||
    If this is the second round, teams can't download solutions of the other teams before the date when they
 | 
			
		||||
    should be available.
 | 
			
		||||
    Juries see also syntheses. They see of course solutions immediately.
 | 
			
		||||
    This is also true for organizers and admins.
 | 
			
		||||
    All can be downloaded as a ZIP archive.
 | 
			
		||||
    """
 | 
			
		||||
    model = Pool
 | 
			
		||||
    extra_context = dict(title=_("Pool detail"))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user