mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 14:32:19 +01:00 
			
		
		
		
	Pool support
This commit is contained in:
		@@ -9,7 +9,7 @@ from rest_framework.filters import SearchFilter
 | 
				
			|||||||
from rest_framework.viewsets import ModelViewSet
 | 
					from rest_framework.viewsets import ModelViewSet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from member.models import TFJMUser, Authorization, Solution, Synthesis, MotivationLetter
 | 
					from member.models import TFJMUser, Authorization, Solution, Synthesis, MotivationLetter
 | 
				
			||||||
from tournament.models import Team, Tournament
 | 
					from tournament.models import Team, Tournament, Pool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserSerializer(serializers.ModelSerializer):
 | 
					class UserSerializer(serializers.ModelSerializer):
 | 
				
			||||||
@@ -59,6 +59,12 @@ class SynthesisSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
        fields = "__all__"
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Pool
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserViewSet(ModelViewSet):
 | 
					class UserViewSet(ModelViewSet):
 | 
				
			||||||
    queryset = TFJMUser.objects.all()
 | 
					    queryset = TFJMUser.objects.all()
 | 
				
			||||||
    serializer_class = UserSerializer
 | 
					    serializer_class = UserSerializer
 | 
				
			||||||
@@ -113,6 +119,13 @@ class SynthesisViewSet(ModelViewSet):
 | 
				
			|||||||
    filterset_fields = ['team', 'team__trigram', 'source', 'round', ]
 | 
					    filterset_fields = ['team', 'team__trigram', 'source', 'round', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    queryset = Pool.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = PoolSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend]
 | 
				
			||||||
 | 
					    filterset_fields = ['teams', 'teams__trigram', 'round', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Routers provide an easy way of automatically determining the URL conf.
 | 
					# Routers provide an easy way of automatically determining the URL conf.
 | 
				
			||||||
# Register each app API router and user viewset
 | 
					# Register each app API router and user viewset
 | 
				
			||||||
router = routers.DefaultRouter()
 | 
					router = routers.DefaultRouter()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import random
 | 
					import random
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
					from django.contrib.auth.mixins import LoginRequiredMixin
 | 
				
			||||||
from django.contrib.auth.models import AnonymousUser
 | 
					from django.contrib.auth.models import AnonymousUser
 | 
				
			||||||
@@ -151,6 +152,16 @@ class DocumentView(LoginRequiredMixin, View):
 | 
				
			|||||||
        if isinstance(doc, Solution) or isinstance(doc, Synthesis) or isinstance(doc, MotivationLetter):
 | 
					        if isinstance(doc, Solution) or isinstance(doc, Synthesis) or isinstance(doc, MotivationLetter):
 | 
				
			||||||
            grant = grant or doc.team == request.user.team or request.user in doc.team.tournament.organizers.all()
 | 
					            grant = grant or doc.team == request.user.team or request.user in doc.team.tournament.organizers.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if isinstance(doc, Synthesis) and request.user.organizes:
 | 
				
			||||||
 | 
					            grant = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if isinstance(doc, Solution):
 | 
				
			||||||
 | 
					            for pool in doc.pools.all():
 | 
				
			||||||
 | 
					                if pool.round == 2 and datetime.now() < doc.tournament.date_solutions_2:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                if self.request.user.team in pool.teams.all():
 | 
				
			||||||
 | 
					                    grant = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not grant:
 | 
					        if not grant:
 | 
				
			||||||
            raise PermissionDenied
 | 
					            raise PermissionDenied
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
from django.contrib.auth.admin import admin
 | 
					from django.contrib.auth.admin import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tournament.models import Team, Tournament, Payment
 | 
					from tournament.models import Team, Tournament, Pool, Payment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Team)
 | 
					@admin.register(Team)
 | 
				
			||||||
@@ -13,6 +13,11 @@ class TournamentAdmin(admin.ModelAdmin):
 | 
				
			|||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Pool)
 | 
				
			||||||
 | 
					class PoolAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Payment)
 | 
					@admin.register(Payment)
 | 
				
			||||||
class PaymentAdmin(admin.ModelAdmin):
 | 
					class PaymentAdmin(admin.ModelAdmin):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,20 @@ import re
 | 
				
			|||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
 | 
					from django.db.models import Q
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from member.models import TFJMUser, Solution, Synthesis
 | 
					from member.models import TFJMUser, Solution, Synthesis
 | 
				
			||||||
