mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-03 13:48:47 +01:00 
			
		
		
		
	Comment code, fix minor issues
This commit is contained in:
		@@ -3,5 +3,8 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APIConfig(AppConfig):
 | 
					class APIConfig(AppConfig):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Manage the inscription through a JSON API.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    name = 'api'
 | 
					    name = 'api'
 | 
				
			||||||
    verbose_name = _('API')
 | 
					    verbose_name = _('API')
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								apps/api/serializers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								apps/api/serializers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					from rest_framework import serializers
 | 
				
			||||||
 | 
					from member.models import TFJMUser, Authorization, MotivationLetter, Solution, Synthesis
 | 
				
			||||||
 | 
					from tournament.models import Team, Tournament, Pool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a User object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = TFJMUser
 | 
				
			||||||
 | 
					        exclude = (
 | 
				
			||||||
 | 
					            'username',
 | 
				
			||||||
 | 
					            'password',
 | 
				
			||||||
 | 
					            'groups',
 | 
				
			||||||
 | 
					            'user_permissions',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TeamSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a Team object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Team
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TournamentSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a Tournament object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Tournament
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthorizationSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize an Authorization object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Authorization
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MotivationLetterSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a MotivationLetter object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = MotivationLetter
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SolutionSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a Solution object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Solution
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SynthesisSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a Synthesis object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Synthesis
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Serialize a Pool object into JSON.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Pool
 | 
				
			||||||
 | 
					        fields = "__all__"
 | 
				
			||||||
							
								
								
									
										150
									
								
								apps/api/urls.py
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								apps/api/urls.py
									
									
									
									
									
								
							@@ -1,152 +1,8 @@
 | 
				
			|||||||
from django.conf.urls import url, include
 | 
					from django.conf.urls import url, include
 | 
				
			||||||
from django_filters.rest_framework import DjangoFilterBackend
 | 
					from rest_framework import routers
 | 
				
			||||||
from rest_framework import routers, serializers, status
 | 
					 | 
				
			||||||
from rest_framework.filters import SearchFilter
 | 
					 | 
				
			||||||
from rest_framework.response import Response
 | 
					 | 
				
			||||||
from rest_framework.viewsets import ModelViewSet
 | 
					 | 
				
			||||||
from member.models import TFJMUser, Authorization, Solution, Synthesis, MotivationLetter
 | 
					 | 
				
			||||||
from tournament.models import Team, Tournament, Pool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UserSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = TFJMUser
 | 
					 | 
				
			||||||
        exclude = (
 | 
					 | 
				
			||||||
            'email',
 | 
					 | 
				
			||||||
            'password',
 | 
					 | 
				
			||||||
            'groups',
 | 
					 | 
				
			||||||
            'user_permissions',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TeamSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Team
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TournamentSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Tournament
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AuthorizationSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Authorization
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MotivationLetterSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = MotivationLetter
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SolutionSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Solution
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SynthesisSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Synthesis
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PoolSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Pool
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UserViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = TFJMUser.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = UserSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
					 | 
				
			||||||
    filterset_fields = ['id', 'first_name', 'last_name', 'email', 'gender', 'student_class', 'role', 'year', 'team',
 | 
					 | 
				
			||||||
                        'team__trigram', 'is_superuser', 'is_staff', 'is_active', ]
 | 
					 | 
				
			||||||
    search_fields = ['$first_name', '$last_name', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TeamViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = Team.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = TeamSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
					 | 
				
			||||||
    filterset_fields = ['name', 'trigram', 'validation_status', 'selected_for_final', 'access_code', 'tournament',
 | 
					 | 
				
			||||||
                        'year', ]
 | 
					 | 
				
			||||||
    search_fields = ['$name', 'trigram', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TournamentViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = Tournament.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = TournamentSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
					 | 
				
			||||||
    filterset_fields = ['name', 'size', 'price', 'date_start', 'date_end', 'final', 'organizers', 'year', ]
 | 
					 | 
				
			||||||
    search_fields = ['$name', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AuthorizationViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = Authorization.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = AuthorizationSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend]
 | 
					 | 
				
			||||||
    filterset_fields = ['user', 'type', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MotivationLetterViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = MotivationLetter.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = MotivationLetterSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend]
 | 
					 | 
				
			||||||
    filterset_fields = ['team', 'team__trigram', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SolutionViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = Solution.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = SolutionSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend]
 | 
					 | 
				
			||||||
    filterset_fields = ['team', 'team__trigram', 'problem', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SynthesisViewSet(ModelViewSet):
 | 
					 | 
				
			||||||
    queryset = Synthesis.objects.all()
 | 
					 | 
				
			||||||
    serializer_class = SynthesisSerializer
 | 
					 | 
				
			||||||
    filter_backends = [DjangoFilterBackend]
 | 
					 | 
				
			||||||
    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', ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create(self, request, *args, **kwargs):
 | 
					 | 
				
			||||||
        data = request.data
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            spl = data.split(";")
 | 
					 | 
				
			||||||
            if len(spl) >= 7:
 | 
					 | 
				
			||||||
                round = int(spl[0])
 | 
					 | 
				
			||||||
                teams = []
 | 
					 | 
				
			||||||
                solutions = []
 | 
					 | 
				
			||||||
                for i in range((len(spl) - 1) // 2):
 | 
					 | 
				
			||||||
                    trigram = spl[1 + 2 * i]
 | 
					 | 
				
			||||||
                    pb = int(spl[2 + 2 * i])
 | 
					 | 
				
			||||||
                    team = Team.objects.get(trigram=trigram)
 | 
					 | 
				
			||||||
                    solution = Solution.objects.get(team=team, problem=pb, final=team.selected_for_final)
 | 
					 | 
				
			||||||
                    teams.append(team)
 | 
					 | 
				
			||||||
                    solutions.append(solution)
 | 
					 | 
				
			||||||
                pool = Pool.objects.create(round=round)
 | 
					 | 
				
			||||||
                pool.teams.set(teams)
 | 
					 | 
				
			||||||
                pool.solutions.set(solutions)
 | 
					 | 
				
			||||||
                pool.save()
 | 
					 | 
				
			||||||
                serializer = PoolSerializer(pool)
 | 
					 | 
				
			||||||
                headers = self.get_success_headers(serializer.data)
 | 
					 | 
				
			||||||
                return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
 | 
					 | 
				
			||||||
        except BaseException:  # JSON data
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        return super().create(request, *args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .viewsets import UserViewSet, TeamViewSet, TournamentViewSet, AuthorizationViewSet, MotivationLetterViewSet, \
 | 
				
			||||||
 | 
					    SolutionViewSet, SynthesisViewSet, PoolViewSet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										124
									
								
								apps/api/viewsets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								apps/api/viewsets.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					from django_filters.rest_framework import DjangoFilterBackend
 | 
				
			||||||
 | 
					from rest_framework import status
 | 
				
			||||||
 | 
					from rest_framework.filters import SearchFilter
 | 
				
			||||||
 | 
					from rest_framework.response import Response
 | 
				
			||||||
 | 
					from rest_framework.viewsets import ModelViewSet
 | 
				
			||||||
 | 
					from member.models import TFJMUser, Authorization, MotivationLetter, Solution, Synthesis
 | 
				
			||||||
 | 
					from tournament.models import Team, Tournament, Pool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .serializers import UserSerializer, TeamSerializer, TournamentSerializer, AuthorizationSerializer, \
 | 
				
			||||||
 | 
					    MotivationLetterSerializer, SolutionSerializer, SynthesisSerializer, PoolSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of users.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = TFJMUser.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = UserSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
				
			||||||
 | 
					    filterset_fields = ['id', 'first_name', 'last_name', 'email', 'gender', 'student_class', 'role', 'year', 'team',
 | 
				
			||||||
 | 
					                        'team__trigram', 'is_superuser', 'is_staff', 'is_active', ]
 | 
				
			||||||
 | 
					    search_fields = ['$first_name', '$last_name', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TeamViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of teams.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = Team.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = TeamSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
				
			||||||
 | 
					    filterset_fields = ['name', 'trigram', 'validation_status', 'selected_for_final', 'access_code', 'tournament',
 | 
				
			||||||
 | 
					                        'year', ]
 | 
				
			||||||
 | 
					    search_fields = ['$name', 'trigram', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TournamentViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of tournaments.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = Tournament.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = TournamentSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
				
			||||||
 | 
					    filterset_fields = ['name', 'size', 'price', 'date_start', 'date_end', 'final', 'organizers', 'year', ]
 | 
				
			||||||
 | 
					    search_fields = ['$name', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthorizationViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of authorizations.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = Authorization.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = AuthorizationSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend]
 | 
				
			||||||
 | 
					    filterset_fields = ['user', 'type', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MotivationLetterViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of motivation letters.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = MotivationLetter.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = MotivationLetterSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend]
 | 
				
			||||||
 | 
					    filterset_fields = ['team', 'team__trigram', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SolutionViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of solutions.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = Solution.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = SolutionSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend]
 | 
				
			||||||
 | 
					    filterset_fields = ['team', 'team__trigram', 'problem', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SynthesisViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of syntheses.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = Synthesis.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = SynthesisSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend]
 | 
				
			||||||
 | 
					    filterset_fields = ['team', 'team__trigram', 'source', 'round', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PoolViewSet(ModelViewSet):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display list of pools.
 | 
				
			||||||
 | 
					    If the request is a POST request and the format is "A;X;x;Y;y;Z;z;..." where A = 1 or 1 = 2,
 | 
				
			||||||
 | 
					     X, Y, Z, ... are team trigrams, x, y, z, ... are numbers of problems, then this is interpreted as a
 | 
				
			||||||
 | 
					     creation a pool for the round A with the solutions of problems x, y, z, ... of the teams X, Y, Z, ... respectively.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    queryset = Pool.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = PoolSerializer
 | 
				
			||||||
 | 
					    filter_backends = [DjangoFilterBackend]
 | 
				
			||||||
 | 
					    filterset_fields = ['teams', 'teams__trigram', 'round', ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        data = request.data
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            spl = data.split(";")
 | 
				
			||||||
 | 
					            if len(spl) >= 7:
 | 
				
			||||||
 | 
					                round = int(spl[0])
 | 
				
			||||||
 | 
					                teams = []
 | 
				
			||||||
 | 
					                solutions = []
 | 
				
			||||||
 | 
					                for i in range((len(spl) - 1) // 2):
 | 
				
			||||||
 | 
					                    trigram = spl[1 + 2 * i]
 | 
				
			||||||
 | 
					                    pb = int(spl[2 + 2 * i])
 | 
				
			||||||
 | 
					                    team = Team.objects.get(trigram=trigram)
 | 
				
			||||||
 | 
					                    solution = Solution.objects.get(team=team, problem=pb, final=team.selected_for_final)
 | 
				
			||||||
 | 
					                    teams.append(team)
 | 
				
			||||||
 | 
					                    solutions.append(solution)
 | 
				
			||||||
 | 
					                pool = Pool.objects.create(round=round)
 | 
				
			||||||
 | 
					                pool.teams.set(teams)
 | 
				
			||||||
 | 
					                pool.solutions.set(solutions)
 | 
				
			||||||
 | 
					                pool.save()
 | 
				
			||||||
 | 
					                serializer = PoolSerializer(pool)
 | 
				
			||||||
 | 
					                headers = self.get_success_headers(serializer.data)
 | 
				
			||||||
 | 
					                return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
 | 
				
			||||||
 | 
					        except BaseException:  # JSON data
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return super().create(request, *args, **kwargs)
 | 
				
			||||||
@@ -5,35 +5,51 @@ from member.models import TFJMUser, Document, Solution, Synthesis, MotivationLet
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@admin.register(TFJMUser)
 | 
					@admin.register(TFJMUser)
 | 
				
			||||||
class TFJMUserAdmin(UserAdmin):
 | 
					class TFJMUserAdmin(UserAdmin):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for users.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    list_display = ('email', 'first_name', 'last_name', 'role', )
 | 
					    list_display = ('email', 'first_name', 'last_name', 'role', )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Document)
 | 
					@admin.register(Document)
 | 
				
			||||||
class DocumentAdmin(PolymorphicParentModelAdmin):
 | 
					class DocumentAdmin(PolymorphicParentModelAdmin):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for any documents.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    child_models = (Authorization, MotivationLetter, Solution, Synthesis,)
 | 
					    child_models = (Authorization, MotivationLetter, Solution, Synthesis,)
 | 
				
			||||||
    polymorphic_list = True
 | 
					    polymorphic_list = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Authorization)
 | 
					@admin.register(Authorization)
 | 
				
			||||||
class AuthorizationAdmin(PolymorphicChildModelAdmin):
 | 
					class AuthorizationAdmin(PolymorphicChildModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for Authorization.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(MotivationLetter)
 | 
					@admin.register(MotivationLetter)
 | 
				
			||||||
class MotivationLetterAdmin(PolymorphicChildModelAdmin):
 | 
					class MotivationLetterAdmin(PolymorphicChildModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for Motivation letters.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Solution)
 | 
					@admin.register(Solution)
 | 
				
			||||||
class SolutionAdmin(PolymorphicChildModelAdmin):
 | 
					class SolutionAdmin(PolymorphicChildModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for solutions.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Synthesis)
 | 
					@admin.register(Synthesis)
 | 
				
			||||||
class SynthesisAdmin(PolymorphicChildModelAdmin):
 | 
					class SynthesisAdmin(PolymorphicChildModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for syntheses.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Config)
 | 
					@admin.register(Config)
 | 
				
			||||||
class ConfigAdmin(admin.ModelAdmin):
 | 
					class ConfigAdmin(admin.ModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for configurations.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,5 +3,8 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MemberConfig(AppConfig):
 | 
					class MemberConfig(AppConfig):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    The member app handles the information that concern a user, its documents, ...
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    name = 'member'
 | 
					    name = 'member'
 | 
				
			||||||
    verbose_name = _('member')
 | 
					    verbose_name = _('member')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,18 +2,22 @@ from django.contrib.auth.forms import UserCreationForm
 | 
				
			|||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from member.models import TFJMUser
 | 
					from .models import TFJMUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SignUpForm(UserCreationForm):
 | 
					class SignUpForm(UserCreationForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Coaches and participants register on the website through this form.
 | 
				
			||||||
 | 
					    TODO: Check if this form works, render it better
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
        super().__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
        self.fields["first_name"].required = True
 | 
					        self.fields["first_name"].required = True
 | 
				
			||||||
        self.fields["last_name"].required = True
 | 
					        self.fields["last_name"].required = True
 | 
				
			||||||
        self.fields["role"].choices = [
 | 
					        self.fields["role"].choices = [
 | 
				
			||||||
            ('', _("Choose a role...")),
 | 
					            ('', _("Choose a role...")),
 | 
				
			||||||
            ('participant', _("Participant")),
 | 
					            ('3participant', _("Participant")),
 | 
				
			||||||
            ('encadrant', _("Encadrant")),
 | 
					            ('2coach', _("Coach")),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -40,6 +44,9 @@ class SignUpForm(UserCreationForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TFJMUserForm(forms.ModelForm):
 | 
					class TFJMUserForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Form to update our own information when we are participant.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = TFJMUser
 | 
					        model = TFJMUser
 | 
				
			||||||
        fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
 | 
					        fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
 | 
				
			||||||
@@ -48,6 +55,9 @@ class TFJMUserForm(forms.ModelForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CoachUserForm(forms.ModelForm):
 | 
					class CoachUserForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Form to update our own information when we are coach.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = TFJMUser
 | 
					        model = TFJMUser
 | 
				
			||||||
        fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
 | 
					        fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
 | 
				
			||||||
@@ -55,6 +65,9 @@ class CoachUserForm(forms.ModelForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdminUserForm(forms.ModelForm):
 | 
					class AdminUserForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Form to update our own information when we are organizer or admin.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = TFJMUser
 | 
					        model = TFJMUser
 | 
				
			||||||
        fields = ('last_name', 'first_name', 'email', 'phone_number', 'description',)
 | 
					        fields = ('last_name', 'first_name', 'email', 'phone_number', 'description',)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,9 @@ from member.models import TFJMUser
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Command(BaseCommand):
 | 
					class Command(BaseCommand):
 | 
				
			||||||
    def handle(self, *args, **options):
 | 
					    def handle(self, *args, **options):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Little script that generate a superuser.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        email = input("Email: ")
 | 
					        email = input("Email: ")
 | 
				
			||||||
        password = "1"
 | 
					        password = "1"
 | 
				
			||||||
        confirm_password = "2"
 | 
					        confirm_password = "2"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core.management import BaseCommand, CommandError
 | 
					from django.core.management import BaseCommand, CommandError
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
from member.models import TFJMUser, Document, Solution, Synthesis, Authorization, MotivationLetter
 | 
					from member.models import TFJMUser, Document, Solution, Synthesis, Authorization, MotivationLetter
 | 
				
			||||||
@@ -5,6 +7,11 @@ from tournament.models import Team, Tournament
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Command(BaseCommand):
 | 
					class Command(BaseCommand):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Import the old database.
 | 
				
			||||||
 | 
					    Tables must be found into the import_olddb folder, as CSV files.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_arguments(self, parser):
 | 
					    def add_arguments(self, parser):
 | 
				
			||||||
        parser.add_argument('--tournaments', '-t', action="store", help="Import tournaments")
 | 
					        parser.add_argument('--tournaments', '-t', action="store", help="Import tournaments")
 | 
				
			||||||
        parser.add_argument('--teams', '-T', action="store", help="Import teams")
 | 
					        parser.add_argument('--teams', '-T', action="store", help="Import teams")
 | 
				
			||||||
@@ -26,6 +33,9 @@ class Command(BaseCommand):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
    def import_tournaments(self):
 | 
					    def import_tournaments(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Import tournaments into the new database.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        print("Importing tournaments...")
 | 
					        print("Importing tournaments...")
 | 
				
			||||||
        with open("import_olddb/tournaments.csv") as f:
 | 
					        with open("import_olddb/tournaments.csv") as f:
 | 
				
			||||||
            first_line = True
 | 
					            first_line = True
 | 
				
			||||||
@@ -75,6 +85,9 @@ class Command(BaseCommand):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
    def import_teams(self):
 | 
					    def import_teams(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Import teams into new database.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        self.stdout.write("Importing teams...")
 | 
					        self.stdout.write("Importing teams...")
 | 
				
			||||||
        with open("import_olddb/teams.csv") as f:
 | 
					        with open("import_olddb/teams.csv") as f:
 | 
				
			||||||
            first_line = True
 | 
					            first_line = True
 | 
				
			||||||
@@ -120,6 +133,10 @@ class Command(BaseCommand):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
    def import_users(self):
 | 
					    def import_users(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Import users into the new database.
 | 
				
			||||||
 | 
					        :return:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        self.stdout.write("Importing users...")
 | 
					        self.stdout.write("Importing users...")
 | 
				
			||||||
        with open("import_olddb/users.csv") as f:
 | 
					        with open("import_olddb/users.csv") as f:
 | 
				
			||||||
            first_line = True
 | 
					            first_line = True
 | 
				
			||||||
@@ -159,7 +176,7 @@ class Command(BaseCommand):
 | 
				
			|||||||
                    "team": Team.objects.get(pk=args[19]) if args[19] else None,
 | 
					                    "team": Team.objects.get(pk=args[19]) if args[19] else None,
 | 
				
			||||||
                    "year": args[20],
 | 
					                    "year": args[20],
 | 
				
			||||||
                    "date_joined": args[23],
 | 
					                    "date_joined": args[23],
 | 
				
			||||||
                    "is_active": args[18] == "ADMIN",  # TODO Replace it with "True"
 | 
					                    "is_active": args[18] == "ADMIN" or os.getenv("TFJM_STAGE", "dev") == "prod",
 | 
				
			||||||
                    "is_staff": args[18] == "ADMIN",
 | 
					                    "is_staff": args[18] == "ADMIN",
 | 
				
			||||||
                    "is_superuser": args[18] == "ADMIN",
 | 
					                    "is_superuser": args[18] == "ADMIN",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -168,6 +185,7 @@ class Command(BaseCommand):
 | 
				
			|||||||
        self.stdout.write(self.style.SUCCESS("Users imported"))
 | 
					        self.stdout.write(self.style.SUCCESS("Users imported"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.stdout.write("Importing organizers...")
 | 
					        self.stdout.write("Importing organizers...")
 | 
				
			||||||
 | 
					        # We also import the information about the organizers of a tournament.
 | 
				
			||||||
        with open("import_olddb/organizers.csv") as f:
 | 
					        with open("import_olddb/organizers.csv") as f:
 | 
				
			||||||
            first_line = True
 | 
					            first_line = True
 | 
				
			||||||
            for line in f:
 | 
					            for line in f:
 | 
				
			||||||
@@ -188,6 +206,9 @@ class Command(BaseCommand):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
    def import_documents(self):
 | 
					    def import_documents(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Import the documents (authorizations, motivation letters, solutions, syntheses) from the old database.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        self.stdout.write("Importing documents...")
 | 
					        self.stdout.write("Importing documents...")
 | 
				
			||||||
        with open("import_olddb/documents.csv") as f:
 | 
					        with open("import_olddb/documents.csv") as f:
 | 
				
			||||||
            first_line = True
 | 
					            first_line = True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,12 +10,16 @@ from tournament.models import Team, Tournament
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TFJMUser(AbstractUser):
 | 
					class TFJMUser(AbstractUser):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    The model of registered users (organizers/juries/admins/coachs/participants)
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    USERNAME_FIELD = 'email'
 | 
					    USERNAME_FIELD = 'email'
 | 
				
			||||||
    REQUIRED_FIELDS = []
 | 
					    REQUIRED_FIELDS = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    email = models.EmailField(
 | 
					    email = models.EmailField(
 | 
				
			||||||
        unique=True,
 | 
					        unique=True,
 | 
				
			||||||
        verbose_name=_("email"),
 | 
					        verbose_name=_("email"),
 | 
				
			||||||
 | 
					        help_text=_("This should be valid and will be controlled."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    team = models.ForeignKey(
 | 
					    team = models.ForeignKey(
 | 
				
			||||||
@@ -24,6 +28,7 @@ class TFJMUser(AbstractUser):
 | 
				
			|||||||
        on_delete=models.SET_NULL,
 | 
					        on_delete=models.SET_NULL,
 | 
				
			||||||
        related_name="users",
 | 
					        related_name="users",
 | 
				
			||||||
        verbose_name=_("team"),
 | 
					        verbose_name=_("team"),
 | 
				
			||||||
 | 
					        help_text=_("Concerns only coaches and participants."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    birth_date = models.DateField(
 | 
					    birth_date = models.DateField(
 | 
				
			||||||
@@ -141,14 +146,25 @@ class TFJMUser(AbstractUser):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def participates(self):
 | 
					    def participates(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return True iff this user is a participant or a coach, ie. if the user is a member of a team that worked
 | 
				
			||||||
 | 
					        for the tournament.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.role == "3participant" or self.role == "2coach"
 | 
					        return self.role == "3participant" or self.role == "2coach"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def organizes(self):
 | 
					    def organizes(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return True iff this user is a local or global organizer of the tournament. This includes juries.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.role == "1volunteer" or self.role == "0admin"
 | 
					        return self.role == "1volunteer" or self.role == "0admin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def admin(self):
 | 
					    def admin(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return True iff this user is a global organizer, ie. an administrator. This should be equivalent to be
 | 
				
			||||||
 | 
					        a superuser.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.role == "0admin"
 | 
					        return self.role == "0admin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -156,6 +172,7 @@ class TFJMUser(AbstractUser):
 | 
				
			|||||||
        verbose_name_plural = _("users")
 | 
					        verbose_name_plural = _("users")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save(self, *args, **kwargs):
 | 
					    def save(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        # We ensure that the username is the email of the user.
 | 
				
			||||||
        self.username = self.email
 | 
					        self.username = self.email
 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					        super().save(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,6 +181,9 @@ class TFJMUser(AbstractUser):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Document(PolymorphicModel):
 | 
					class Document(PolymorphicModel):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Abstract model of any saved document (solution, synthesis, motivation letter, authorization)
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    file = models.FileField(
 | 
					    file = models.FileField(
 | 
				
			||||||
        unique=True,
 | 
					        unique=True,
 | 
				
			||||||
        verbose_name=_("file"),
 | 
					        verbose_name=_("file"),
 | 
				
			||||||
@@ -184,6 +204,9 @@ class Document(PolymorphicModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Authorization(Document):
 | 
					class Authorization(Document):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Model for authorization papers (parental consent, photo consent, sanitary plug, ...)
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    user = models.ForeignKey(
 | 
					    user = models.ForeignKey(
 | 
				
			||||||
        TFJMUser,
 | 
					        TFJMUser,
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
@@ -211,6 +234,9 @@ class Authorization(Document):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MotivationLetter(Document):
 | 
					class MotivationLetter(Document):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Model for motivation letters of a team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    team = models.ForeignKey(
 | 
					    team = models.ForeignKey(
 | 
				
			||||||
        Team,
 | 
					        Team,
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
@@ -227,6 +253,9 @@ class MotivationLetter(Document):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Solution(Document):
 | 
					class Solution(Document):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Model for solutions of team for a given problem, for the regional or final tournament.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    team = models.ForeignKey(
 | 
					    team = models.ForeignKey(
 | 
				
			||||||
        Team,
 | 
					        Team,
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
@@ -245,6 +274,11 @@ class Solution(Document):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def tournament(self):
 | 
					    def tournament(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get the concerned tournament of a solution.
 | 
				
			||||||
 | 
					        Generally the local tournament of a team, but it can be the final tournament if this is a solution for the
 | 
				
			||||||
 | 
					        final tournament.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return Tournament.get_final() if self.final else self.team.tournament
 | 
					        return Tournament.get_final() if self.final else self.team.tournament
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -258,6 +292,9 @@ class Solution(Document):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Synthesis(Document):
 | 
					class Synthesis(Document):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Model for syntheses of a team for a given round and for a given role, for the regional or final tournament.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    team = models.ForeignKey(
 | 
					    team = models.ForeignKey(
 | 
				
			||||||
        Team,
 | 
					        Team,
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
@@ -289,6 +326,11 @@ class Synthesis(Document):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def tournament(self):
 | 
					    def tournament(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get the concerned tournament of a solution.
 | 
				
			||||||
 | 
					        Generally the local tournament of a team, but it can be the final tournament if this is a solution for the
 | 
				
			||||||
 | 
					        final tournament.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return Tournament.get_final() if self.final else self.team.tournament
 | 
					        return Tournament.get_final() if self.final else self.team.tournament
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -303,6 +345,9 @@ class Synthesis(Document):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Config(models.Model):
 | 
					class Config(models.Model):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Dictionary of configuration variables.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    key = models.CharField(
 | 
					    key = models.CharField(
 | 
				
			||||||
        max_length=255,
 | 
					        max_length=255,
 | 
				
			||||||
        primary_key=True,
 | 
					        primary_key=True,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,13 @@
 | 
				
			|||||||
import django_tables2 as tables
 | 
					import django_tables2 as tables
 | 
				
			||||||
from django_tables2 import A
 | 
					from django_tables2 import A
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from member.models import TFJMUser
 | 
					from .models import TFJMUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserTable(tables.Table):
 | 
					class UserTable(tables.Table):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Table of users that are matched with a given queryset.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    last_name = tables.LinkColumn(
 | 
					    last_name = tables.LinkColumn(
 | 
				
			||||||
        "member:information",
 | 
					        "member:information",
 | 
				
			||||||
        args=[A("pk")],
 | 
					        args=[A("pk")],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,11 +6,17 @@ from member.models import Config
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_config(value):
 | 
					def get_config(value):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return a value stored into the config table in the database with a given key.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    config = Config.objects.get_or_create(key=value)[0]
 | 
					    config = Config.objects.get_or_create(key=value)[0]
 | 
				
			||||||
    return config.value
 | 
					    return config.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_env(value):
 | 
					def get_env(value):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get a specified environment variable.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    return os.getenv(value)
 | 
					    return os.getenv(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,26 +12,33 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
from django.views import View
 | 
					from django.views import View
 | 
				
			||||||
from django.views.generic import CreateView, UpdateView, DetailView, FormView
 | 
					from django.views.generic import CreateView, UpdateView, DetailView, FormView
 | 
				
			||||||
from django_tables2 import SingleTableView
 | 
					from django_tables2 import SingleTableView
 | 
				
			||||||
 | 
					 | 
				
			||||||
from tournament.forms import TeamForm, JoinTeam
 | 
					from tournament.forms import TeamForm, JoinTeam
 | 
				
			||||||
from tournament.models import Team
 | 
					from tournament.models import Team
 | 
				
			||||||
from tournament.views import AdminMixin, TeamMixin
 | 
					from tournament.views import AdminMixin, TeamMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm
 | 
					from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm
 | 
				
			||||||
from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis
 | 
					from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis
 | 
				
			||||||
from .tables import UserTable
 | 
					from .tables import UserTable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CreateUserView(CreateView):
 | 
					class CreateUserView(CreateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Signup form view.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = TFJMUser
 | 
					    model = TFJMUser
 | 
				
			||||||
    form_class = SignUpForm
 | 
					    form_class = SignUpForm
 | 
				
			||||||
    template_name = "registration/signup.html"
 | 
					    template_name = "registration/signup.html"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MyAccountView(LoginRequiredMixin, UpdateView):
 | 
					class MyAccountView(LoginRequiredMixin, UpdateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Update our personal data.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = TFJMUser
 | 
					    model = TFJMUser
 | 
				
			||||||
    template_name = "member/my_account.html"
 | 
					    template_name = "member/my_account.html"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_form_class(self):
 | 
					    def get_form_class(self):
 | 
				
			||||||
 | 
					        # The used form can change according to the role of the user.
 | 
				
			||||||
        return AdminUserForm if self.request.user.organizes else TFJMUserForm \
 | 
					        return AdminUserForm if self.request.user.organizes else TFJMUserForm \
 | 
				
			||||||
            if self.request.user.role == "3participant" else CoachUserForm
 | 
					            if self.request.user.role == "3participant" else CoachUserForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,6 +47,10 @@ class MyAccountView(LoginRequiredMixin, UpdateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserDetailView(LoginRequiredMixin, DetailView):
 | 
					class UserDetailView(LoginRequiredMixin, DetailView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    View the personal information of a given user.
 | 
				
			||||||
 | 
					    Only organizers can see this page, since there are personal data.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = TFJMUser
 | 
					    model = TFJMUser
 | 
				
			||||||
    form_class = TFJMUserForm
 | 
					    form_class = TFJMUserForm
 | 
				
			||||||
    context_object_name = "tfjmuser"
 | 
					    context_object_name = "tfjmuser"
 | 
				
			||||||
@@ -57,7 +68,10 @@ class UserDetailView(LoginRequiredMixin, DetailView):
 | 
				
			|||||||
        return super().dispatch(request, *args, **kwargs)
 | 
					        return super().dispatch(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
        if "view_as" in request.POST:
 | 
					        """
 | 
				
			||||||
 | 
					        An administrator can log in through this page as someone else, and act as this other person.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if "view_as" in request.POST and self.request.admin:
 | 
				
			||||||
            session = request.session
 | 
					            session = request.session
 | 
				
			||||||
            session["admin"] = request.user.pk
 | 
					            session["admin"] = request.user.pk
 | 
				
			||||||
            obj = self.get_object()
 | 
					            obj = self.get_object()
 | 
				
			||||||
@@ -74,6 +88,10 @@ class UserDetailView(LoginRequiredMixin, DetailView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddTeamView(LoginRequiredMixin, CreateView):
 | 
					class AddTeamView(LoginRequiredMixin, CreateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Register a new team.
 | 
				
			||||||
 | 
					    Users can choose the name, the trigram and a preferred tournament.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = Team
 | 
					    model = Team
 | 
				
			||||||
    form_class = TeamForm
 | 
					    form_class = TeamForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -86,6 +104,7 @@ class AddTeamView(LoginRequiredMixin, CreateView):
 | 
				
			|||||||
            form.add_error('name', _("You are already in a team."))
 | 
					            form.add_error('name', _("You are already in a team."))
 | 
				
			||||||
            return self.form_invalid(form)
 | 
					            return self.form_invalid(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generate a random access code
 | 
				
			||||||
        team = form.instance
 | 
					        team = form.instance
 | 
				
			||||||
        alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
 | 
					        alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
 | 
				
			||||||
        code = ""
 | 
					        code = ""
 | 
				
			||||||
@@ -107,6 +126,9 @@ class AddTeamView(LoginRequiredMixin, CreateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class JoinTeamView(LoginRequiredMixin, FormView):
 | 
					class JoinTeamView(LoginRequiredMixin, FormView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Join a team with a given access code.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = Team
 | 
					    model = Team
 | 
				
			||||||
    form_class = JoinTeam
 | 
					    form_class = JoinTeam
 | 
				
			||||||
    template_name = "tournament/team_form.html"
 | 
					    template_name = "tournament/team_form.html"
 | 
				
			||||||
@@ -122,7 +144,7 @@ class JoinTeamView(LoginRequiredMixin, FormView):
 | 
				
			|||||||
            form.add_error('access_code', _("You are already in a team."))
 | 
					            form.add_error('access_code', _("You are already in a team."))
 | 
				
			||||||
            return self.form_invalid(form)
 | 
					            return self.form_invalid(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.request.user.role == '2coach' and len(team.encadrants) == 3:
 | 
					        if self.request.user.role == '2coach' and len(team.coaches) == 3:
 | 
				
			||||||
            form.add_error('access_code', _("This team is full of coachs."))
 | 
					            form.add_error('access_code', _("This team is full of coachs."))
 | 
				
			||||||
            return self.form_invalid(form)
 | 
					            return self.form_invalid(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -130,6 +152,9 @@ class JoinTeamView(LoginRequiredMixin, FormView):
 | 
				
			|||||||
            form.add_error('access_code', _("This team is full of participants."))
 | 
					            form.add_error('access_code', _("This team is full of participants."))
 | 
				
			||||||
            return self.form_invalid(form)
 | 
					            return self.form_invalid(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not team.invalid:
 | 
				
			||||||
 | 
					            form.add_error('access_code', _("This team is already validated or waiting for validation."))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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)
 | 
				
			||||||
@@ -139,11 +164,24 @@ class JoinTeamView(LoginRequiredMixin, FormView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MyTeamView(TeamMixin, View):
 | 
					class MyTeamView(TeamMixin, View):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Redirect to the page of the information of our personal team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
        return redirect("tournament:team_detail", pk=request.user.team.pk)
 | 
					        return redirect("tournament:team_detail", pk=request.user.team.pk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DocumentView(LoginRequiredMixin, View):
 | 
					class DocumentView(LoginRequiredMixin, View):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    View a PDF document, if we have the right.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - Everyone can see the documents that concern itself.
 | 
				
			||||||
 | 
					    - An administrator can see anything.
 | 
				
			||||||
 | 
					    - An organizer can see documents that are related to its tournament.
 | 
				
			||||||
 | 
					    - A jury can see solutions and syntheses that are evaluated in their pools.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
        doc = Document.objects.get(file=self.kwargs["file"])
 | 
					        doc = Document.objects.get(file=self.kwargs["file"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -172,6 +210,9 @@ class DocumentView(LoginRequiredMixin, View):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProfileListView(AdminMixin, SingleTableView):
 | 
					class ProfileListView(AdminMixin, SingleTableView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    List all registered profiles.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = TFJMUser
 | 
					    model = TFJMUser
 | 
				
			||||||
    queryset = TFJMUser.objects.order_by("role", "last_name", "first_name")
 | 
					    queryset = TFJMUser.objects.order_by("role", "last_name", "first_name")
 | 
				
			||||||
    table_class = UserTable
 | 
					    table_class = UserTable
 | 
				
			||||||
@@ -180,6 +221,9 @@ class ProfileListView(AdminMixin, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrphanedProfileListView(AdminMixin, SingleTableView):
 | 
					class OrphanedProfileListView(AdminMixin, SingleTableView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    List all orphaned profiles, ie. participants that have no team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = TFJMUser
 | 
					    model = TFJMUser
 | 
				
			||||||
    queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\
 | 
					    queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\
 | 
				
			||||||
        .order_by("role", "last_name", "first_name")
 | 
					        .order_by("role", "last_name", "first_name")
 | 
				
			||||||
@@ -189,6 +233,9 @@ class OrphanedProfileListView(AdminMixin, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrganizersListView(AdminMixin, SingleTableView):
 | 
					class OrganizersListView(AdminMixin, SingleTableView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    List all organizers.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = TFJMUser
 | 
					    model = TFJMUser
 | 
				
			||||||
    queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\
 | 
					    queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\
 | 
				
			||||||
        .order_by("role", "last_name", "first_name")
 | 
					        .order_by("role", "last_name", "first_name")
 | 
				
			||||||
@@ -198,6 +245,10 @@ class OrganizersListView(AdminMixin, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ResetAdminView(AdminMixin, View):
 | 
					class ResetAdminView(AdminMixin, View):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return to admin view, clear the session field that let an administrator to log in as someone else.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch(self, request, *args, **kwargs):
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
        if "_fake_user_id" in request.session:
 | 
					        if "_fake_user_id" in request.session:
 | 
				
			||||||
            del request.session["_fake_user_id"]
 | 
					            del request.session["_fake_user_id"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +1,31 @@
 | 
				
			|||||||
from django.contrib.auth.admin import admin
 | 
					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)
 | 
					@admin.register(Team)
 | 
				
			||||||
class TeamAdmin(admin.ModelAdmin):
 | 
					class TeamAdmin(admin.ModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for teams.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Tournament)
 | 
					@admin.register(Tournament)
 | 
				
			||||||
class TournamentAdmin(admin.ModelAdmin):
 | 
					class TournamentAdmin(admin.ModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for tournaments.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Pool)
 | 
					@admin.register(Pool)
 | 
				
			||||||
class PoolAdmin(admin.ModelAdmin):
 | 
					class PoolAdmin(admin.ModelAdmin):
 | 
				
			||||||
    pass
 | 
					    """
 | 
				
			||||||
 | 
					    Django admin page for pools.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Payment)
 | 
					@admin.register(Payment)
 | 
				
			||||||
class PaymentAdmin(admin.ModelAdmin):
 | 
					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):
 | 
					class TournamentConfig(AppConfig):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    The tournament app handles all that is related to the tournaments.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    name = 'tournament'
 | 
					    name = 'tournament'
 | 
				
			||||||
    verbose_name = _('tournament')
 | 
					    verbose_name = _('tournament')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,11 @@ from tournament.models import Tournament, Team, Pool
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentForm(forms.ModelForm):
 | 
					class TournamentForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Create and update tournaments.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Only organizers can organize tournaments. Well, that's pretty normal...
 | 
				
			||||||
    organizers = forms.ModelMultipleChoiceField(
 | 
					    organizers = forms.ModelMultipleChoiceField(
 | 
				
			||||||
        TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer")).order_by('role'),
 | 
					        TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer")).order_by('role'),
 | 
				
			||||||
        label=_("Organizers"),
 | 
					        label=_("Organizers"),
 | 
				
			||||||
@@ -44,6 +49,10 @@ class TournamentForm(forms.ModelForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrganizerForm(forms.ModelForm):
 | 
					class OrganizerForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Register an organizer in the website.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = TFJMUser
 | 
					        model = TFJMUser
 | 
				
			||||||
        fields = ('last_name', 'first_name', 'email', 'is_superuser',)
 | 
					        fields = ('last_name', 'first_name', 'email', 'is_superuser',)
 | 
				
			||||||
@@ -64,6 +73,9 @@ class OrganizerForm(forms.ModelForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamForm(forms.ModelForm):
 | 
					class TeamForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Add and update a team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    tournament = forms.ModelChoiceField(
 | 
					    tournament = forms.ModelChoiceField(
 | 
				
			||||||
        Tournament.objects.filter(date_inscription__gte=timezone.now(), final=False),
 | 
					        Tournament.objects.filter(date_inscription__gte=timezone.now(), final=False),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -94,6 +106,10 @@ class TeamForm(forms.ModelForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class JoinTeam(forms.Form):
 | 
					class JoinTeam(forms.Form):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Form to join a team with an access code.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    access_code = forms.CharField(
 | 
					    access_code = forms.CharField(
 | 
				
			||||||
        label=_("Access code"),
 | 
					        label=_("Access code"),
 | 
				
			||||||
        max_length=6,
 | 
					        max_length=6,
 | 
				
			||||||
@@ -117,6 +133,10 @@ class JoinTeam(forms.Form):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SolutionForm(forms.ModelForm):
 | 
					class SolutionForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Form to upload a solution.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    problem = forms.ChoiceField(
 | 
					    problem = forms.ChoiceField(
 | 
				
			||||||
        label=_("Problem"),
 | 
					        label=_("Problem"),
 | 
				
			||||||
        choices=[(str(i), _("Problem #{problem:d}").format(problem=i)) for i in range(1, 9)],
 | 
					        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):
 | 
					class SynthesisForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Form to upload a synthesis.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = Synthesis
 | 
					        model = Synthesis
 | 
				
			||||||
        fields = ('file', 'source', 'round',)
 | 
					        fields = ('file', 'source', 'round',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PoolForm(forms.ModelForm):
 | 
					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(
 | 
					    team1 = forms.ModelChoiceField(
 | 
				
			||||||
        Team.objects.filter(validation_status="2valid").all(),
 | 
					        Team.objects.filter(validation_status="2valid").all(),
 | 
				
			||||||
        empty_label=_("Choose a team..."),
 | 
					        empty_label=_("Choose a team..."),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,10 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Tournament(models.Model):
 | 
					class Tournament(models.Model):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Store the information of a tournament.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = models.CharField(
 | 
					    name = models.CharField(
 | 
				
			||||||
        max_length=255,
 | 
					        max_length=255,
 | 
				
			||||||
        verbose_name=_("name"),
 | 
					        verbose_name=_("name"),
 | 
				
			||||||
@@ -18,10 +22,12 @@ class Tournament(models.Model):
 | 
				
			|||||||
        'member.TFJMUser',
 | 
					        'member.TFJMUser',
 | 
				
			||||||
        related_name="organized_tournaments",
 | 
					        related_name="organized_tournaments",
 | 
				
			||||||
        verbose_name=_("organizers"),
 | 
					        verbose_name=_("organizers"),
 | 
				
			||||||
 | 
					        help_text=_("List of all organizers that can see and manipulate data of the tournament and the teams."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    size = models.PositiveSmallIntegerField(
 | 
					    size = models.PositiveSmallIntegerField(
 | 
				
			||||||
        verbose_name=_("size"),
 | 
					        verbose_name=_("size"),
 | 
				
			||||||
 | 
					        help_text=_("Number of teams that are allowed to join the tournament."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    place = models.CharField(
 | 
					    place = models.CharField(
 | 
				
			||||||
@@ -31,6 +37,7 @@ class Tournament(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    price = models.PositiveSmallIntegerField(
 | 
					    price = models.PositiveSmallIntegerField(
 | 
				
			||||||
        verbose_name=_("price"),
 | 
					        verbose_name=_("price"),
 | 
				
			||||||
 | 
					        help_text=_("Price asked to participants. Free with a scholarship."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    description = models.TextField(
 | 
					    description = models.TextField(
 | 
				
			||||||
@@ -74,6 +81,7 @@ class Tournament(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    final = models.BooleanField(
 | 
					    final = models.BooleanField(
 | 
				
			||||||
        verbose_name=_("final tournament"),
 | 
					        verbose_name=_("final tournament"),
 | 
				
			||||||
 | 
					        help_text=_("It should be only one final tournament."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    year = models.PositiveIntegerField(
 | 
					    year = models.PositiveIntegerField(
 | 
				
			||||||
@@ -83,27 +91,43 @@ class Tournament(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def teams(self):
 | 
					    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)
 | 
					        return self._teams if not self.final else Team.objects.filter(selected_for_final=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def linked_organizers(self):
 | 
					    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>'
 | 
					        return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
 | 
				
			||||||
                for user in self.organizers.all()]
 | 
					                for user in self.organizers.all()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def solutions(self):
 | 
					    def solutions(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get all sent solutions for this tournament.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        from member.models import Solution
 | 
					        from member.models import Solution
 | 
				
			||||||
        return Solution.objects.filter(final=self.final) if self.final \
 | 
					        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
 | 
					    @property
 | 
				
			||||||
    def syntheses(self):
 | 
					    def syntheses(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get all sent syntheses for this tournament.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        from member.models import Synthesis
 | 
					        from member.models import Synthesis
 | 
				
			||||||
        return Synthesis.objects.filter(final=self.final) if self.final \
 | 
					        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
 | 
					    @classmethod
 | 
				
			||||||
    def get_final(cls):
 | 
					    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)
 | 
					        return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -111,6 +135,12 @@ class Tournament(models.Model):
 | 
				
			|||||||
        verbose_name_plural = _("tournaments")
 | 
					        verbose_name_plural = _("tournaments")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def send_mail_to_organizers(self, template_name, subject="Contact TFJM²", **kwargs):
 | 
					    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 = kwargs
 | 
				
			||||||
        context["tournament"] = self
 | 
					        context["tournament"] = self
 | 
				
			||||||
        for user in self.organizers.all():
 | 
					        for user in self.organizers.all():
 | 
				
			||||||
@@ -130,6 +160,10 @@ class Tournament(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Team(models.Model):
 | 
					class Team(models.Model):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Store information about a registered team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = models.CharField(
 | 
					    name = models.CharField(
 | 
				
			||||||
        max_length=255,
 | 
					        max_length=255,
 | 
				
			||||||
        verbose_name=_("name"),
 | 
					        verbose_name=_("name"),
 | 
				
			||||||
@@ -138,6 +172,7 @@ class Team(models.Model):
 | 
				
			|||||||
    trigram = models.CharField(
 | 
					    trigram = models.CharField(
 | 
				
			||||||
        max_length=3,
 | 
					        max_length=3,
 | 
				
			||||||
        verbose_name=_("trigram"),
 | 
					        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(
 | 
					    tournament = models.ForeignKey(
 | 
				
			||||||
@@ -145,6 +180,7 @@ class Team(models.Model):
 | 
				
			|||||||
        on_delete=models.PROTECT,
 | 
					        on_delete=models.PROTECT,
 | 
				
			||||||
        related_name="_teams",
 | 
					        related_name="_teams",
 | 
				
			||||||
        verbose_name=_("tournament"),
 | 
					        verbose_name=_("tournament"),
 | 
				
			||||||
 | 
					        help_text=_("The tournament where the team is registered."),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inscription_date = models.DateTimeField(
 | 
					    inscription_date = models.DateTimeField(
 | 
				
			||||||
@@ -191,31 +227,59 @@ class Team(models.Model):
 | 
				
			|||||||
        return self.validation_status == "0invalid"
 | 
					        return self.validation_status == "0invalid"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def encadrants(self):
 | 
					    def coaches(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get all coaches of a team.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.users.all().filter(role="2coach")
 | 
					        return self.users.all().filter(role="2coach")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @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>'
 | 
					        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
 | 
					    @property
 | 
				
			||||||
    def participants(self):
 | 
					    def participants(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get all particpants of a team, coaches excluded.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.users.all().filter(role="3participant")
 | 
					        return self.users.all().filter(role="3participant")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def linked_participants(self):
 | 
					    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>'
 | 
					        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
 | 
					    @property
 | 
				
			||||||
    def future_tournament(self):
 | 
					    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
 | 
					        return Tournament.get_final() if self.selected_for_final else self.tournament
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def can_validate(self):
 | 
					    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.
 | 
					        # 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:
 | 
					    class Meta:
 | 
				
			||||||
        verbose_name = _("team")
 | 
					        verbose_name = _("team")
 | 
				
			||||||
@@ -223,6 +287,12 @@ class Team(models.Model):
 | 
				
			|||||||
        unique_together = (('name', 'year',), ('trigram', 'year',),)
 | 
					        unique_together = (('name', 'year',), ('trigram', 'year',),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def send_mail(self, template_name, subject="Contact TFJM²", **kwargs):
 | 
					    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 = kwargs
 | 
				
			||||||
        context["team"] = self
 | 
					        context["team"] = self
 | 
				
			||||||
        for user in self.users.all():
 | 
					        for user in self.users.all():
 | 
				
			||||||
@@ -236,6 +306,12 @@ class Team(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Pool(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(
 | 
					    teams = models.ManyToManyField(
 | 
				
			||||||
        Team,
 | 
					        Team,
 | 
				
			||||||
        related_name="pools",
 | 
					        related_name="pools",
 | 
				
			||||||
@@ -264,14 +340,24 @@ class Pool(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def problems(self):
 | 
					    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())
 | 
					        return list(d["problem"] for d in self.solutions.values("problem").all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def tournament(self):
 | 
					    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
 | 
					        return self.solutions.first().tournament
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def syntheses(self):
 | 
					    def syntheses(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Get the syntheses of the teams that are in this pool, for the correct round.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        from member.models import Synthesis
 | 
					        from member.models import Synthesis
 | 
				
			||||||
        return Synthesis.objects.filter(team__in=self.teams.all(), round=self.round, final=self.tournament.final)
 | 
					        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):
 | 
					class Payment(models.Model):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Store some information about payments, to recover data.
 | 
				
			||||||
 | 
					    TODO: handle it...
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    user = models.OneToOneField(
 | 
					    user = models.OneToOneField(
 | 
				
			||||||
        'member.TFJMUser',
 | 
					        'member.TFJMUser',
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,10 @@ from .models import Tournament, Team, Pool
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentTable(tables.Table):
 | 
					class TournamentTable(tables.Table):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    List all tournaments.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = tables.LinkColumn(
 | 
					    name = tables.LinkColumn(
 | 
				
			||||||
        "tournament:detail",
 | 
					        "tournament:detail",
 | 
				
			||||||
        args=[A("pk")],
 | 
					        args=[A("pk")],
 | 
				
			||||||
@@ -31,6 +35,10 @@ class TournamentTable(tables.Table):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamTable(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(
 | 
					    name = tables.LinkColumn(
 | 
				
			||||||
        "tournament:team_detail",
 | 
					        "tournament:team_detail",
 | 
				
			||||||
        args=[A("pk")],
 | 
					        args=[A("pk")],
 | 
				
			||||||
@@ -46,6 +54,10 @@ class TeamTable(tables.Table):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SolutionTable(tables.Table):
 | 
					class SolutionTable(tables.Table):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display a table of some solutions.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    team = tables.LinkColumn(
 | 
					    team = tables.LinkColumn(
 | 
				
			||||||
        "tournament:team_detail",
 | 
					        "tournament:team_detail",
 | 
				
			||||||
        args=[A("team.pk")],
 | 
					        args=[A("team.pk")],
 | 
				
			||||||
@@ -81,6 +93,10 @@ class SolutionTable(tables.Table):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SynthesisTable(tables.Table):
 | 
					class SynthesisTable(tables.Table):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display a table of some syntheses.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    team = tables.LinkColumn(
 | 
					    team = tables.LinkColumn(
 | 
				
			||||||
        "tournament:team_detail",
 | 
					        "tournament:team_detail",
 | 
				
			||||||
        args=[A("team.pk")],
 | 
					        args=[A("team.pk")],
 | 
				
			||||||
@@ -116,6 +132,10 @@ class SynthesisTable(tables.Table):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PoolTable(tables.Table):
 | 
					class PoolTable(tables.Table):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display a table of some pools.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    problems = tables.Column(
 | 
					    problems = tables.Column(
 | 
				
			||||||
        verbose_name=_("Problems"),
 | 
					        verbose_name=_("Problems"),
 | 
				
			||||||
        orderable=False,
 | 
					        orderable=False,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,10 @@ from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, P
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdminMixin(LoginRequiredMixin):
 | 
					class AdminMixin(LoginRequiredMixin):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    If a view extends this mixin, then the view will be only accessible to administrators.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch(self, request, *args, **kwargs):
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
        if not request.user.is_authenticated or not request.user.admin:
 | 
					        if not request.user.is_authenticated or not request.user.admin:
 | 
				
			||||||
            raise PermissionDenied
 | 
					            raise PermissionDenied
 | 
				
			||||||
@@ -31,6 +35,10 @@ class AdminMixin(LoginRequiredMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrgaMixin(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):
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
        if not request.user.is_authenticated or not request.user.organizes:
 | 
					        if not request.user.is_authenticated or not request.user.organizes:
 | 
				
			||||||
            raise PermissionDenied
 | 
					            raise PermissionDenied
 | 
				
			||||||
@@ -38,6 +46,10 @@ class OrgaMixin(LoginRequiredMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamMixin(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):
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
        if not request.user.is_authenticated or not request.user.team:
 | 
					        if not request.user.is_authenticated or not request.user.team:
 | 
				
			||||||
            raise PermissionDenied
 | 
					            raise PermissionDenied
 | 
				
			||||||
@@ -45,6 +57,10 @@ class TeamMixin(LoginRequiredMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentListView(SingleTableView):
 | 
					class TournamentListView(SingleTableView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display the list of all tournaments, ordered by start date then name.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model = Tournament
 | 
					    model = Tournament
 | 
				
			||||||
    table_class = TournamentTable
 | 
					    table_class = TournamentTable
 | 
				
			||||||
    extra_context = dict(title=_("Tournaments list"),)
 | 
					    extra_context = dict(title=_("Tournaments list"),)
 | 
				
			||||||
@@ -64,6 +80,10 @@ class TournamentListView(SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentCreateView(AdminMixin, CreateView):
 | 
					class TournamentCreateView(AdminMixin, CreateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Create a tournament. Only accessible to admins.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model = Tournament
 | 
					    model = Tournament
 | 
				
			||||||
    form_class = TournamentForm
 | 
					    form_class = TournamentForm
 | 
				
			||||||
    extra_context = dict(title=_("Add tournament"),)
 | 
					    extra_context = dict(title=_("Add tournament"),)
 | 
				
			||||||
@@ -73,6 +93,11 @@ class TournamentCreateView(AdminMixin, CreateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TournamentDetailView(DetailView):
 | 
					class TournamentDetailView(DetailView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Display the detail of a tournament.
 | 
				
			||||||
 | 
					    Accessible to all, including not authenticated users.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model = Tournament
 | 
					    model = Tournament
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
@@ -96,7 +121,20 @@ class TournamentDetailView(DetailView):
 | 
				
			|||||||
        return context
 | 
					        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
 | 
					    model = Tournament
 | 
				
			||||||
    form_class = TournamentForm
 | 
					    form_class = TournamentForm
 | 
				
			||||||
    extra_context = dict(title=_("Update tournament"),)
 | 
					    extra_context = dict(title=_("Update tournament"),)
 | 
				
			||||||
@@ -106,9 +144,16 @@ class TournamentUpdateView(AdminMixin, UpdateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
					class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    View the detail of a team.
 | 
				
			||||||
 | 
					    Restricted to this team, admins and organizers of its tournament.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = Team
 | 
					    model = Team
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch(self, request, *args, **kwargs):
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Protect the page and process the request.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        if not request.user.is_authenticated or \
 | 
					        if not request.user.is_authenticated or \
 | 
				
			||||||
                (not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()
 | 
					                (not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()
 | 
				
			||||||
                 and self.get_object() != request.user.team):
 | 
					                 and self.get_object() != request.user.team):
 | 
				
			||||||
@@ -116,7 +161,15 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
				
			|||||||
        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)
 | 
					        """
 | 
				
			||||||
 | 
					        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()
 | 
					        team = self.get_object()
 | 
				
			||||||
        if "zip" in request.POST:
 | 
					        if "zip" in request.POST:
 | 
				
			||||||
            solutions = team.solutions.all()
 | 
					            solutions = team.solutions.all()
 | 
				
			||||||
@@ -140,7 +193,7 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
				
			|||||||
            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 "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.validation_status = "1waiting"
 | 
				
			||||||
            team.save()
 | 
					            team.save()
 | 
				
			||||||
            team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team)
 | 
					            team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team)
 | 
				
			||||||
@@ -159,6 +212,7 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
				
			|||||||
            team.delete()
 | 
					            team.delete()
 | 
				
			||||||
            return redirect('tournament:detail', pk=team.tournament.pk)
 | 
					            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:
 | 
					        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():
 | 
					            for solution in team.solutions.all():
 | 
				
			||||||
                alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
 | 
					                alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
 | 
				
			||||||
                id = ""
 | 
					                id = ""
 | 
				
			||||||
@@ -194,6 +248,11 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamUpdateView(LoginRequiredMixin, UpdateView):
 | 
					class TeamUpdateView(LoginRequiredMixin, UpdateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Update the information about a team.
 | 
				
			||||||
 | 
					    Team members, admins and organizers are allowed to do this.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model = Team
 | 
					    model = Team
 | 
				
			||||||
    form_class = TeamForm
 | 
					    form_class = TeamForm
 | 
				
			||||||
    extra_context = dict(title=_("Update team"),)
 | 
					    extra_context = dict(title=_("Update team"),)
 | 
				
			||||||
@@ -206,6 +265,12 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddOrganizerView(AdminMixin, CreateView):
 | 
					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
 | 
					    model = TFJMUser
 | 
				
			||||||
    form_class = OrganizerForm
 | 
					    form_class = OrganizerForm
 | 
				
			||||||
    extra_context = dict(title=_("Add organizer"),)
 | 
					    extra_context = dict(title=_("Add organizer"),)
 | 
				
			||||||
@@ -223,6 +288,10 @@ class AddOrganizerView(AdminMixin, CreateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
 | 
					class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Upload and view solutions for a team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model = Solution
 | 
					    model = Solution
 | 
				
			||||||
    table_class = SolutionTable
 | 
					    table_class = SolutionTable
 | 
				
			||||||
    form_class = SolutionForm
 | 
					    form_class = SolutionForm
 | 
				
			||||||
@@ -288,6 +357,11 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SolutionsOrgaListView(OrgaMixin, 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
 | 
					    model = Solution
 | 
				
			||||||
    table_class = SolutionTable
 | 
					    table_class = SolutionTable
 | 
				
			||||||
    template_name = "tournament/solutions_orga_list.html"
 | 
					    template_name = "tournament/solutions_orga_list.html"
 | 
				
			||||||
@@ -333,6 +407,9 @@ class SolutionsOrgaListView(OrgaMixin, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
 | 
					class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Upload and view syntheses for a team.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = Synthesis
 | 
					    model = Synthesis
 | 
				
			||||||
    table_class = SynthesisTable
 | 
					    table_class = SynthesisTable
 | 
				
			||||||
    form_class = SynthesisForm
 | 
					    form_class = SynthesisForm
 | 
				
			||||||
@@ -407,6 +484,10 @@ class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SynthesesOrgaListView(OrgaMixin, 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
 | 
					    model = Synthesis
 | 
				
			||||||
    table_class = SynthesisTable
 | 
					    table_class = SynthesisTable
 | 
				
			||||||
    template_name = "tournament/syntheses_orga_list.html"
 | 
					    template_name = "tournament/syntheses_orga_list.html"
 | 
				
			||||||
@@ -452,6 +533,10 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PoolListView(LoginRequiredMixin, 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
 | 
					    model = Pool
 | 
				
			||||||
    table_class = PoolTable
 | 
					    table_class = PoolTable
 | 
				
			||||||
    extra_context = dict(title=_("Pools"))
 | 
					    extra_context = dict(title=_("Pools"))
 | 
				
			||||||
@@ -469,6 +554,10 @@ class PoolListView(LoginRequiredMixin, SingleTableView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PoolCreateView(AdminMixin, CreateView):
 | 
					class PoolCreateView(AdminMixin, CreateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Create a pool manually.
 | 
				
			||||||
 | 
					    This page should not be used: prefer send automatically data from the drawing bot.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model = Pool
 | 
					    model = Pool
 | 
				
			||||||
    form_class = PoolForm
 | 
					    form_class = PoolForm
 | 
				
			||||||
    extra_context = dict(title=_("Create pool"))
 | 
					    extra_context = dict(title=_("Create pool"))
 | 
				
			||||||
@@ -478,6 +567,15 @@ class PoolCreateView(AdminMixin, CreateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PoolDetailView(LoginRequiredMixin, DetailView):
 | 
					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
 | 
					    model = Pool
 | 
				
			||||||
    extra_context = dict(title=_("Pool detail"))
 | 
					    extra_context = dict(title=_("Pool detail"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,10 +34,6 @@ msgstr "Choisir un rôle ..."
 | 
				
			|||||||
msgid "Participant"
 | 
					msgid "Participant"
 | 
				
			||||||
msgstr "Participant"
 | 
					msgstr "Participant"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: apps/member/forms.py:16
 | 
					 | 
				
			||||||
msgid "Encadrant"
 | 
					 | 
				
			||||||
msgstr "Encadrant"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: apps/member/models.py:18 templates/member/tfjmuser_detail.html:35
 | 
					#: apps/member/models.py:18 templates/member/tfjmuser_detail.html:35
 | 
				
			||||||
msgid "email"
 | 
					msgid "email"
 | 
				
			||||||
msgstr "Adresse électronique"
 | 
					msgstr "Adresse électronique"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@
 | 
				
			|||||||
                        href="{% url "tournament:detail" pk=team.tournament.pk %}">{{ team.tournament }}</a></dd>
 | 
					                        href="{% url "tournament:detail" pk=team.tournament.pk %}">{{ team.tournament }}</a></dd>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <dt class="col-xl-6 text-right">{% trans 'coachs'|capfirst %}</dt>
 | 
					                <dt class="col-xl-6 text-right">{% trans 'coachs'|capfirst %}</dt>
 | 
				
			||||||
                <dd class="col-xl-6">{% autoescape off %}{{ team.linked_encadrants|join:", " }}{% endautoescape %}</dd>
 | 
					                <dd class="col-xl-6">{% autoescape off %}{{ team.linked_coaches|join:", " }}{% endautoescape %}</dd>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <dt class="col-xl-6 text-right">{% trans 'participants'|capfirst %}</dt>
 | 
					                <dt class="col-xl-6 text-right">{% trans 'participants'|capfirst %}</dt>
 | 
				
			||||||
                <dd class="col-xl-6">
 | 
					                <dd class="col-xl-6">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -181,3 +181,6 @@ if os.getenv("TFJM_STAGE", "dev") == "prod":
 | 
				
			|||||||
    from .settings_prod import *
 | 
					    from .settings_prod import *
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    from .settings_dev import *
 | 
					    from .settings_dev import *
 | 
				
			||||||
 | 
					    INSTALLED_APPS += [
 | 
				
			||||||
 | 
					        "django_extensions"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user