from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput
 | 
					from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput
 | 
				
			||||||
from tournament.models import Tournament, Team
 | 
					from tournament.models import Tournament, Team, Pool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentForm(forms.ModelForm):
 | 
					class TournamentForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    organizers = forms.ModelMultipleChoiceField(
 | 
				
			||||||
 | 
					        TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer")).order_by('role'),
 | 
				
			||||||
 | 
					        label=_("Organizers"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean(self):
 | 
					    def clean(self):
 | 
				
			||||||
        cleaned_data = super().clean()
 | 
					        cleaned_data = super().clean()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,3 +128,74 @@ class SynthesisForm(forms.ModelForm):
 | 
				
			|||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = Synthesis
 | 
					        model = Synthesis
 | 
				
			||||||
        fields = ('file', 'source', 'round',)
 | 
					        fields = ('file', 'source', 'round',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    team1 = forms.ModelChoiceField(
 | 
				
			||||||
 | 
					        Team.objects.filter(validation_status="2valid").all(),
 | 
				
			||||||
 | 
					        empty_label=_("Choose a team..."),
 | 
				
			||||||
 | 
					        label=_("Team 1"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    problem1 = forms.IntegerField(
 | 
				
			||||||
 | 
					        min_value=1,
 | 
				
			||||||
 | 
					        max_value=8,
 | 
				
			||||||
 | 
					        initial=1,
 | 
				
			||||||
 | 
					        label=_("Problem defended by team 1"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    team2 = forms.ModelChoiceField(
 | 
				
			||||||
 | 
					        Team.objects.filter(validation_status="2valid").all(),
 | 
				
			||||||
 | 
					        empty_label=_("Choose a team..."),
 | 
				
			||||||
 | 
					        label=_("Team 2"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    problem2 = forms.IntegerField(
 | 
				
			||||||
 | 
					        min_value=1,
 | 
				
			||||||
 | 
					        max_value=8,
 | 
				
			||||||
 | 
					        initial=2,
 | 
				
			||||||
 | 
					        label=_("Problem defended by team 2"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    team3 = forms.ModelChoiceField(
 | 
				
			||||||
 | 
					        Team.objects.filter(validation_status="2valid").all(),
 | 
				
			||||||
 | 
					        empty_label=_("Choose a team..."),
 | 
				
			||||||
 | 
					        label=_("Team 3"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    problem3 = forms.IntegerField(
 | 
				
			||||||
 | 
					        min_value=1,
 | 
				
			||||||
 | 
					        max_value=8,
 | 
				
			||||||
 | 
					        initial=3,
 | 
				
			||||||
 | 
					        label=_("Problem defended by team 3"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean(self):
 | 
				
			||||||
 | 
					        cleaned_data = super().clean()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        team1, pb1 = cleaned_data["team1"], cleaned_data["problem1"]
 | 
				
			||||||
 | 
					        team2, pb2 = cleaned_data["team2"], cleaned_data["problem2"]
 | 
				
			||||||
 | 
					        team3, pb3 = cleaned_data["team3"], cleaned_data["problem3"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sol1 = Solution.objects.get(team=team1, problem=pb1, final=team1.selected_for_final)
 | 
				
			||||||
 | 
					        sol2 = Solution.objects.get(team=team2, problem=pb2, final=team2.selected_for_final)
 | 
				
			||||||
 | 
					        sol3 = Solution.objects.get(team=team3, problem=pb3, final=team3.selected_for_final)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cleaned_data["teams"] = [team1, team2, team3]
 | 
				
			||||||
 | 
					        cleaned_data["solutions"] = [sol1, sol2, sol3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cleaned_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self, commit=True):
 | 
				
			||||||
 | 
					        pool = super().save(commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pool.refresh_from_db()
 | 
				
			||||||
 | 
					        pool.teams.set(self.cleaned_data["teams"])
 | 
				
			||||||
 | 
					        pool.solutions.set(self.cleaned_data["solutions"])
 | 
				
			||||||
 | 
					        pool.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return pool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Pool
 | 
				
			||||||
 | 
					        fields = ('round', 'juries',)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -198,7 +198,52 @@ class Team(models.Model):
 | 
				
			|||||||
        unique_together = (('name', 'year',), ('trigram', 'year',),)
 | 
					        unique_together = (('name', 'year',), ('trigram', 'year',),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return self.name
 | 
					        return self.trigram + " -- " + self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Pool(models.Model):
 | 
				
			||||||
 | 
					    teams = models.ManyToManyField(
 | 
				
			||||||
 | 
					        Team,
 | 
				
			||||||
 | 
					        related_name="pools",
 | 
				
			||||||
 | 
					        verbose_name=_("teams"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    solutions = models.ManyToManyField(
 | 
				
			||||||
 | 
					        "member.Solution",
 | 
				
			||||||
 | 
					        related_name="pools",
 | 
				
			||||||
 | 
					        verbose_name=_("solutions"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    round = models.PositiveIntegerField(
 | 
				
			||||||
 | 
					        choices=[
 | 
				
			||||||
 | 
					            (1, _("Round 1")),
 | 
				
			||||||
 | 
					            (2, _("Round 2")),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        verbose_name=_("round"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    juries = models.ManyToManyField(
 | 
				
			||||||
 | 
					        "member.TFJMUser",
 | 
				
			||||||
 | 
					        related_name="pools",
 | 
				
			||||||
 | 
					        verbose_name=_("juries"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def problems(self):
 | 
				
			||||||
 | 
					        return list(d["problem"] for d in self.solutions.values("problem").all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def tournament(self):
 | 
				
			||||||
 | 
					        return self.solutions.first().tournament
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def syntheses(self):
 | 
				
			||||||
 | 
					        from member.models import Synthesis
 | 
				
			||||||
 | 
					        return Synthesis.objects.filter(team__in=self.teams.all(), round=self.round, final=self.tournament.final)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        verbose_name = _("pool")
 | 
				
			||||||
 | 
					        verbose_name_plural = _("pools")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Payment(models.Model):
 | 
					class Payment(models.Model):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
 | 
				
			|||||||
from django_tables2 import A
 | 
					from django_tables2 import A
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from member.models import Solution, Synthesis
 | 
					from member.models import Solution, Synthesis
 | 
				
			||||||
from .models import Tournament, Team
 | 
					from .models import Tournament, Team, Pool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentTable(tables.Table):
 | 
					class TournamentTable(tables.Table):
 | 
				
			||||||
@@ -105,3 +105,21 @@ class SynthesisTable(tables.Table):
 | 
				
			|||||||
        attrs = {
 | 
					        attrs = {
 | 
				
			||||||
            'class': 'table table-condensed table-striped table-hover'
 | 
					            'class': 'table table-condensed table-striped table-hover'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolTable(tables.Table):
 | 
				
			||||||
 | 
					    def render_teams(self, value):
 | 
				
			||||||
 | 
					        return ", ".join(team.trigram for team in value.all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def render_problems(self, value):
 | 
				
			||||||
 | 
					        return ", ".join([str(pb) for pb in value])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def render_juries(self, value):
 | 
				
			||||||
 | 
					        return ", ".join(str(jury) for jury in value.all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Pool
 | 
				
			||||||
 | 
					        fields = ("teams", "problems", "round", "juries", )
 | 
				
			||||||
 | 
					        attrs = {
 | 
				
			||||||
 | 
					            'class': 'table table-condensed table-striped table-hover'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
from django.urls import path
 | 
					from django.urls import path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
 | 
					from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
 | 
				
			||||||
    TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView,\
 | 
					    TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView, \
 | 
				
			||||||
    SynthesesOrgaListView
 | 
					    SynthesesOrgaListView, PoolListView, PoolCreateView, PoolDetailView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app_name = "tournament"
 | 
					app_name = "tournament"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,4 +18,7 @@ urlpatterns = [
 | 
				
			|||||||
    path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
 | 
					    path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
 | 
				
			||||||
    path("syntheses/", SynthesesView.as_view(), name="syntheses"),
 | 
					    path("syntheses/", SynthesesView.as_view(), name="syntheses"),
 | 
				
			||||||
    path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
 | 
					    path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
 | 
				
			||||||
 | 
					    path("pools/", PoolListView.as_view(), name="pools"),
 | 
				
			||||||
 | 
					    path("pools/add/", PoolCreateView.as_view(), name="create_pool"),
 | 
				
			||||||
 | 
					    path("pool/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,9 +17,9 @@ from django.views.generic.edit import BaseFormView
 | 
				
			|||||||
from django_tables2.views import SingleTableView
 | 
					from django_tables2.views import SingleTableView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from member.models import TFJMUser, Solution, Synthesis
 | 
					from member.models import TFJMUser, Solution, Synthesis
 | 
				
			||||||
from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm
 | 
					from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm, PoolForm
 | 
				
			||||||
from .models import Tournament, Team
 | 
					from .models import Tournament, Team, Pool
 | 
				
			||||||
from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable
 | 
					from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, PoolTable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdminMixin(LoginRequiredMixin):
 | 
					class AdminMixin(LoginRequiredMixin):
 | 
				
			||||||
@@ -411,3 +411,73 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView):
 | 
				
			|||||||
        return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round',
 | 
					        return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round',
 | 
				
			||||||
                           'source',)
 | 
					                           'source',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolListView(LoginRequiredMixin, SingleTableView):
 | 
				
			||||||
 | 
					    model = Pool
 | 
				
			||||||
 | 
					    table_class = PoolTable
 | 
				
			||||||
 | 
					    extra_context = dict(title=_("Pools"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        qs = super().get_queryset()
 | 
				
			||||||
 | 
					        user = self.request.user
 | 
				
			||||||
 | 
					        if not user.admin and user.organizes:
 | 
				
			||||||
 | 
					            qs = qs.filter(Q(jurys=user) | Q(solutions__tournament__organizers=user))
 | 
				
			||||||
 | 
					        elif user.participates:
 | 
				
			||||||
 | 
					            qs = qs.filter(teams=user.team)
 | 
				
			||||||
 | 
					        return qs.distinct()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolCreateView(AdminMixin, CreateView):
 | 
				
			||||||
 | 
					    model = Pool
 | 
				
			||||||
 | 
					    form_class = PoolForm
 | 
				
			||||||
 | 
					    extra_context = dict(title=_("Create pool"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_success_url(self):
 | 
				
			||||||
 | 
					        return reverse_lazy("tournament:pools")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolDetailView(LoginRequiredMixin, DetailView):
 | 
				
			||||||
 | 
					    model = Pool
 | 
				
			||||||
 | 
					    extra_context = dict(title=_("Pool detail"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        qs = super().get_queryset()
 | 
				
			||||||
 | 
					        user = self.request.user
 | 
				
			||||||
 | 
					        if not user.admin and user.organizes:
 | 
				
			||||||
 | 
					            qs = qs.filter(Q(jurys=user) | Q(solutions__tournament__organizers=user))
 | 
				
			||||||
 | 
					        elif user.participates:
 | 
				
			||||||
 | 
					            qs = qs.filter(teams=user.team)
 | 
				
			||||||
 | 
					        return qs.distinct()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        user = request.user
 | 
				
			||||||
 | 
					        pool = self.get_object()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if "solutions_zip" in request.POST:
 | 
				
			||||||
 | 
					            out = BytesIO()
 | 
				
			||||||
 | 
					            zf = zipfile.ZipFile(out, "w")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for solution in pool.solutions.all():
 | 
				
			||||||
 | 
					                zf.write(solution.file.path, str(solution) + ".pdf")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            zf.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
 | 
				
			||||||
 | 
					            resp['Content-Disposition'] = 'attachment; filename={}' \
 | 
				
			||||||
 | 
					                .format(_("Solutions of a pool.zip").replace(" ", "%20"))
 | 
				
			||||||
 | 
					            return resp
 | 
				
			||||||
 | 
					        elif "syntheses_zip" in request.POST:
 | 
				
			||||||
 | 
					            out = BytesIO()
 | 
				
			||||||
 | 
					            zf = zipfile.ZipFile(out, "w")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for synthesis in pool.syntheses.all():
 | 
				
			||||||
 | 
					                zf.write(synthesis.file.path, str(synthesis) + ".pdf")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            zf.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
 | 
				
			||||||
 | 
					            resp['Content-Disposition'] = 'attachment; filename={}' \
 | 
				
			||||||
 | 
					                .format(_("Syntheses of a pool.zip").replace(" ", "%20"))
 | 
				
			||||||
 | 
					            return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.get(request, *args, **kwargs)
 | 
				
			||||||
@@ -129,6 +129,9 @@
 | 
				
			|||||||
                            <a class="nav-link" href="{% url "tournament:all_syntheses" %}"><i class="fas fa-feather"></i> {% trans "Syntheses" %}</a>
 | 
					                            <a class="nav-link" href="{% url "tournament:all_syntheses" %}"><i class="fas fa-feather"></i> {% trans "Syntheses" %}</a>
 | 
				
			||||||
                        </li>
 | 
					                        </li>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                    <li class="nav-item active">
 | 
				
			||||||
 | 
					                        <a class="nav-link" href="{% url "tournament:pools" %}"><i class="fas fa-swimming-pool"></i> {% trans "Pools" %}</a>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                <li class="nav-item active">
 | 
					                <li class="nav-item active">
 | 
				
			||||||
                    <a class="nav-link" href="https://www.helloasso.com/associations/animath/formulaires/5/widget"><i
 | 
					                    <a class="nav-link" href="https://www.helloasso.com/associations/animath/formulaires/5/widget"><i
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										70
									
								
								templates/tournament/pool_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								templates/tournament/pool_detail.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% load getconfig i18n django_tables2 %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    <div class="card bg-light shadow">
 | 
				
			||||||
 | 
					        <div class="card-header text-center">
 | 
				
			||||||
 | 
					            <h4>{{ title }}</h4>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <dl class="row">
 | 
				
			||||||
 | 
					                <dt class="col-xl-6 text-right">{% trans 'juries'|capfirst %}</dt>
 | 
				
			||||||
 | 
					                <dd class="col-xl-6">{{ pool.juries.all|join:", " }}</dd>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <dt class="col-xl-6 text-right">{% trans 'teams'|capfirst %}</dt>
 | 
				
			||||||
 | 
					                <dd class="col-xl-6">{{ pool.teams.all|join:", " }}</dd>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <dt class="col-xl-6 text-right">{% trans 'round'|capfirst %}</dt>
 | 
				
			||||||
 | 
					                <dd class="col-xl-6">{{ pool.round }}</dd>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <dt class="col-xl-6 text-right">{% trans 'tournament'|capfirst %}</dt>
 | 
				
			||||||
 | 
					                <dd class="col-xl-6">{{ pool.tournament }}</dd>
 | 
				
			||||||
 | 
					            </dl>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <hr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="card bg-light shadow">
 | 
				
			||||||
 | 
					        <div class="card-header text-center">
 | 
				
			||||||
 | 
					            <h4>{% trans "Solutions" %}</h4>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <ul>
 | 
				
			||||||
 | 
					                {% for solution in pool.solutions.all %}
 | 
				
			||||||
 | 
					                    <li><a data-turbolinks="false" href="{{ solution.file.url }}">{{ solution }}</a></li>
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-footer text-center">
 | 
				
			||||||
 | 
					            <form method="post">
 | 
				
			||||||
 | 
					                {% csrf_token %}
 | 
				
			||||||
 | 
					                <button class="btn btn-success" name="solutions_zip">{% trans "Download ZIP archive" %}</button>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% if user.organizes %}
 | 
				
			||||||
 | 
					        <hr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="card bg-light shadow">
 | 
				
			||||||
 | 
					            <div class="card-header text-center">
 | 
				
			||||||
 | 
					                <h4>{% trans "Syntheses" %}</h4>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="card-body">
 | 
				
			||||||
 | 
					                <ul>
 | 
				
			||||||
 | 
					                    {% for synthesis in pool.syntheses.all %}
 | 
				
			||||||
 | 
					                        <li><a data-turbolinks="false" href="{{ synthesis.file.url }}">{{ synthesis }}</a></li>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </ul>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="card-footer text-center">
 | 
				
			||||||
 | 
					                <form method="post">
 | 
				
			||||||
 | 
					                    {% csrf_token %}
 | 
				
			||||||
 | 
					                    <button class="btn btn-success" name="syntheses_zip">{% trans "Download ZIP archive" %}</button>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										11
									
								
								templates/tournament/pool_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								templates/tournament/pool_form.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% load i18n crispy_forms_filters %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    <form method="post">
 | 
				
			||||||
 | 
					        {% csrf_token %}
 | 
				
			||||||
 | 
					        {{ form|crispy }}
 | 
				
			||||||
 | 
					        <input type="submit" class="btn btn-primary btn-block" value="{% trans "Submit" %}">
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										12
									
								
								templates/tournament/pool_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								templates/tournament/pool_list.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% load i18n django_tables2 %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    {% render_table table %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% if user.admin %}
 | 
				
			||||||
 | 
					        <hr>
 | 
				
			||||||
 | 
					        <a href="{% url "tournament:create_pool" %}"><button class="btn btn-secondary btn-block">{% trans "Add pool" %}</button></a>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user