diff --git a/.dockerignore b/.dockerignore
index 12143a1..efc2616 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,4 +1,3 @@
__pycache__
media
-import_olddb
db.sqlite3
diff --git a/.gitignore b/.gitignore
index 173574d..29e51da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,10 +38,8 @@ coverage
secrets.py
*.log
media/
+
# Virtualenv
env/
venv/
db.sqlite3
-
-# Don't git personal data
-import_olddb/
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index f4e487b..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,31 +0,0 @@
-FROM python:3-alpine
-
-ENV PYTHONUNBUFFERED 1
-
-# Install LaTeX requirements
-RUN apk add --no-cache gettext texlive nginx gcc libc-dev libffi-dev postgresql-dev mariadb-connector-c-dev
-
-RUN apk add --no-cache bash
-
-RUN mkdir /code
-WORKDIR /code
-COPY requirements.txt /code/requirements.txt
-RUN pip install -r requirements.txt --no-cache-dir
-
-COPY . /code/
-
-# Configure nginx
-RUN mkdir /run/nginx
-RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log
-RUN ln -sf /code/nginx_tfjm.conf /etc/nginx/conf.d/tfjm.conf
-RUN rm /etc/nginx/conf.d/default.conf
-
-RUN cp /code/tfjm.cron /etc/crontabs/tfjm
-
-# With a bashrc, the shell is better
-RUN ln -s /code/.bashrc /root/.bashrc
-
-ENTRYPOINT ["/code/entrypoint.sh"]
-EXPOSE 80
-
-CMD ["./manage.py", "shell_plus", "--ptpython"]
diff --git a/README.md b/README.md
deleted file mode 100644
index a9459c3..0000000
--- a/README.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Plateforme d'inscription du TFJM²
-
-La plateforme du TFJM² est née pour l'édition 2020 du tournoi. D'abord codée en PHP, elle a subi une refonte totale en
-Python, à l'aide du framework Web [Django](https://www.djangoproject.com/).
-
-Cette plateforme permet aux participants et encadrants de s'inscrire et de déposer leurs autorisations nécessaires.
-Ils pourront ensuite déposer leurs solutions et notes de synthèse pour le premier tour en temps voulu. La plateforme
-offre également un accès pour les organisateurs et les jurys leur permettant de communiquer avec les équipes et de
-récupérer les documents nécessaires.
-
-Un wiki plus détaillé arrivera ultérieurement. L'interface organisateur et jury est vouée à être plus poussée.
-
-L'instance de production est disponible à l'adresse [inscription.tfjm.org](https://inscription.tfjm.org).
-
-## Installation
-
-Le plus simple pour installer la plateforme est d'utiliser l'image Docker incluse, qui fait tourner un serveur Nginx
-exposé sur le port 80 avec le serveur Django. Ci-dessous une configuration Docker-Compose, à adapter selon vos besoins :
-
-```yaml
- inscription-tfjm:
- build: ./inscription-tfjm
- links:
- - postgres
- ports:
- - "80:80"
- env_file:
- - ./inscription-tfjm.env
- volumes:
- # - ./inscription-tfjm:/code
- - ./inscription-tfjm/media:/code/media
-```
-
-Le volume `/code` n'est à ajouter uniquement en développement, et jamais en production.
-
-Il faut remplir les variables d'environnement suivantes :
-
-```env
-TFJM_STAGE= # dev ou prod
-TFJM_YEAR=2021 # Année de la session du TFJM²
-DJANGO_DB_TYPE= # MySQL, PostgreSQL ou SQLite (par défaut)
-DJANGO_DB_HOST= # Hôte de la base de données
-DJANGO_DB_NAME= # Nom de la base de données
-DJANGO_DB_USER= # Utilisateur de la base de données
-DJANGO_DB_PASSWORD= # Mot de passe pour accéder à la base de données
-SMTP_HOST= # Hôte SMTP pour l'envoi de mails
-SMTP_PORT=465 # Port du serveur SMTP
-SMTP_HOST_USER= # Utilisateur du compte SMTP
-SMTP_HOST_PASSWORD= # Mot de passe du compte SMTP
-FROM_EMAIL=contact@tfjm.org # Nom de l'expéditeur des mails
-SERVER_EMAIL=contact@tfjm.org # Adresse e-mail expéditrice
-```
-
-Si le type de base de données sélectionné est SQLite, la variable `DJANGO_DB_HOST` sera utilisée en guise de chemin vers
-le fichier de base de données (par défaut, `db.sqlite3`).
-
-En développement, il est recommandé d'utiliser SQLite pour des raisons de simplicité. Les paramètres de mail ne seront
-pas utilisés, et les mails qui doivent être envoyés seront envoyés dans la console.
-
-En production, il est recommandé de ne pas utiliser SQLite pour des raisons de performances.
-
-La dernière différence entre le développment et la production est qu'en développement, chaque modification d'un fichier
-est détectée et le serveur se relance automatiquement dès lors.
-
-Une fois le site lancé, le premier compte créé sera un compte administrateur.
\ No newline at end of file
diff --git a/apps/api/__init__.py b/apps/api/__init__.py
deleted file mode 100644
index 08884cb..0000000
--- a/apps/api/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = 'api.apps.APIConfig'
diff --git a/apps/api/apps.py b/apps/api/apps.py
deleted file mode 100644
index 6e03468..0000000
--- a/apps/api/apps.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.apps import AppConfig
-from django.utils.translation import gettext_lazy as _
-
-
-class APIConfig(AppConfig):
- """
- Manage the inscription through a JSON API.
- """
- name = 'api'
- verbose_name = _('API')
diff --git a/apps/api/serializers.py b/apps/api/serializers.py
deleted file mode 100644
index 1685020..0000000
--- a/apps/api/serializers.py
+++ /dev/null
@@ -1,80 +0,0 @@
-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__"
diff --git a/apps/api/urls.py b/apps/api/urls.py
deleted file mode 100644
index b2e617f..0000000
--- a/apps/api/urls.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from django.conf.urls import url, include
-from rest_framework import routers
-
-from .viewsets import UserViewSet, TeamViewSet, TournamentViewSet, AuthorizationViewSet, MotivationLetterViewSet, \
- SolutionViewSet, SynthesisViewSet, PoolViewSet
-
-# Routers provide an easy way of automatically determining the URL conf.
-# Register each app API router and user viewset
-router = routers.DefaultRouter()
-router.register('user', UserViewSet)
-router.register('team', TeamViewSet)
-router.register('tournament', TournamentViewSet)
-router.register('authorization', AuthorizationViewSet)
-router.register('motivation_letter', MotivationLetterViewSet)
-router.register('solution', SolutionViewSet)
-router.register('synthesis', SynthesisViewSet)
-router.register('pool', PoolViewSet)
-
-app_name = 'api'
-
-# Wire up our API using automatic URL routing.
-# Additionally, we include login URLs for the browsable API.
-urlpatterns = [
- url('^', include(router.urls)),
- url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
-]
diff --git a/apps/api/viewsets.py b/apps/api/viewsets.py
deleted file mode 100644
index 785e446..0000000
--- a/apps/api/viewsets.py
+++ /dev/null
@@ -1,124 +0,0 @@
-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)
\ No newline at end of file
diff --git a/apps/member/__init__.py b/apps/member/__init__.py
deleted file mode 100644
index 6bb559b..0000000
--- a/apps/member/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = 'member.apps.MemberConfig'
diff --git a/apps/member/admin.py b/apps/member/admin.py
deleted file mode 100644
index a41bb92..0000000
--- a/apps/member/admin.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from django.contrib.auth.admin import admin
-from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin
-from member.models import TFJMUser, Document, Solution, Synthesis, MotivationLetter, Authorization, Config
-
-
-@admin.register(TFJMUser)
-class TFJMUserAdmin(admin.ModelAdmin):
- """
- Django admin page for users.
- """
- list_display = ('email', 'first_name', 'last_name', 'role', )
- search_fields = ('last_name', 'first_name',)
-
-
-@admin.register(Document)
-class DocumentAdmin(PolymorphicParentModelAdmin):
- """
- Django admin page for any documents.
- """
- child_models = (Authorization, MotivationLetter, Solution, Synthesis,)
- polymorphic_list = True
-
-
-@admin.register(Authorization)
-class AuthorizationAdmin(PolymorphicChildModelAdmin):
- """
- Django admin page for Authorization.
- """
-
-
-@admin.register(MotivationLetter)
-class MotivationLetterAdmin(PolymorphicChildModelAdmin):
- """
- Django admin page for Motivation letters.
- """
-
-
-@admin.register(Solution)
-class SolutionAdmin(PolymorphicChildModelAdmin):
- """
- Django admin page for solutions.
- """
-
-
-@admin.register(Synthesis)
-class SynthesisAdmin(PolymorphicChildModelAdmin):
- """
- Django admin page for syntheses.
- """
-
-
-@admin.register(Config)
-class ConfigAdmin(admin.ModelAdmin):
- """
- Django admin page for configurations.
- """
diff --git a/apps/member/apps.py b/apps/member/apps.py
deleted file mode 100644
index 61c9ae8..0000000
--- a/apps/member/apps.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.apps import AppConfig
-from django.utils.translation import gettext_lazy as _
-
-
-class MemberConfig(AppConfig):
- """
- The member app handles the information that concern a user, its documents, ...
- """
- name = 'member'
- verbose_name = _('member')
diff --git a/apps/member/forms.py b/apps/member/forms.py
deleted file mode 100644
index 6fb9f6c..0000000
--- a/apps/member/forms.py
+++ /dev/null
@@ -1,83 +0,0 @@
-from bootstrap_datepicker_plus import DatePickerInput
-from django.contrib.auth.forms import UserCreationForm
-from django import forms
-from django.utils.translation import gettext_lazy as _
-
-from .models import TFJMUser
-
-
-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):
- super().__init__(*args, **kwargs)
- self.fields["first_name"].required = True
- self.fields["last_name"].required = True
- self.fields["role"].choices = [
- ('', _("Choose a role...")),
- ('3participant', _("Participant")),
- ('2coach', _("Coach")),
- ]
-
- class Meta:
- model = TFJMUser
- fields = (
- 'role',
- 'email',
- 'first_name',
- 'last_name',
- 'birth_date',
- 'gender',
- 'address',
- 'postal_code',
- 'city',
- 'country',
- 'phone_number',
- 'school',
- 'student_class',
- 'responsible_name',
- 'responsible_phone',
- 'responsible_email',
- 'description',
- )
- widgets = {
- "birth_date": DatePickerInput(),
- }
-
-
-class TFJMUserForm(forms.ModelForm):
- """
- Form to update our own information when we are participant.
- """
- class Meta:
- model = TFJMUser
- fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
- 'city', 'country', 'school', 'student_class', 'responsible_name', 'responsible_phone',
- 'responsible_email',)
- widgets = {
- "birth_date": DatePickerInput(),
- }
-
-
-class CoachUserForm(forms.ModelForm):
- """
- Form to update our own information when we are coach.
- """
- class Meta:
- model = TFJMUser
- fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
- 'city', 'country', 'description',)
- widgets = {
- "birth_date": DatePickerInput(),
- }
-
-
-class AdminUserForm(forms.ModelForm):
- """
- Form to update our own information when we are organizer or admin.
- """
- class Meta:
- model = TFJMUser
- fields = ('last_name', 'first_name', 'email', 'phone_number', 'description',)
diff --git a/apps/member/management/__init__.py b/apps/member/management/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/member/management/commands/__init__.py b/apps/member/management/commands/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/member/management/commands/create_su.py b/apps/member/management/commands/create_su.py
deleted file mode 100644
index 93ec091..0000000
--- a/apps/member/management/commands/create_su.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import os
-from datetime import date
-from getpass import getpass
-from django.core.management import BaseCommand
-from member.models import TFJMUser
-
-
-class Command(BaseCommand):
- def handle(self, *args, **options):
- """
- Little script that generate a superuser.
- """
- email = input("Email: ")
- password = "1"
- confirm_password = "2"
- while password != confirm_password:
- password = getpass("Password: ")
- confirm_password = getpass("Confirm password: ")
- if password != confirm_password:
- self.stderr.write(self.style.ERROR("Passwords don't match."))
-
- user = TFJMUser.objects.create(
- email=email,
- password="",
- role="admin",
- year=os.getenv("TFJM_YEAR", date.today().year),
- is_active=True,
- is_staff=True,
- is_superuser=True,
- )
- user.set_password(password)
- user.save()
diff --git a/apps/member/management/commands/extract_solutions.py b/apps/member/management/commands/extract_solutions.py
deleted file mode 100644
index 7b59ad1..0000000
--- a/apps/member/management/commands/extract_solutions.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import os
-from urllib.request import urlretrieve
-from shutil import copyfile
-
-from django.core.management import BaseCommand
-from django.utils import translation
-from member.models import Solution
-from tournament.models import Tournament
-
-
-class Command(BaseCommand):
- PROBLEMS = [
- 'Création de puzzles',
- 'Départ en vacances',
- 'Un festin stratégique',
- 'Sauver les meubles',
- 'Prêt à décoller !',
- 'Ils nous espionnent !',
- 'De joyeux bûcherons',
- 'Robots auto-réplicateurs',
- ]
-
- def add_arguments(self, parser):
- parser.add_argument('dir',
- type=str,
- default='.',
- help="Directory where solutions should be saved.")
- parser.add_argument('--language', '-l',
- type=str,
- choices=['en', 'fr'],
- default='fr',
- help="Language of the title of the files.")
-
- def handle(self, *args, **options):
- """
- Copy solutions elsewhere.
- """
- d = options['dir']
- teams_dir = d + '/Par équipe'
- os.makedirs(teams_dir, exist_ok=True)
-
- translation.activate(options['language'])
-
- copied = 0
-
- for tournament in Tournament.objects.all():
- os.mkdir(teams_dir + '/' + tournament.name)
- for team in tournament.teams.filter(validation_status='2valid'):
- os.mkdir(teams_dir + '/' + tournament.name + '/' + str(team))
- for sol in tournament.solutions:
- if not os.path.isfile('media/' + sol.file.name):
- self.stdout.write(self.style.WARNING(("Warning: solution '{sol}' is not found. Maybe the file"
- "was deleted?").format(sol=str(sol))))
- continue
- copyfile('media/' + sol.file.name, teams_dir + '/' + tournament.name
- + '/' + str(sol.team) + '/' + str(sol) + '.pdf')
- copied += 1
-
- self.stdout.write(self.style.SUCCESS("Successfully copied {copied} solutions!".format(copied=copied)))
-
- os.mkdir(d + '/Par problème')
-
- for pb in range(1, 9):
- sols = Solution.objects.filter(problem=pb).all()
- pbdir = d + '/Par problème/Problème n°{number} — {problem}'.format(number=pb, problem=self.PROBLEMS[pb - 1])
- os.mkdir(pbdir)
- for sol in sols:
- os.symlink('../../Par équipe/' + sol.tournament.name + '/' + str(sol.team) + '/' + str(sol) + '.pdf',
- pbdir + '/' + str(sol) + '.pdf')
-
- self.stdout.write(self.style.SUCCESS("Symlinks by problem created!"))
-
- urlretrieve('https://tfjm.org/wp-content/uploads/2020/01/Problemes2020_23_01_v1_1.pdf', d + '/Énoncés.pdf')
-
- self.stdout.write(self.style.SUCCESS("Questions retrieved!"))
diff --git a/apps/member/management/commands/import_olddb.py b/apps/member/management/commands/import_olddb.py
deleted file mode 100644
index d3ff94f..0000000
--- a/apps/member/management/commands/import_olddb.py
+++ /dev/null
@@ -1,309 +0,0 @@
-import os
-
-from django.core.management import BaseCommand, CommandError
-from django.db import transaction
-from member.models import TFJMUser, Document, Solution, Synthesis, Authorization, MotivationLetter
-from tournament.models import Team, Tournament
-
-
-class Command(BaseCommand):
- """
- Import the old database.
- Tables must be found into the import_olddb folder, as CSV files.
- """
-
- def add_arguments(self, parser):
- parser.add_argument('--tournaments', '-t', action="store", help="Import tournaments")
- parser.add_argument('--teams', '-T', action="store", help="Import teams")
- parser.add_argument('--users', '-u', action="store", help="Import users")
- parser.add_argument('--documents', '-d', action="store", help="Import all documents")
-
- def handle(self, *args, **options):
- if "tournaments" in options:
- self.import_tournaments()
-
- if "teams" in options:
- self.import_teams()
-
- if "users" in options:
- self.import_users()
-
- if "documents" in options:
- self.import_documents()
-
- @transaction.atomic
- def import_tournaments(self):
- """
- Import tournaments into the new database.
- """
- print("Importing tournaments...")
- with open("import_olddb/tournaments.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- if Tournament.objects.filter(pk=args[0]).exists():
- continue
-
- obj_dict = {
- "id": args[0],
- "name": args[1],
- "size": args[2],
- "place": args[3],
- "price": args[4],
- "description": args[5],
- "date_start": args[6],
- "date_end": args[7],
- "date_inscription": args[8],
- "date_solutions": args[9],
- "date_syntheses": args[10],
- "date_solutions_2": args[11],
- "date_syntheses_2": args[12],
- "final": args[13],
- "year": args[14],
- }
- with transaction.atomic():
- Tournament.objects.create(**obj_dict)
- print(self.style.SUCCESS("Tournaments imported"))
-
- @staticmethod
- def validation_status(status):
- if status == "NOT_READY":
- return "0invalid"
- elif status == "WAITING":
- return "1waiting"
- elif status == "VALIDATED":
- return "2valid"
- else:
- raise CommandError("Unknown status: {}".format(status))
-
- @transaction.atomic
- def import_teams(self):
- """
- Import teams into new database.
- """
- self.stdout.write("Importing teams...")
- with open("import_olddb/teams.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- if Team.objects.filter(pk=args[0]).exists():
- continue
-
- obj_dict = {
- "id": args[0],
- "name": args[1],
- "trigram": args[2],
- "tournament": Tournament.objects.get(pk=args[3]),
- "inscription_date": args[13],
- "validation_status": Command.validation_status(args[14]),
- "selected_for_final": args[15],
- "access_code": args[16],
- "year": args[17],
- }
- with transaction.atomic():
- Team.objects.create(**obj_dict)
- print(self.style.SUCCESS("Teams imported"))
-
- @staticmethod
- def role(role):
- if role == "ADMIN":
- return "0admin"
- elif role == "ORGANIZER":
- return "1volunteer"
- elif role == "ENCADRANT":
- return "2coach"
- elif role == "PARTICIPANT":
- return "3participant"
- else:
- raise CommandError("Unknown role: {}".format(role))
-
- @transaction.atomic
- def import_users(self):
- """
- Import users into the new database.
- :return:
- """
- self.stdout.write("Importing users...")
- with open("import_olddb/users.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- if TFJMUser.objects.filter(pk=args[0]).exists():
- continue
-
- obj_dict = {
- "id": args[0],
- "email": args[1],
- "username": args[1],
- "password": "bcrypt$" + args[2],
- "last_name": args[3],
- "first_name": args[4],
- "birth_date": args[5],
- "gender": "male" if args[6] == "M" else "female",
- "address": args[7],
- "postal_code": args[8],
- "city": args[9],
- "country": args[10],
- "phone_number": args[11],
- "school": args[12],
- "student_class": args[13].lower().replace('premiere', 'première') if args[13] else None,
- "responsible_name": args[14],
- "responsible_phone": args[15],
- "responsible_email": args[16],
- "description": args[17].replace("\\n", "\n") if args[17] else None,
- "role": Command.role(args[18]),
- "team": Team.objects.get(pk=args[19]) if args[19] else None,
- "year": args[20],
- "date_joined": args[23],
- "is_active": args[18] == "ADMIN" or os.getenv("TFJM_STAGE", "dev") == "prod",
- "is_staff": args[18] == "ADMIN",
- "is_superuser": args[18] == "ADMIN",
- }
- with transaction.atomic():
- TFJMUser.objects.create(**obj_dict)
- self.stdout.write(self.style.SUCCESS("Users imported"))
-
- self.stdout.write("Importing organizers...")
- # We also import the information about the organizers of a tournament.
- with open("import_olddb/organizers.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- with transaction.atomic():
- tournament = Tournament.objects.get(pk=args[2])
- organizer = TFJMUser.objects.get(pk=args[1])
- tournament.organizers.add(organizer)
- tournament.save()
- self.stdout.write(self.style.SUCCESS("Organizers imported"))
-
- @transaction.atomic
- def import_documents(self):
- """
- Import the documents (authorizations, motivation letters, solutions, syntheses) from the old database.
- """
- self.stdout.write("Importing documents...")
- with open("import_olddb/documents.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- if Document.objects.filter(file=args[0]).exists():
- doc = Document.objects.get(file=args[0])
- doc.uploaded_at = args[5].replace(" ", "T")
- doc.save()
- continue
-
- obj_dict = {
- "file": args[0],
- "uploaded_at": args[5],
- }
- if args[4] != "MOTIVATION_LETTER":
- obj_dict["user"] = TFJMUser.objects.get(args[1]),
- obj_dict["type"] = args[4].lower()
- else:
- try:
- obj_dict["team"] = Team.objects.get(pk=args[2])
- except Team.DoesNotExist:
- print("Team with pk {} does not exist, ignoring".format(args[2]))
- continue
- with transaction.atomic():
- if args[4] != "MOTIVATION_LETTER":
- Authorization.objects.create(**obj_dict)
- else:
- MotivationLetter.objects.create(**obj_dict)
- self.stdout.write(self.style.SUCCESS("Authorizations imported"))
-
- with open("import_olddb/solutions.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- if Document.objects.filter(file=args[0]).exists():
- doc = Document.objects.get(file=args[0])
- doc.uploaded_at = args[4].replace(" ", "T")
- doc.save()
- continue
-
- obj_dict = {
- "file": args[0],
- "team": Team.objects.get(pk=args[1]),
- "problem": args[3],
- "uploaded_at": args[4],
- }
- with transaction.atomic():
- try:
- Solution.objects.create(**obj_dict)
- except:
- print("Solution exists")
- self.stdout.write(self.style.SUCCESS("Solutions imported"))
-
- with open("import_olddb/syntheses.csv") as f:
- first_line = True
- for line in f:
- if first_line:
- first_line = False
- continue
-
- line = line[:-1].replace("\"", "")
- args = line.split(";")
- args = [arg if arg and arg != "NULL" else None for arg in args]
-
- if Document.objects.filter(file=args[0]).exists():
- doc = Document.objects.get(file=args[0])
- doc.uploaded_at = args[5].replace(" ", "T")
- doc.save()
- continue
-
- obj_dict = {
- "file": args[0],
- "team": Team.objects.get(pk=args[1]),
- "source": "opponent" if args[3] == "1" else "rapporteur",
- "round": args[4],
- "uploaded_at": args[5],
- }
- with transaction.atomic():
- try:
- Synthesis.objects.create(**obj_dict)
- except:
- print("Synthesis exists")
- self.stdout.write(self.style.SUCCESS("Syntheses imported"))
diff --git a/apps/member/migrations/0001_initial.py b/apps/member/migrations/0001_initial.py
deleted file mode 100644
index fab265e..0000000
--- a/apps/member/migrations/0001_initial.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# Generated by Django 3.1 on 2020-09-19 18:15
-
-import django.contrib.auth.models
-import django.contrib.auth.validators
-from django.db import migrations, models
-import django.db.models.deletion
-import django.utils.timezone
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ('auth', '0012_alter_user_first_name_max_length'),
- ('contenttypes', '0002_remove_content_type_name'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Config',
- fields=[
- ('key', models.CharField(max_length=255, primary_key=True, serialize=False, verbose_name='key')),
- ('value', models.TextField(default='', verbose_name='value')),
- ],
- options={
- 'verbose_name': 'configuration',
- 'verbose_name_plural': 'configurations',
- },
- ),
- migrations.CreateModel(
- name='Document',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('file', models.FileField(unique=True, upload_to='', verbose_name='file')),
- ('uploaded_at', models.DateTimeField(auto_now_add=True, verbose_name='uploaded at')),
- ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_member.document_set+', to='contenttypes.contenttype')),
- ],
- options={
- 'verbose_name': 'document',
- 'verbose_name_plural': 'documents',
- },
- ),
- migrations.CreateModel(
- name='Authorization',
- fields=[
- ('document_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='member.document')),
- ('type', models.CharField(choices=[('parental_consent', 'Parental consent'), ('photo_consent', 'Photo consent'), ('sanitary_plug', 'Sanitary plug'), ('scholarship', 'Scholarship')], max_length=32, verbose_name='type')),
- ],
- options={
- 'verbose_name': 'authorization',
- 'verbose_name_plural': 'authorizations',
- },
- bases=('member.document',),
- ),
- migrations.CreateModel(
- name='MotivationLetter',
- fields=[
- ('document_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='member.document')),
- ],
- options={
- 'verbose_name': 'motivation letter',
- 'verbose_name_plural': 'motivation letters',
- },
- bases=('member.document',),
- ),
- migrations.CreateModel(
- name='Solution',
- fields=[
- ('document_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='member.document')),
- ('problem', models.PositiveSmallIntegerField(verbose_name='problem')),
- ('final', models.BooleanField(default=False, verbose_name='final solution')),
- ],
- options={
- 'verbose_name': 'solution',
- 'verbose_name_plural': 'solutions',
- },
- bases=('member.document',),
- ),
- migrations.CreateModel(
- name='Synthesis',
- fields=[
- ('document_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='member.document')),
- ('source', models.CharField(choices=[('opponent', 'Opponent'), ('rapporteur', 'Rapporteur')], max_length=16, verbose_name='source')),
- ('round', models.PositiveSmallIntegerField(choices=[(1, 'Round 1'), (2, 'Round 2')], verbose_name='round')),
- ('final', models.BooleanField(default=False, verbose_name='final synthesis')),
- ],
- options={
- 'verbose_name': 'synthesis',
- 'verbose_name_plural': 'syntheses',
- },
- bases=('member.document',),
- ),
- migrations.CreateModel(
- name='TFJMUser',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('password', models.CharField(max_length=128, verbose_name='password')),
- ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
- ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
- ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
- ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
- ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
- ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
- ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
- ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
- ('email', models.EmailField(help_text='This should be valid and will be controlled.', max_length=254, unique=True, verbose_name='email')),
- ('birth_date', models.DateField(default=None, null=True, verbose_name='birth date')),
- ('gender', models.CharField(choices=[('male', 'Male'), ('female', 'Female'), ('non-binary', 'Non binary')], default=None, max_length=16, null=True, verbose_name='gender')),
- ('address', models.CharField(default=None, max_length=255, null=True, verbose_name='address')),
- ('postal_code', models.PositiveIntegerField(default=None, null=True, verbose_name='postal code')),
- ('city', models.CharField(default=None, max_length=255, null=True, verbose_name='city')),
- ('country', models.CharField(default='France', max_length=255, null=True, verbose_name='country')),
- ('phone_number', models.CharField(blank=True, default=None, max_length=20, null=True, verbose_name='phone number')),
- ('school', models.CharField(default=None, max_length=255, null=True, verbose_name='school')),
- ('student_class', models.CharField(choices=[('seconde', 'Seconde or less'), ('première', 'Première'), ('terminale', 'Terminale')], default=None, max_length=16, null=True, verbose_name='class')),
- ('responsible_name', models.CharField(default=None, max_length=255, null=True, verbose_name='responsible name')),
- ('responsible_phone', models.CharField(default=None, max_length=20, null=True, verbose_name='responsible phone')),
- ('responsible_email', models.EmailField(default=None, max_length=254, null=True, verbose_name='responsible email')),
- ('description', models.TextField(default=None, null=True, verbose_name='description')),
- ('role', models.CharField(choices=[('0admin', 'Admin'), ('1volunteer', 'Organizer'), ('2coach', 'Coach'), ('3participant', 'Participant')], max_length=16)),
- ('year', models.PositiveIntegerField(default=2020, verbose_name='year')),
- ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
- ],
- options={
- 'verbose_name': 'user',
- 'verbose_name_plural': 'users',
- },
- managers=[
- ('objects', django.contrib.auth.models.UserManager()),
- ],
- ),
- ]
diff --git a/apps/member/migrations/0002_auto_20200919_2015.py b/apps/member/migrations/0002_auto_20200919_2015.py
deleted file mode 100644
index 7997bc8..0000000
--- a/apps/member/migrations/0002_auto_20200919_2015.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Generated by Django 3.1 on 2020-09-19 18:15
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ('member', '0001_initial'),
- ('auth', '0012_alter_user_first_name_max_length'),
- ('tournament', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='tfjmuser',
- name='team',
- field=models.ForeignKey(help_text='Concerns only coaches and participants.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='tournament.team', verbose_name='team'),
- ),
- migrations.AddField(
- model_name='tfjmuser',
- name='user_permissions',
- field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
- ),
- migrations.AddField(
- model_name='synthesis',
- name='team',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='syntheses', to='tournament.team', verbose_name='team'),
- ),
- migrations.AddField(
- model_name='solution',
- name='team',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='tournament.team', verbose_name='team'),
- ),
- migrations.AddField(
- model_name='motivationletter',
- name='team',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='motivation_letters', to='tournament.team', verbose_name='team'),
- ),
- migrations.AddField(
- model_name='authorization',
- name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='authorizations', to=settings.AUTH_USER_MODEL, verbose_name='user'),
- ),
- migrations.AlterUniqueTogether(
- name='synthesis',
- unique_together={('team', 'source', 'round', 'final')},
- ),
- migrations.AlterUniqueTogether(
- name='solution',
- unique_together={('team', 'problem', 'final')},
- ),
- ]
diff --git a/apps/member/migrations/0003_tfjmuser_email_confirmed.py b/apps/member/migrations/0003_tfjmuser_email_confirmed.py
deleted file mode 100644
index c4bb95b..0000000
--- a/apps/member/migrations/0003_tfjmuser_email_confirmed.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 3.1 on 2020-09-19 20:45
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('member', '0002_auto_20200919_2015'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='tfjmuser',
- name='email_confirmed',
- field=models.BooleanField(default=False, verbose_name='email confirmed'),
- ),
- ]
diff --git a/apps/member/migrations/__init__.py b/apps/member/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/member/models.py b/apps/member/models.py
deleted file mode 100644
index 5ec4ffc..0000000
--- a/apps/member/models.py
+++ /dev/null
@@ -1,399 +0,0 @@
-import os
-from datetime import date
-
-from django.contrib.auth.models import AbstractUser
-from django.contrib.sites.models import Site
-from django.db import models
-from django.template import loader
-from django.utils.encoding import force_bytes
-from django.utils.http import urlsafe_base64_encode
-from django.utils.translation import gettext_lazy as _
-from polymorphic.models import PolymorphicModel
-from tournament.models import Team, Tournament
-
-from .tokens import email_validation_token
-
-
-class TFJMUser(AbstractUser):
- """
- The model of registered users (organizers/juries/admins/coachs/participants)
- """
- USERNAME_FIELD = 'email'
- REQUIRED_FIELDS = []
-
- email = models.EmailField(
- unique=True,
- verbose_name=_("email"),
- help_text=_("This should be valid and will be controlled."),
- )
-
- team = models.ForeignKey(
- Team,
- null=True,
- on_delete=models.SET_NULL,
- related_name="users",
- verbose_name=_("team"),
- help_text=_("Concerns only coaches and participants."),
- )
-
- birth_date = models.DateField(
- null=True,
- default=None,
- verbose_name=_("birth date"),
- )
-
- gender = models.CharField(
- max_length=16,
- null=True,
- default=None,
- choices=[
- ("male", _("Male")),
- ("female", _("Female")),
- ("non-binary", _("Non binary")),
- ],
- verbose_name=_("gender"),
- )
-
- address = models.CharField(
- max_length=255,
- null=True,
- default=None,
- verbose_name=_("address"),
- )
-
- postal_code = models.PositiveIntegerField(
- null=True,
- default=None,
- verbose_name=_("postal code"),
- )
-
- city = models.CharField(
- max_length=255,
- null=True,
- default=None,
- verbose_name=_("city"),
- )
-
- country = models.CharField(
- max_length=255,
- default="France",
- null=True,
- verbose_name=_("country"),
- )
-
- phone_number = models.CharField(
- max_length=20,
- null=True,
- blank=True,
- default=None,
- verbose_name=_("phone number"),
- )
-
- school = models.CharField(
- max_length=255,
- null=True,
- default=None,
- verbose_name=_("school"),
- )
-
- student_class = models.CharField(
- max_length=16,
- choices=[
- ('seconde', _("Seconde or less")),
- ('première', _("Première")),
- ('terminale', _("Terminale")),
- ],
- null=True,
- default=None,
- verbose_name="class",
- )
-
- responsible_name = models.CharField(
- max_length=255,
- null=True,
- default=None,
- verbose_name=_("responsible name"),
- )
-
- responsible_phone = models.CharField(
- max_length=20,
- null=True,
- default=None,
- verbose_name=_("responsible phone"),
- )
-
- responsible_email = models.EmailField(
- null=True,
- default=None,
- verbose_name=_("responsible email"),
- )
-
- description = models.TextField(
- null=True,
- default=None,
- verbose_name=_("description"),
- )
-
- role = models.CharField(
- max_length=16,
- choices=[
- ("0admin", _("Admin")),
- ("1volunteer", _("Organizer")),
- ("2coach", _("Coach")),
- ("3participant", _("Participant")),
- ]
- )
-
- year = models.PositiveIntegerField(
- default=os.getenv("TFJM_YEAR", date.today().year),
- verbose_name=_("year"),
- )
-
- email_confirmed = models.BooleanField(
- verbose_name=_("email confirmed"),
- default=False,
- )
-
- @property
- 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"
-
- @property
- 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"
-
- @property
- 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"
-
- class Meta:
- verbose_name = _("user")
- verbose_name_plural = _("users")
-
- def save(self, *args, **kwargs):
- # We ensure that the username is the email of the user.
- self.username = self.email
- super().save(*args, **kwargs)
-
- def __str__(self):
- return self.first_name + " " + self.last_name
-
- def send_email_validation_link(self):
- subject = "[TFJM²] " + str(_("Activate your Note Kfet account"))
- token = email_validation_token.make_token(self)
- uid = urlsafe_base64_encode(force_bytes(self.pk))
- site = Site.objects.first()
- message = loader.render_to_string('registration/mails/email_validation_email.txt',
- {
- 'user': self,
- 'domain': site.domain,
- 'token': token,
- 'uid': uid,
- })
- html = loader.render_to_string('registration/mails/email_validation_email.html',
- {
- 'user': self,
- 'domain': site.domain,
- 'token': token,
- 'uid': uid,
- })
- self.email_user(subject, message, html_message=html)
-
-
-class Document(PolymorphicModel):
- """
- Abstract model of any saved document (solution, synthesis, motivation letter, authorization)
- """
- file = models.FileField(
- unique=True,
- verbose_name=_("file"),
- )
-
- uploaded_at = models.DateTimeField(
- auto_now_add=True,
- verbose_name=_("uploaded at"),
- )
-
- class Meta:
- verbose_name = _("document")
- verbose_name_plural = _("documents")
-
- def delete(self, *args, **kwargs):
- self.file.delete(True)
- return super().delete(*args, **kwargs)
-
-
-class Authorization(Document):
- """
- Model for authorization papers (parental consent, photo consent, sanitary plug, ...)
- """
- user = models.ForeignKey(
- TFJMUser,
- on_delete=models.CASCADE,
- related_name="authorizations",
- verbose_name=_("user"),
- )
-
- type = models.CharField(
- max_length=32,
- choices=[
- ("parental_consent", _("Parental consent")),
- ("photo_consent", _("Photo consent")),
- ("sanitary_plug", _("Sanitary plug")),
- ("scholarship", _("Scholarship")),
- ],
- verbose_name=_("type"),
- )
-
- class Meta:
- verbose_name = _("authorization")
- verbose_name_plural = _("authorizations")
-
- def __str__(self):
- return _("{authorization} for user {user}").format(authorization=self.type, user=str(self.user))
-
-
-class MotivationLetter(Document):
- """
- Model for motivation letters of a team.
- """
- team = models.ForeignKey(
- Team,
- on_delete=models.CASCADE,
- related_name="motivation_letters",
- verbose_name=_("team"),
- )
-
- class Meta:
- verbose_name = _("motivation letter")
- verbose_name_plural = _("motivation letters")
-
- def __str__(self):
- return _("Motivation letter of team {team} ({trigram})").format(team=self.team.name, trigram=self.team.trigram)
-
-
-class Solution(Document):
- """
- Model for solutions of team for a given problem, for the regional or final tournament.
- """
- team = models.ForeignKey(
- Team,
- on_delete=models.CASCADE,
- related_name="solutions",
- verbose_name=_("team"),
- )
-
- problem = models.PositiveSmallIntegerField(
- verbose_name=_("problem"),
- )
-
- final = models.BooleanField(
- default=False,
- verbose_name=_("final solution"),
- )
-
- @property
- 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
-
- class Meta:
- verbose_name = _("solution")
- verbose_name_plural = _("solutions")
- unique_together = ('team', 'problem', 'final',)
-
- def __str__(self):
- if self.final:
- return _("Solution of team {trigram} for problem {problem} for final")\
- .format(trigram=self.team.trigram, problem=self.problem)
- else:
- return _("Solution of team {trigram} for problem {problem}")\
- .format(trigram=self.team.trigram, problem=self.problem)
-
-
-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,
- on_delete=models.CASCADE,
- related_name="syntheses",
- verbose_name=_("team"),
- )
-
- source = models.CharField(
- max_length=16,
- choices=[
- ("opponent", _("Opponent")),
- ("rapporteur", _("Rapporteur")),
- ],
- verbose_name=_("source"),
- )
-
- round = models.PositiveSmallIntegerField(
- choices=[
- (1, _("Round 1")),
- (2, _("Round 2")),
- ],
- verbose_name=_("round"),
- )
-
- final = models.BooleanField(
- default=False,
- verbose_name=_("final synthesis"),
- )
-
- @property
- 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
-
- class Meta:
- verbose_name = _("synthesis")
- verbose_name_plural = _("syntheses")
- unique_together = ('team', 'source', 'round', 'final',)
-
- def __str__(self):
- return _("Synthesis of team {trigram} that is {source} for the round {round} of tournament {tournament}")\
- .format(trigram=self.team.trigram, source=self.get_source_display().lower(), round=self.round,
- tournament=self.tournament)
-
-
-class Config(models.Model):
- """
- Dictionary of configuration variables.
- """
- key = models.CharField(
- max_length=255,
- primary_key=True,
- verbose_name=_("key"),
- )
-
- value = models.TextField(
- default="",
- verbose_name=_("value"),
- )
-
- class Meta:
- verbose_name = _("configuration")
- verbose_name_plural = _("configurations")
diff --git a/apps/member/tables.py b/apps/member/tables.py
deleted file mode 100644
index 779dc47..0000000
--- a/apps/member/tables.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import django_tables2 as tables
-from django_tables2 import A
-
-from .models import TFJMUser
-
-
-class UserTable(tables.Table):
- """
- Table of users that are matched with a given queryset.
- """
- last_name = tables.LinkColumn(
- "member:information",
- args=[A("pk")],
- )
-
- first_name = tables.LinkColumn(
- "member:information",
- args=[A("pk")],
- )
-
- class Meta:
- model = TFJMUser
- fields = ("last_name", "first_name", "role", "date_joined", )
- attrs = {
- 'class': 'table table-condensed table-striped table-hover'
- }
diff --git a/apps/member/templatetags/__init__.py b/apps/member/templatetags/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/member/templatetags/getconfig.py b/apps/member/templatetags/getconfig.py
deleted file mode 100644
index 0c6d776..0000000
--- a/apps/member/templatetags/getconfig.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from django import template
-
-import os
-
-from member.models import Config
-
-
-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]
- return config.value
-
-
-def get_env(value):
- """
- Get a specified environment variable.
- """
- return os.getenv(value)
-
-
-register = template.Library()
-register.filter('get_config', get_config)
-register.filter('get_env', get_env)
diff --git a/apps/member/tokens.py b/apps/member/tokens.py
deleted file mode 100644
index d089892..0000000
--- a/apps/member/tokens.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from django.contrib.auth.tokens import PasswordResetTokenGenerator
-
-
-class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
- """
- Create a unique token generator to confirm email addresses.
- """
- def _make_hash_value(self, user, timestamp):
- """
- Hash the user's primary key and some user state that's sure to change
- after an account validation to produce a token that invalidated when
- it's used:
- 1. The user.profile.email_confirmed field will change upon an account
- validation.
- 2. The last_login field will usually be updated very shortly after
- an account validation.
- Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually
- invalidates the token.
- """
- # Truncate microseconds so that tokens are consistent even if the
- # database doesn't support microseconds.
- login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
- return str(user.pk) + str(user.email) + str(user.email_confirmed) + str(login_timestamp) + str(timestamp)
-
-
-email_validation_token = AccountActivationTokenGenerator()
diff --git a/apps/member/urls.py b/apps/member/urls.py
deleted file mode 100644
index ffe31fa..0000000
--- a/apps/member/urls.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.urls import path
-
-from .views import CreateUserView, MyAccountView, UserDetailView, AddTeamView, JoinTeamView, MyTeamView, \
- ProfileListView, OrphanedProfileListView, OrganizersListView, ResetAdminView, UserValidationEmailSentView, \
- UserResendValidationEmailView, UserValidateView
-
-app_name = "member"
-
-urlpatterns = [
- path('signup/', CreateUserView.as_view(), name="signup"),
- path('validate_email/sent/', UserValidationEmailSentView.as_view(), name='email_validation_sent'),
- path('validate_email/resend//', UserResendValidationEmailView.as_view(),
- name='email_validation_resend'),
- path('validate_email///', UserValidateView.as_view(), name='email_validation'),
- path("my-account/", MyAccountView.as_view(), name="my_account"),
- path("information//", UserDetailView.as_view(), name="information"),
- path("add-team/", AddTeamView.as_view(), name="add_team"),
- path("join-team/", JoinTeamView.as_view(), name="join_team"),
- path("my-team/", MyTeamView.as_view(), name="my_team"),
- path("profiles/", ProfileListView.as_view(), name="all_profiles"),
- path("orphaned-profiles/", OrphanedProfileListView.as_view(), name="orphaned_profiles"),
- path("organizers/", OrganizersListView.as_view(), name="organizers"),
- path("reset-admin/", ResetAdminView.as_view(), name="reset_admin"),
-]
diff --git a/apps/member/views.py b/apps/member/views.py
deleted file mode 100644
index aef0c61..0000000
--- a/apps/member/views.py
+++ /dev/null
@@ -1,374 +0,0 @@
-import random
-
-from django.conf import settings
-from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
-from django.contrib.auth.models import AnonymousUser
-from django.core.exceptions import PermissionDenied, ValidationError
-from django.db.models import Q
-from django.http import FileResponse, Http404
-from django.shortcuts import redirect, resolve_url
-from django.urls import reverse_lazy
-from django.utils import timezone
-from django.utils.decorators import method_decorator
-from django.utils.http import urlsafe_base64_decode
-from django.utils.translation import gettext_lazy as _
-from django.views import View
-from django.views.decorators.debug import sensitive_post_parameters
-from django.views.generic import CreateView, UpdateView, DetailView, FormView, TemplateView
-from django_tables2 import SingleTableView
-from tournament.forms import TeamForm, JoinTeam
-from tournament.models import Team, Tournament, Pool
-from tournament.views import AdminMixin, TeamMixin, OrgaMixin
-
-from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm
-from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis
-from .tables import UserTable
-from .tokens import email_validation_token
-
-
-class CreateUserView(CreateView):
- """
- Signup form view.
- """
- model = TFJMUser
- form_class = SignUpForm
- template_name = "registration/signup.html"
-
- # When errors are reported from the signup view, don't send passwords to admins
- @method_decorator(sensitive_post_parameters('password1', 'password2',))
- def dispatch(self, request, *args, **kwargs):
- return super().dispatch(request, *args, **kwargs)
-
- def form_valid(self, form):
- form.instance.send_email_validation_link()
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse_lazy('member:email_validation_sent')
-
-
-class UserValidateView(TemplateView):
- """
- A view to validate the email address.
- """
- title = _("Email validation")
- template_name = 'registration/email_validation_complete.html'
- extra_context = {"title": _("Validate email")}
-
- def get(self, *args, **kwargs):
- """
- With a given token and user id (in params), validate the email address.
- """
- assert 'uidb64' in kwargs and 'token' in kwargs
-
- self.validlink = False
- user = self.get_user(kwargs['uidb64'])
- token = kwargs['token']
-
- # Validate the token
- if user is not None and email_validation_token.check_token(user, token):
- self.validlink = True
- user.email_confirmed = True
- user.save()
- return self.render_to_response(self.get_context_data(), status=200 if self.validlink else 400)
-
- def get_user(self, uidb64):
- """
- Get user from the base64-encoded string.
- """
- try:
- # urlsafe_base64_decode() decodes to bytestring
- uid = urlsafe_base64_decode(uidb64).decode()
- user = TFJMUser.objects.get(pk=uid)
- except (TypeError, ValueError, OverflowError, TFJMUser.DoesNotExist, ValidationError):
- user = None
- return user
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['user_object'] = self.get_user(self.kwargs["uidb64"])
- context['login_url'] = resolve_url(settings.LOGIN_URL)
- if self.validlink:
- context['validlink'] = True
- else:
- context.update({
- 'title': _('Email validation unsuccessful'),
- 'validlink': False,
- })
- return context
-
-
-class UserValidationEmailSentView(TemplateView):
- """
- Display the information that the validation link has been sent.
- """
- template_name = 'registration/email_validation_email_sent.html'
- extra_context = {"title": _('Email validation email sent')}
-
-
-class UserResendValidationEmailView(LoginRequiredMixin, DetailView):
- """
- Rensend the email validation link.
- """
- model = TFJMUser
- extra_context = {"title": _("Resend email validation link")}
-
- def get(self, request, *args, **kwargs):
- user = self.get_object()
-
- user.profile.send_email_validation_link()
-
- url = 'member:user_detail' if user.profile.registration_valid else 'member:future_user_detail'
- return redirect(url, user.id)
-
-
-class MyAccountView(LoginRequiredMixin, UpdateView):
- """
- Update our personal data.
- """
- model = TFJMUser
- template_name = "member/my_account.html"
-
- 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 \
- if self.request.user.role == "3participant" else CoachUserForm
-
- def get_object(self, queryset=None):
- return self.request.user
-
- def get_success_url(self):
- return reverse_lazy('member:my_account')
-
-
-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
- form_class = TFJMUserForm
- context_object_name = "tfjmuser"
-
- def dispatch(self, request, *args, **kwargs):
- if isinstance(request.user, AnonymousUser):
- raise PermissionDenied
-
- self.object = self.get_object()
-
- if not request.user.admin \
- and (self.object.team is not None and request.user not in self.object.team.tournament.organizers.all())\
- and (self.object.team is not None and self.object.team.selected_for_final
- and request.user not in Tournament.get_final().organizers.all())\
- and self.request.user != self.object:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
- def post(self, request, *args, **kwargs):
- """
- 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.user.admin:
- session = request.session
- session["admin"] = request.user.pk
- obj = self.get_object()
- session["_fake_user_id"] = obj.pk
- return redirect(request.path)
- return self.get(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- context["title"] = str(self.object)
-
- return context
-
-
-class AddTeamView(LoginRequiredMixin, CreateView):
- """
- Register a new team.
- Users can choose the name, the trigram and a preferred tournament.
- """
- model = Team
- form_class = TeamForm
-
- def form_valid(self, form):
- if self.request.user.organizes:
- form.add_error('name', _("You can't organize and participate at the same time."))
- return self.form_invalid(form)
-
- if self.request.user.team:
- form.add_error('name', _("You are already in a team."))
- return self.form_invalid(form)
-
- # Generate a random access code
- team = form.instance
- alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
- code = ""
- for i in range(6):
- code += random.choice(alphabet)
- team.access_code = code
- team.validation_status = "0invalid"
-
- team.save()
- team.refresh_from_db()
-
- self.request.user.team = team
- self.request.user.save()
-
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse_lazy("member:my_team")
-
-
-class JoinTeamView(LoginRequiredMixin, FormView):
- """
- Join a team with a given access code.
- """
- model = Team
- form_class = JoinTeam
- template_name = "tournament/team_form.html"
-
- def form_valid(self, form):
- team = form.cleaned_data["team"]
-
- if self.request.user.organizes:
- form.add_error('access_code', _("You can't organize and participate at the same time."))
- return self.form_invalid(form)
-
- if self.request.user.team:
- form.add_error('access_code', _("You are already in a team."))
- return self.form_invalid(form)
-
- if self.request.user.role == '2coach' and len(team.coaches) == 3:
- form.add_error('access_code', _("This team is full of coachs."))
- return self.form_invalid(form)
-
- if self.request.user.role == '3participant' and len(team.participants) == 6:
- form.add_error('access_code', _("This team is full of participants."))
- 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.save()
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse_lazy("member:my_team")
-
-
-class MyTeamView(TeamMixin, View):
- """
- Redirect to the page of the information of our personal team.
- """
-
- def get(self, request, *args, **kwargs):
- return redirect("tournament:team_detail", pk=request.user.team.pk)
-
-
-class DocumentView(AccessMixin, 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):
- try:
- doc = Document.objects.get(file=self.kwargs["file"])
- except Document.DoesNotExist:
- raise Http404(_("No %(verbose_name)s found matching the query") %
- {'verbose_name': Document._meta.verbose_name})
-
- if request.user.is_authenticated:
- grant = request.user.admin
-
- if isinstance(doc, Solution) or isinstance(doc, Synthesis):
- grant = grant or doc.team == request.user.team or request.user in doc.tournament.organizers.all()
- elif isinstance(doc, MotivationLetter):
- grant = grant or doc.team == request.user.team or request.user in doc.team.tournament.organizers.all()
- grant = grant or doc.team.selected_for_final and request.user in Tournament.get_final().organizers.all()
-
- if isinstance(doc, Solution):
- for pool in doc.pools.all():
- if request.user in pool.juries.all():
- grant = True
- break
- if pool.round == 2 and timezone.now() < doc.tournament.date_solutions_2:
- continue
- if self.request.user.team in pool.teams.all():
- grant = True
- elif isinstance(doc, Synthesis):
- for pool in request.user.pools.all(): # If the user is a jury in the pool
- if doc.team in pool.teams.all() and doc.final == pool.tournament.final:
- grant = True
- break
- else:
- pool = Pool.objects.filter(extra_access_token=self.request.session["extra_access_token"])
- if pool.exists():
- pool = pool.get()
- if isinstance(doc, Solution):
- grant = doc in pool.solutions.all()
- elif isinstance(doc, Synthesis):
- grant = doc.team in pool.teams.all() and doc.final == pool.tournament.final
- else:
- grant = False
- else:
- grant = False
-
- if not grant:
- raise PermissionDenied
-
- return FileResponse(doc.file, content_type="application/pdf", filename=str(doc) + ".pdf")
-
-
-class ProfileListView(AdminMixin, SingleTableView):
- """
- List all registered profiles.
- """
- model = TFJMUser
- queryset = TFJMUser.objects.order_by("role", "last_name", "first_name")
- table_class = UserTable
- template_name = "member/profile_list.html"
- extra_context = dict(title=_("All profiles"), type="all")
-
-
-class OrphanedProfileListView(AdminMixin, SingleTableView):
- """
- List all orphaned profiles, ie. participants that have no team.
- """
- model = TFJMUser
- queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\
- .order_by("role", "last_name", "first_name")
- table_class = UserTable
- template_name = "member/profile_list.html"
- extra_context = dict(title=_("Orphaned profiles"), type="orphaned")
-
-
-class OrganizersListView(OrgaMixin, SingleTableView):
- """
- List all organizers.
- """
- model = TFJMUser
- queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\
- .order_by("role", "last_name", "first_name")
- table_class = UserTable
- template_name = "member/profile_list.html"
- extra_context = dict(title=_("Organizers"), type="organizers")
-
-
-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):
- if "_fake_user_id" in request.session:
- del request.session["_fake_user_id"]
- return redirect(request.GET["path"])
diff --git a/apps/tournament/__init__.py b/apps/tournament/__init__.py
deleted file mode 100644
index 9868b56..0000000
--- a/apps/tournament/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = 'tournament.apps.TournamentConfig'
diff --git a/apps/tournament/admin.py b/apps/tournament/admin.py
deleted file mode 100644
index c55cc4b..0000000
--- a/apps/tournament/admin.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from django.contrib.auth.admin import admin
-
-from .models import Team, Tournament, Pool, Payment
-
-
-@admin.register(Team)
-class TeamAdmin(admin.ModelAdmin):
- """
- Django admin page for teams.
- """
-
-
-@admin.register(Tournament)
-class TournamentAdmin(admin.ModelAdmin):
- """
- Django admin page for tournaments.
- """
-
-
-@admin.register(Pool)
-class PoolAdmin(admin.ModelAdmin):
- """
- Django admin page for pools.
- """
-
-
-@admin.register(Payment)
-class PaymentAdmin(admin.ModelAdmin):
- """
- Django admin page for payments.
- """
diff --git a/apps/tournament/apps.py b/apps/tournament/apps.py
deleted file mode 100644
index 66a212b..0000000
--- a/apps/tournament/apps.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.apps import AppConfig
-from django.utils.translation import gettext_lazy as _
-
-
-class TournamentConfig(AppConfig):
- """
- The tournament app handles all that is related to the tournaments.
- """
- name = 'tournament'
- verbose_name = _('tournament')
diff --git a/apps/tournament/forms.py b/apps/tournament/forms.py
deleted file mode 100644
index 901b97a..0000000
--- a/apps/tournament/forms.py
+++ /dev/null
@@ -1,262 +0,0 @@
-import os
-import re
-
-from django import forms
-from django.db.models import Q
-from django.template.defaultfilters import filesizeformat
-from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-
-from member.models import TFJMUser, Solution, Synthesis
-from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput
-from tournament.models import Tournament, Team, Pool
-
-
-class TournamentForm(forms.ModelForm):
- """
- Create and update tournaments.
- """
-
- # Only organizers can organize tournaments. Well, that's pretty normal...
- organizers = forms.ModelMultipleChoiceField(
- TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer")).order_by('role'),
- label=_("Organizers"),
- )
-
- def clean(self):
- cleaned_data = super().clean()
-
- if not self.instance.pk:
- if Tournament.objects.filter(name=cleaned_data["data"], year=os.getenv("TFJM_YEAR")):
- self.add_error("name", _("This tournament already exists."))
- if cleaned_data["final"] and Tournament.objects.filter(final=True, year=os.getenv("TFJM_YEAR")):
- self.add_error("name", _("The final tournament was already defined."))
-
- return cleaned_data
-
- class Meta:
- model = Tournament
- exclude = ('year',)
- widgets = {
- "price": AmountInput(),
- "date_start": DatePickerInput(),
- "date_end": DatePickerInput(),
- "date_inscription": DateTimePickerInput(),
- "date_solutions": DateTimePickerInput(),
- "date_syntheses": DateTimePickerInput(),
- "date_solutions_2": DateTimePickerInput(),
- "date_syntheses_2": DateTimePickerInput(),
- }
-
-
-class OrganizerForm(forms.ModelForm):
- """
- Register an organizer in the website.
- """
-
- class Meta:
- model = TFJMUser
- fields = ('last_name', 'first_name', 'email', 'is_superuser',)
-
- def clean(self):
- cleaned_data = super().clean()
-
- if TFJMUser.objects.filter(email=cleaned_data["email"], year=os.getenv("TFJM_YEAR")).exists():
- self.add_error("email", _("This organizer already exist."))
-
- return cleaned_data
-
- def save(self, commit=True):
- user = self.instance
- user.role = '0admin' if user.is_superuser else '1volunteer'
- user.save()
- super().save(commit)
-
-
-class TeamForm(forms.ModelForm):
- """
- Add and update a team.
- """
- tournament = forms.ModelChoiceField(
- Tournament.objects.filter(date_inscription__gte=timezone.now(), final=False),
- )
-
- class Meta:
- model = Team
- fields = ('name', 'trigram', 'tournament',)
-
- def clean(self):
- cleaned_data = super().clean()
-
- cleaned_data["trigram"] = cleaned_data["trigram"].upper()
-
- if not re.match("[A-Z]{3}", cleaned_data["trigram"]):
- self.add_error("trigram", _("The trigram must be composed of three upcase letters."))
-
- if not self.instance.pk:
- if Team.objects.filter(trigram=cleaned_data["trigram"], year=os.getenv("TFJM_YEAR")).exists():
- self.add_error("trigram", _("This trigram is already used."))
-
- if Team.objects.filter(name=cleaned_data["name"], year=os.getenv("TFJM_YEAR")).exists():
- self.add_error("name", _("This name is already used."))
-
- if cleaned_data["tournament"].date_inscription < timezone.now:
- self.add_error("tournament", _("This tournament is already closed."))
-
- return cleaned_data
-
-
-class JoinTeam(forms.Form):
- """
- Form to join a team with an access code.
- """
-
- access_code = forms.CharField(
- label=_("Access code"),
- max_length=6,
- )
-
- def clean(self):
- cleaned_data = super().clean()
-
- if not re.match("[a-z0-9]{6}", cleaned_data["access_code"]):
- self.add_error('access_code', _("The access code must be composed of 6 alphanumeric characters."))
-
- team = Team.objects.filter(access_code=cleaned_data["access_code"])
- if not team.exists():
- self.add_error('access_code', _("This access code is invalid."))
- team = team.get()
- if not team.invalid:
- self.add_error('access_code', _("The team is already validated."))
- cleaned_data["team"] = team
-
- return cleaned_data
-
-
-class SolutionForm(forms.ModelForm):
- """
- Form to upload a solution.
- """
-
- problem = forms.ChoiceField(
- label=_("Problem"),
- choices=[(str(i), _("Problem #%(problem)d") % {"problem": i}) for i in range(1, 9)],
- )
-
- def clean_file(self):
- content = self.cleaned_data['file']
- content_type = content.content_type
- if content_type in ["application/pdf"]:
- if content.size > 5 * 2 ** 20:
- raise forms.ValidationError(
- _('Please keep filesize under %(max_size)s. Current filesize %(current_size)s') % {
- "max_size": filesizeformat(2 * 2 ** 20),
- "current_size": filesizeformat(content.size)
- })
- else:
- raise forms.ValidationError(_('The file should be a PDF file.'))
- return content
-
- class Meta:
- model = Solution
- fields = ('file', 'problem',)
-
-
-class SynthesisForm(forms.ModelForm):
- """
- Form to upload a synthesis.
- """
-
- def clean_file(self):
- content = self.cleaned_data['file']
- content_type = content.content_type
- if content_type in ["application/pdf"]:
- if content.size > 5 * 2 ** 20:
- raise forms.ValidationError(
- _('Please keep filesize under %(max_size)s. Current filesize %(current_size)s') % {
- "max_size": filesizeformat(2 * 2 ** 20),
- "current_size": filesizeformat(content.size)
- })
- else:
- raise forms.ValidationError(_('The file should be a PDF file.'))
- return content
-
- class Meta:
- model = Synthesis
- fields = ('file', 'source', 'round',)
-
-
-class PoolForm(forms.ModelForm):
- """
- Form to add a pool.
- Should not be used: prefer to pass by API and auto-add pools with the results of the draw.
- """
-
- team1 = forms.ModelChoiceField(
- Team.objects.filter(validation_status="2valid").all(),
- empty_label=_("Choose a team..."),
- label=_("Team 1"),
- )
-
- problem1 = forms.IntegerField(
- min_value=1,
- max_value=8,
- initial=1,
- label=_("Problem defended by team 1"),
- )
-
- team2 = forms.ModelChoiceField(
- Team.objects.filter(validation_status="2valid").all(),
- empty_label=_("Choose a team..."),
- label=_("Team 2"),
- )
-
- problem2 = forms.IntegerField(
- min_value=1,
- max_value=8,
- initial=2,
- label=_("Problem defended by team 2"),
- )
-
- team3 = forms.ModelChoiceField(
- Team.objects.filter(validation_status="2valid").all(),
- empty_label=_("Choose a team..."),
- label=_("Team 3"),
- )
-
- problem3 = forms.IntegerField(
- min_value=1,
- max_value=8,
- initial=3,
- label=_("Problem defended by team 3"),
- )
-
- def clean(self):
- cleaned_data = super().clean()
-
- team1, pb1 = cleaned_data["team1"], cleaned_data["problem1"]
- team2, pb2 = cleaned_data["team2"], cleaned_data["problem2"]
- team3, pb3 = cleaned_data["team3"], cleaned_data["problem3"]
-
- sol1 = Solution.objects.get(team=team1, problem=pb1, final=team1.selected_for_final)
- sol2 = Solution.objects.get(team=team2, problem=pb2, final=team2.selected_for_final)
- sol3 = Solution.objects.get(team=team3, problem=pb3, final=team3.selected_for_final)
-
- cleaned_data["teams"] = [team1, team2, team3]
- cleaned_data["solutions"] = [sol1, sol2, sol3]
-
- return cleaned_data
-
- def save(self, commit=True):
- pool = super().save(commit)
-
- pool.refresh_from_db()
- pool.teams.set(self.cleaned_data["teams"])
- pool.solutions.set(self.cleaned_data["solutions"])
- pool.save()
-
- return pool
-
- class Meta:
- model = Pool
- fields = ('round', 'juries',)
diff --git a/apps/tournament/migrations/0001_initial.py b/apps/tournament/migrations/0001_initial.py
deleted file mode 100644
index 22ee135..0000000
--- a/apps/tournament/migrations/0001_initial.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# Generated by Django 3.1 on 2020-09-19 18:15
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import django.utils.timezone
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ('member', '0001_initial'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Tournament',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=255, verbose_name='name')),
- ('size', models.PositiveSmallIntegerField(help_text='Number of teams that are allowed to join the tournament.', verbose_name='size')),
- ('place', models.CharField(max_length=255, verbose_name='place')),
- ('price', models.PositiveSmallIntegerField(help_text='Price asked to participants. Free with a scholarship.', verbose_name='price')),
- ('description', models.TextField(verbose_name='description')),
- ('date_start', models.DateField(default=django.utils.timezone.now, verbose_name='date start')),
- ('date_end', models.DateField(default=django.utils.timezone.now, verbose_name='date end')),
- ('date_inscription', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date of registration closing')),
- ('date_solutions', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date of maximal solution submission')),
- ('date_syntheses', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date of maximal syntheses submission for the first round')),
- ('date_solutions_2', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date when solutions of round 2 are available')),
- ('date_syntheses_2', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date of maximal syntheses submission for the second round')),
- ('final', models.BooleanField(help_text='It should be only one final tournament.', verbose_name='final tournament')),
- ('year', models.PositiveIntegerField(default=2020, verbose_name='year')),
- ('organizers', models.ManyToManyField(help_text='List of all organizers that can see and manipulate data of the tournament and the teams.', related_name='organized_tournaments', to=settings.AUTH_USER_MODEL, verbose_name='organizers')),
- ],
- options={
- 'verbose_name': 'tournament',
- 'verbose_name_plural': 'tournaments',
- },
- ),
- migrations.CreateModel(
- name='Team',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=255, verbose_name='name')),
- ('trigram', models.CharField(help_text='The trigram should be composed of 3 capitalize letters, that is a funny acronym for the team.', max_length=3, verbose_name='trigram')),
- ('inscription_date', models.DateTimeField(auto_now_add=True, verbose_name='inscription date')),
- ('validation_status', models.CharField(choices=[('0invalid', 'Registration not validated'), ('1waiting', 'Waiting for validation'), ('2valid', 'Registration validated')], max_length=8, verbose_name='validation status')),
- ('selected_for_final', models.BooleanField(default=False, verbose_name='selected for final')),
- ('access_code', models.CharField(max_length=6, unique=True, verbose_name='access code')),
- ('year', models.PositiveIntegerField(default=2020, verbose_name='year')),
- ('tournament', models.ForeignKey(help_text='The tournament where the team is registered.', on_delete=django.db.models.deletion.PROTECT, related_name='_teams', to='tournament.tournament', verbose_name='tournament')),
- ],
- options={
- 'verbose_name': 'team',
- 'verbose_name_plural': 'teams',
- 'unique_together': {('name', 'year'), ('trigram', 'year')},
- },
- ),
- migrations.CreateModel(
- name='Pool',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('round', models.PositiveIntegerField(choices=[(1, 'Round 1'), (2, 'Round 2')], verbose_name='round')),
- ('extra_access_token', models.CharField(default='', help_text='Let other users access to the pool data without logging in.', max_length=64, verbose_name='extra access token')),
- ('juries', models.ManyToManyField(related_name='pools', to=settings.AUTH_USER_MODEL, verbose_name='juries')),
- ('solutions', models.ManyToManyField(related_name='pools', to='member.Solution', verbose_name='solutions')),
- ('teams', models.ManyToManyField(related_name='pools', to='tournament.Team', verbose_name='teams')),
- ],
- options={
- 'verbose_name': 'pool',
- 'verbose_name_plural': 'pools',
- },
- ),
- migrations.CreateModel(
- name='Payment',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('method', models.CharField(choices=[('not_paid', 'Not paid'), ('credit_card', 'Credit card'), ('check', 'Bank check'), ('transfer', 'Bank transfer'), ('cash', 'Cash'), ('scholarship', 'Scholarship')], default='not_paid', max_length=16, verbose_name='payment method')),
- ('validation_status', models.CharField(choices=[('0invalid', 'Registration not validated'), ('1waiting', 'Waiting for validation'), ('2valid', 'Registration validated')], max_length=8, verbose_name='validation status')),
- ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='tournament.team', verbose_name='team')),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment', to=settings.AUTH_USER_MODEL, verbose_name='user')),
- ],
- options={
- 'verbose_name': 'payment',
- 'verbose_name_plural': 'payments',
- },
- ),
- ]
diff --git a/apps/tournament/migrations/__init__.py b/apps/tournament/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/tournament/models.py b/apps/tournament/models.py
deleted file mode 100644
index 11d5886..0000000
--- a/apps/tournament/models.py
+++ /dev/null
@@ -1,432 +0,0 @@
-import os
-import random
-
-from django.core.mail import send_mail
-from django.db import models
-from django.template.loader import render_to_string
-from django.urls import reverse_lazy
-from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-
-
-class Tournament(models.Model):
- """
- Store the information of a tournament.
- """
-
- name = models.CharField(
- max_length=255,
- verbose_name=_("name"),
- )
-
- organizers = models.ManyToManyField(
- 'member.TFJMUser',
- related_name="organized_tournaments",
- verbose_name=_("organizers"),
- help_text=_("List of all organizers that can see and manipulate data of the tournament and the teams."),
- )
-
- size = models.PositiveSmallIntegerField(
- verbose_name=_("size"),
- help_text=_("Number of teams that are allowed to join the tournament."),
- )
-
- place = models.CharField(
- max_length=255,
- verbose_name=_("place"),
- )
-
- price = models.PositiveSmallIntegerField(
- verbose_name=_("price"),
- help_text=_("Price asked to participants. Free with a scholarship."),
- )
-
- description = models.TextField(
- verbose_name=_("description"),
- )
-
- date_start = models.DateField(
- default=timezone.now,
- verbose_name=_("date start"),
- )
-
- date_end = models.DateField(
- default=timezone.now,
- verbose_name=_("date end"),
- )
-
- date_inscription = models.DateTimeField(
- default=timezone.now,
- verbose_name=_("date of registration closing"),
- )
-
- date_solutions = models.DateTimeField(
- default=timezone.now,
- verbose_name=_("date of maximal solution submission"),
- )
-
- date_syntheses = models.DateTimeField(
- default=timezone.now,
- verbose_name=_("date of maximal syntheses submission for the first round"),
- )
-
- date_solutions_2 = models.DateTimeField(
- default=timezone.now,
- verbose_name=_("date when solutions of round 2 are available"),
- )
-
- date_syntheses_2 = models.DateTimeField(
- default=timezone.now,
- verbose_name=_("date of maximal syntheses submission for the second round"),
- )
-
- final = models.BooleanField(
- verbose_name=_("final tournament"),
- help_text=_("It should be only one final tournament."),
- )
-
- year = models.PositiveIntegerField(
- default=os.getenv("TFJM_YEAR", timezone.now().year),
- verbose_name=_("year"),
- )
-
- @property
- def teams(self):
- """
- Get all teams that are registered to this tournament, with a distinction for the final tournament.
- """
- return self._teams if not self.final else Team.objects.filter(selected_for_final=True)
-
- @property
- def linked_organizers(self):
- """
- Display a list of the organizers with links to their personal page.
- """
- return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + ''
- for user in self.organizers.all()]
-
- @property
- def solutions(self):
- """
- Get all sent solutions for this tournament.
- """
- from member.models import Solution
- return Solution.objects.filter(final=self.final) if self.final \
- else Solution.objects.filter(team__tournament=self, final=False)
-
- @property
- def syntheses(self):
- """
- Get all sent syntheses for this tournament.
- """
- from member.models import Synthesis
- return Synthesis.objects.filter(final=self.final) if self.final \
- else Synthesis.objects.filter(team__tournament=self, final=False)
-
- @classmethod
- def get_final(cls):
- """
- Get the final tournament.
- This should exist and be unique.
- """
- return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True)
-
- class Meta:
- verbose_name = _("tournament")
- verbose_name_plural = _("tournaments")
-
- def send_mail_to_organizers(self, template_name, subject="Contact TFJM²", **kwargs):
- """
- Send a mail to all organizers of the tournament.
- The template of the mail should be found either in templates/mail_templates/.html for the HTML
- version and in templates/mail_templates/.txt for the plain text version.
- The context of the template contains the tournament and the user. Extra context can be given through the kwargs.
- """
- context = kwargs
- context["tournament"] = self
- for user in self.organizers.all():
- context["user"] = user
- message = render_to_string("mail_templates/" + template_name + ".txt", context=context)
- message_html = render_to_string("mail_templates/" + template_name + ".html", context=context)
- send_mail(subject, message, "contact@tfjm.org", [user.email], html_message=message_html)
- from member.models import TFJMUser
- for user in TFJMUser.objects.get(is_superuser=True).all():
- context["user"] = user
- message = render_to_string("mail_templates/" + template_name + ".txt", context=context)
- message_html = render_to_string("mail_templates/" + template_name + ".html", context=context)
- send_mail(subject, message, "contact@tfjm.org", [user.email], html_message=message_html)
-
- def __str__(self):
- return self.name
-
-
-class Team(models.Model):
- """
- Store information about a registered team.
- """
-
- name = models.CharField(
- max_length=255,
- verbose_name=_("name"),
- )
-
- trigram = models.CharField(
- max_length=3,
- verbose_name=_("trigram"),
- help_text=_("The trigram should be composed of 3 capitalize letters, that is a funny acronym for the team."),
- )
-
- tournament = models.ForeignKey(
- Tournament,
- on_delete=models.PROTECT,
- related_name="_teams",
- verbose_name=_("tournament"),
- help_text=_("The tournament where the team is registered."),
- )
-
- inscription_date = models.DateTimeField(
- auto_now_add=True,
- verbose_name=_("inscription date"),
- )
-
- validation_status = models.CharField(
- max_length=8,
- choices=[
- ("0invalid", _("Registration not validated")),
- ("1waiting", _("Waiting for validation")),
- ("2valid", _("Registration validated")),
- ],
- verbose_name=_("validation status"),
- )
-
- selected_for_final = models.BooleanField(
- default=False,
- verbose_name=_("selected for final"),
- )
-
- access_code = models.CharField(
- max_length=6,
- unique=True,
- verbose_name=_("access code"),
- )
-
- year = models.PositiveIntegerField(
- default=os.getenv("TFJM_YEAR", timezone.now().year),
- verbose_name=_("year"),
- )
-
- @property
- def valid(self):
- return self.validation_status == "2valid"
-
- @property
- def waiting(self):
- return self.validation_status == "1waiting"
-
- @property
- def invalid(self):
- return self.validation_status == "0invalid"
-
- @property
- def coaches(self):
- """
- Get all coaches of a team.
- """
- return self.users.all().filter(role="2coach")
-
- @property
- def linked_coaches(self):
- """
- Get a list of the coaches of a team with html links to their pages.
- """
- return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + ''
- for user in self.coaches]
-
- @property
- def participants(self):
- """
- Get all particpants of a team, coaches excluded.
- """
- return self.users.all().filter(role="3participant")
-
- @property
- def linked_participants(self):
- """
- Get a list of the participants of a team with html links to their pages.
- """
- return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + ''
- for user in self.participants]
-
- @property
- def future_tournament(self):
- """
- Get the last tournament where the team is registered.
- Only matters if the team is selected for final: if this is the case, we return the final tournament.
- Useful for deadlines.
- """
- return Tournament.get_final() if self.selected_for_final else self.tournament
-
- @property
- def can_validate(self):
- """
- Check if a given team is able to ask for validation.
- A team can validate if:
- * All participants filled the photo consent
- * Minor participants filled the parental consent
- * Minor participants filled the sanitary plug
- * Teams sent their motivation letter
- * The team contains at least 4 participants
- * The team contains at least 1 coach
- """
- # TODO In a normal time, team needs a motivation letter and authorizations.
- return self.coaches.exists() and self.participants.count() >= 4\
- and self.tournament.date_inscription <= timezone.now()
-
- class Meta:
- verbose_name = _("team")
- verbose_name_plural = _("teams")
- unique_together = (('name', 'year',), ('trigram', 'year',),)
-
- def send_mail(self, template_name, subject="Contact TFJM²", **kwargs):
- """
- Send a mail to all members of a team with a given template.
- The template of the mail should be found either in templates/mail_templates/.html for the HTML
- version and in templates/mail_templates/.txt for the plain text version.
- The context of the template contains the team and the user. Extra context can be given through the kwargs.
- """
- context = kwargs
- context["team"] = self
- for user in self.users.all():
- context["user"] = user
- message = render_to_string("mail_templates/" + template_name + ".txt", context=context)
- message_html = render_to_string("mail_templates/" + template_name + ".html", context=context)
- send_mail(subject, message, "contact@tfjm.org", [user.email], html_message=message_html)
-
- def __str__(self):
- return self.trigram + " — " + self.name
-
-
-class Pool(models.Model):
- """
- Store information of a pool.
- A pool is only a list of accessible solutions to some teams and some juries.
- TODO: check that the set of teams is equal to the set of the teams that have a solution in this set.
- TODO: Moreover, a team should send only one solution.
- """
- teams = models.ManyToManyField(
- Team,
- related_name="pools",
- verbose_name=_("teams"),
- )
-
- solutions = models.ManyToManyField(
- "member.Solution",
- related_name="pools",
- verbose_name=_("solutions"),
- )
-
- round = models.PositiveIntegerField(
- choices=[
- (1, _("Round 1")),
- (2, _("Round 2")),
- ],
- verbose_name=_("round"),
- )
-
- juries = models.ManyToManyField(
- "member.TFJMUser",
- related_name="pools",
- verbose_name=_("juries"),
- )
-
- extra_access_token = models.CharField(
- max_length=64,
- default="",
- verbose_name=_("extra access token"),
- help_text=_("Let other users access to the pool data without logging in."),
- )
-
- @property
- def problems(self):
- """
- Get problem numbers of the sent solutions as a list of integers.
- """
- return list(d["problem"] for d in self.solutions.values("problem").all())
-
- @property
- def tournament(self):
- """
- Get the concerned tournament.
- We assume that the pool is correct, so all solutions belong to the same tournament.
- """
- return self.solutions.first().tournament
-
- @property
- def syntheses(self):
- """
- Get the syntheses of the teams that are in this pool, for the correct round.
- """
- from member.models import Synthesis
- return Synthesis.objects.filter(team__in=self.teams.all(), round=self.round, final=self.tournament.final)
-
- def save(self, **kwargs):
- if not self.extra_access_token:
- alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
- code = "".join(random.choice(alphabet) for _ in range(64))
- self.extra_access_token = code
- super().save(**kwargs)
-
- class Meta:
- verbose_name = _("pool")
- verbose_name_plural = _("pools")
-
-
-class Payment(models.Model):
- """
- Store some information about payments, to recover data.
- TODO: handle it...
- """
- user = models.OneToOneField(
- 'member.TFJMUser',
- on_delete=models.CASCADE,
- related_name="payment",
- verbose_name=_("user"),
- )
-
- team = models.ForeignKey(
- Team,
- on_delete=models.CASCADE,
- related_name="payments",
- verbose_name=_("team"),
- )
-
- method = models.CharField(
- max_length=16,
- choices=[
- ("not_paid", _("Not paid")),
- ("credit_card", _("Credit card")),
- ("check", _("Bank check")),
- ("transfer", _("Bank transfer")),
- ("cash", _("Cash")),
- ("scholarship", _("Scholarship")),
- ],
- default="not_paid",
- verbose_name=_("payment method"),
- )
-
- validation_status = models.CharField(
- max_length=8,
- choices=[
- ("0invalid", _("Registration not validated")),
- ("1waiting", _("Waiting for validation")),
- ("2valid", _("Registration validated")),
- ],
- verbose_name=_("validation status"),
- )
-
- class Meta:
- verbose_name = _("payment")
- verbose_name_plural = _("payments")
-
- def __str__(self):
- return _("Payment of {user}").format(str(self.user))
diff --git a/apps/tournament/tables.py b/apps/tournament/tables.py
deleted file mode 100644
index 33e39a1..0000000
--- a/apps/tournament/tables.py
+++ /dev/null
@@ -1,164 +0,0 @@
-import django_tables2 as tables
-from django.urls import reverse_lazy
-from django.utils.html import format_html
-from django.utils.translation import gettext_lazy as _
-from django_tables2 import A
-
-from member.models import Solution, Synthesis
-from .models import Tournament, Team, Pool
-
-
-class TournamentTable(tables.Table):
- """
- List all tournaments.
- """
-
- name = tables.LinkColumn(
- "tournament:detail",
- args=[A("pk")],
- )
-
- date_start = tables.Column(
- verbose_name=_("dates").capitalize(),
- )
-
- def render_date_start(self, record):
- return _("From {start:%b %d %Y} to {end:%b %d %Y}").format(start=record.date_start, end=record.date_end)
-
- class Meta:
- model = Tournament
- fields = ("name", "date_start", "date_inscription", "date_solutions", "size", )
- attrs = {
- 'class': 'table table-condensed table-striped table-hover'
- }
- order_by = ('date_start', 'name',)
-
-
-class TeamTable(tables.Table):
- """
- Table of some teams. Can be filtered with a queryset (for example, teams of a tournament)
- """
-
- name = tables.LinkColumn(
- "tournament:team_detail",
- args=[A("pk")],
- )
-
- class Meta:
- model = Team
- fields = ("name", "trigram", "validation_status", )
- attrs = {
- 'class': 'table table-condensed table-striped table-hover'
- }
- order_by = ('-validation_status', 'trigram',)
-
-
-class SolutionTable(tables.Table):
- """
- Display a table of some solutions.
- """
-
- team = tables.LinkColumn(
- "tournament:team_detail",
- args=[A("team.pk")],
- )
-
- tournament = tables.LinkColumn(
- "tournament:detail",
- args=[A("tournament.pk")],
- accessor=A("tournament"),
- order_by=("team__tournament__date_start", "team__tournament__name",),
- verbose_name=_("Tournament"),
- )
-
- file = tables.LinkColumn(
- "document",
- args=[A("file")],
- attrs={
- "a": {
- "data-turbolinks": "false",
- }
- }
- )
-
- def render_file(self):
- return _("Download")
-
- class Meta:
- model = Solution
- fields = ("team", "tournament", "problem", "uploaded_at", "file", )
- attrs = {
- 'class': 'table table-condensed table-striped table-hover'
- }
-
-
-class SynthesisTable(tables.Table):
- """
- Display a table of some syntheses.
- """
-
- team = tables.LinkColumn(
- "tournament:team_detail",
- args=[A("team.pk")],
- )
-
- tournament = tables.LinkColumn(
- "tournament:detail",
- args=[A("tournament.pk")],
- accessor=A("tournament"),
- order_by=("team__tournament__date_start", "team__tournament__name",),
- verbose_name=_("tournament"),
- )
-
- file = tables.LinkColumn(
- "document",
- args=[A("file")],
- attrs={
- "a": {
- "data-turbolinks": "false",
- }
- }
- )
-
- def render_file(self):
- return _("Download")
-
- class Meta:
- model = Synthesis
- fields = ("team", "tournament", "round", "source", "uploaded_at", "file", )
- attrs = {
- 'class': 'table table-condensed table-striped table-hover'
- }
-
-
-class PoolTable(tables.Table):
- """
- Display a table of some pools.
- """
-
- problems = tables.Column(
- verbose_name=_("Problems"),
- orderable=False,
- )
-
- tournament = tables.LinkColumn(
- "tournament:detail",
- args=[A("tournament.pk")],
- verbose_name=_("Tournament"),
- order_by=("teams__tournament__date_start", "teams__tournament__name",),
- )
-
- def render_teams(self, record, value):
- return format_html('{trigrams}',
- url=reverse_lazy('tournament:pool_detail', args=(record.pk,)),
- trigrams=", ".join(team.trigram for team in value.all()))
-
- def render_problems(self, value):
- return ", ".join([str(pb) for pb in value])
-
- class Meta:
- model = Pool
- fields = ("teams", "tournament", "problems", "round", )
- attrs = {
- 'class': 'table table-condensed table-striped table-hover'
- }
diff --git a/apps/tournament/urls.py b/apps/tournament/urls.py
deleted file mode 100644
index 364f23d..0000000
--- a/apps/tournament/urls.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.urls import path
-
-from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
- TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView, \
- SynthesesOrgaListView, PoolListView, PoolCreateView, PoolDetailView
-
-app_name = "tournament"
-
-urlpatterns = [
- path('list/', TournamentListView.as_view(), name="list"),
- path("add/", TournamentCreateView.as_view(), name="add"),
- path('/', TournamentDetailView.as_view(), name="detail"),
- path('/update/', TournamentUpdateView.as_view(), name="update"),
- path('team//', TeamDetailView.as_view(), name="team_detail"),
- path('team//update/', TeamUpdateView.as_view(), name="team_update"),
- path("add-organizer/", AddOrganizerView.as_view(), name="add_organizer"),
- path("solutions/", SolutionsView.as_view(), name="solutions"),
- path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
- path("syntheses/", SynthesesView.as_view(), name="syntheses"),
- path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
- path("pools/", PoolListView.as_view(), name="pools"),
- path("pool/add/", PoolCreateView.as_view(), name="create_pool"),
- path("pool//", PoolDetailView.as_view(), name="pool_detail"),
-]
diff --git a/apps/tournament/views.py b/apps/tournament/views.py
deleted file mode 100644
index 450351f..0000000
--- a/apps/tournament/views.py
+++ /dev/null
@@ -1,662 +0,0 @@
-import random
-import zipfile
-from datetime import timedelta
-from io import BytesIO
-
-from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
-from django.core.exceptions import PermissionDenied
-from django.core.mail import send_mail
-from django.db.models import Q
-from django.http import HttpResponse
-from django.shortcuts import redirect
-from django.template.loader import render_to_string
-from django.urls import reverse_lazy
-from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import DetailView, CreateView, UpdateView
-from django.views.generic.edit import BaseFormView
-from django_tables2.views import SingleTableView
-from member.models import TFJMUser, Solution, Synthesis
-
-from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm, PoolForm
-from .models import Tournament, Team, Pool
-from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, PoolTable
-
-
-class AdminMixin(LoginRequiredMixin):
- """
- If a view extends this mixin, then the view will be only accessible to administrators.
- """
-
- def dispatch(self, request, *args, **kwargs):
- if not request.user.is_authenticated or not request.user.admin:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
-
-class OrgaMixin(AccessMixin):
- """
- If a view extends this mixin, then the view will be only accessible to administrators or organizers.
- """
-
- def dispatch(self, request, *args, **kwargs):
- if not request.user.is_authenticated and not request.session["extra_access_token"]:
- return self.handle_no_permission()
- elif request.user.is_authenticated and not request.user.organizes:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
-
-class TeamMixin(LoginRequiredMixin):
- """
- If a view extends this mixin, then the view will be only accessible to users that are registered in a team.
- """
-
- def dispatch(self, request, *args, **kwargs):
- if not request.user.is_authenticated or not request.user.team:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
-
-class TournamentListView(SingleTableView):
- """
- Display the list of all tournaments, ordered by start date then name.
- """
-
- model = Tournament
- table_class = TournamentTable
- extra_context = dict(title=_("Tournaments list"),)
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- team_users = TFJMUser.objects.filter(Q(team__isnull=False) | Q(role="admin") | Q(role="organizer"))\
- .order_by('-role')
- valid_team_users = team_users.filter(
- Q(team__validation_status="2valid") | Q(role="admin") | Q(role="organizer"))
-
- context["team_users_emails"] = [user.email for user in team_users]
- context["valid_team_users_emails"] = [user.email for user in valid_team_users]
-
- return context
-
-
-class TournamentCreateView(AdminMixin, CreateView):
- """
- Create a tournament. Only accessible to admins.
- """
-
- model = Tournament
- form_class = TournamentForm
- extra_context = dict(title=_("Add tournament"),)
-
- def get_success_url(self):
- return reverse_lazy('tournament:detail', args=(self.object.pk,))
-
-
-class TournamentDetailView(DetailView):
- """
- Display the detail of a tournament.
- Accessible to all, including not authenticated users.
- """
-
- model = Tournament
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- context["title"] = _("Tournament of {name}").format(name=self.object.name)
-
- if self.object.final:
- team_users = TFJMUser.objects.filter(team__selected_for_final=True)
- valid_team_users = team_users
- else:
- team_users = TFJMUser.objects.filter(
- Q(team__tournament=self.object)
- | Q(organized_tournaments=self.object)).order_by('role')
- valid_team_users = team_users.filter(
- Q(team__validation_status="2valid")
- | Q(role="admin")
- | Q(organized_tournaments=self.object))
-
- context["team_users_emails"] = [user.email for user in team_users]
- context["valid_team_users_emails"] = [user.email for user in valid_team_users]
-
- context["teams"] = TeamTable(self.object.teams.all())
-
- return context
-
-
-class TournamentUpdateView(OrgaMixin, UpdateView):
- """
- Update the data of a tournament.
- Reserved to admins and organizers of the tournament.
- """
-
- def dispatch(self, request, *args, **kwargs):
- """
- Restrict the view to organizers of tournaments, then process the request.
- """
- if self.request.user.role == "1volunteer" and self.request.user not in self.get_object().organizers.all():
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
- model = Tournament
- form_class = TournamentForm
- extra_context = dict(title=_("Update tournament"),)
-
- def get_success_url(self):
- return reverse_lazy('tournament:detail', args=(self.object.pk,))
-
-
-class TeamDetailView(LoginRequiredMixin, DetailView):
- """
- View the detail of a team.
- Restricted to this team, admins and organizers of its tournament.
- """
- model = Team
-
- def dispatch(self, request, *args, **kwargs):
- """
- Protect the page and process the request.
- """
- if not request.user.is_authenticated or \
- (not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()
- and not (self.get_object().selected_for_final
- and request.user in Tournament.get_final().organizers.all())
- and self.get_object() != request.user.team):
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
- def post(self, request, *args, **kwargs):
- """
- Process POST requests. Supported requests:
- - get the solutions of the team as a ZIP archive
- - a user leaves its team (if the composition is not validated yet)
- - the team requests the validation
- - Organizers can validate or invalidate the request
- - Admins can delete teams
- - Admins can select teams for the final tournament
- """
- team = self.get_object()
- if "zip" in request.POST:
- solutions = team.solutions.all()
-
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for solution in solutions:
- zf.write(solution.file.path, str(solution) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}'\
- .format(_("Solutions for team {team}.zip")
- .format(team=str(team)).replace(" ", "%20"))
- return resp
- elif "leave" in request.POST and request.user.participates:
- request.user.team = None
- request.user.save()
- if not team.users.exists():
- team.delete()
- return redirect('tournament:detail', pk=team.tournament.pk)
- elif "request_validation" in request.POST and request.user.participates and team.can_validate:
- team.validation_status = "1waiting"
- team.save()
- team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team)
- return redirect('tournament:team_detail', pk=team.pk)
- elif "validate" in request.POST and request.user.organizes:
- team.validation_status = "2valid"
- team.save()
- team.send_mail("validate_team", "Équipe validée TFJM²")
- return redirect('tournament:team_detail', pk=team.pk)
- elif "invalidate" in request.POST and request.user.organizes:
- team.validation_status = "0invalid"
- team.save()
- team.send_mail("unvalidate_team", "Équipe non validée TFJM²")
- return redirect('tournament:team_detail', pk=team.pk)
- elif "delete" in request.POST and request.user.organizes:
- team.delete()
- return redirect('tournament:detail', pk=team.tournament.pk)
- elif "select_final" in request.POST and request.user.admin and not team.selected_for_final and team.pools:
- # We copy all solutions for solutions for the final
- for solution in team.solutions.all():
- alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
- id = ""
- for i in range(64):
- id += random.choice(alphabet)
- with solution.file.open("rb") as source:
- with open("/code/media/" + id, "wb") as dest:
- for chunk in source.chunks():
- dest.write(chunk)
- new_sol = Solution(
- file=id,
- team=team,
- problem=solution.problem,
- final=True,
- )
- new_sol.save()
- team.selected_for_final = True
- team.save()
- team.send_mail("select_for_final", "Sélection pour la finale, félicitations ! - TFJM²",
- final=Tournament.get_final())
- return redirect('tournament:team_detail', pk=team.pk)
-
- return self.get(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- context["title"] = _("Information about team")
- context["ordered_solutions"] = self.object.solutions.order_by('final', 'problem',).all()
- context["team_users_emails"] = [user.email for user in self.object.users.all()]
-
- return context
-
-
-class TeamUpdateView(LoginRequiredMixin, UpdateView):
- """
- Update the information about a team.
- Team members, admins and organizers are allowed to do this.
- """
-
- model = Team
- form_class = TeamForm
- extra_context = dict(title=_("Update team"),)
-
- def dispatch(self, request, *args, **kwargs):
- if not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all() \
- and self.get_object() != self.request.user.team:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
-
-class AddOrganizerView(AdminMixin, CreateView):
- """
- Add a new organizer account. No password is created, the user should reset its password using the link
- sent by mail. Only name and email are requested.
- Only admins are granted to do this.
- """
-
- model = TFJMUser
- form_class = OrganizerForm
- extra_context = dict(title=_("Add organizer"),)
- template_name = "tournament/add_organizer.html"
-
- def form_valid(self, form):
- user = form.instance
- msg = render_to_string("mail_templates/add_organizer.txt", context=dict(user=user))
- msg_html = render_to_string("mail_templates/add_organizer.html", context=dict(user=user))
- send_mail('Organisateur du TFJM² 2020', msg, 'contact@tfjm.org', [user.email], html_message=msg_html)
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse_lazy('index')
-
-
-class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
- """
- Upload and view solutions for a team.
- """
-
- model = Solution
- table_class = SolutionTable
- form_class = SolutionForm
- template_name = "tournament/solutions_list.html"
- extra_context = dict(title=_("Solutions"))
-
- def post(self, request, *args, **kwargs):
- if "zip" in request.POST:
- solutions = request.user.team.solutions
-
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for solution in solutions:
- zf.write(solution.file.path, str(solution) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}'\
- .format(_("Solutions for team {team}.zip")
- .format(team=str(request.user.team)).replace(" ", "%20"))
- return resp
-
- return super().post(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- self.object_list = self.get_queryset()
- context = super().get_context_data(**kwargs)
- context["now"] = timezone.now()
- context["real_deadline"] = self.request.user.team.future_tournament.date_solutions + timedelta(minutes=30)
- return context
-
- def get_queryset(self):
- qs = super().get_queryset().filter(team=self.request.user.team)
- return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
- 'problem',)
-
- def form_valid(self, form):
- solution = form.instance
- solution.team = self.request.user.team
- solution.final = solution.team.selected_for_final
-
- if timezone.now() > solution.tournament.date_solutions + timedelta(minutes=30):
- form.add_error('file', _("You can't publish your solution anymore. Deadline: {date:%m-%d-%Y %H:%M}.")
- .format(date=timezone.localtime(solution.tournament.date_solutions)))
- return super().form_invalid(form)
-
- prev_sol = Solution.objects.filter(problem=solution.problem, team=solution.team, final=solution.final)
- for sol in prev_sol.all():
- sol.delete()
- alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
- id = ""
- for i in range(64):
- id += random.choice(alphabet)
- solution.file.name = id
- solution.save()
-
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse_lazy("tournament:solutions")
-
-
-class SolutionsOrgaListView(OrgaMixin, SingleTableView):
- """
- View all solutions sent by teams for the organized tournaments. Juries can view solutions of their pools.
- Organizers can download a ZIP archive for each organized tournament.
- """
-
- model = Solution
- table_class = SolutionTable
- template_name = "tournament/solutions_orga_list.html"
- extra_context = dict(title=_("All solutions"))
-
- def post(self, request, *args, **kwargs):
- if "tournament_zip" in request.POST:
- tournament = Tournament.objects.get(pk=int(request.POST["tournament_zip"]))
- solutions = tournament.solutions
- if not request.user.admin and request.user not in tournament.organizers.all():
- raise PermissionDenied
-
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for solution in solutions:
- zf.write(solution.file.path, str(solution) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}'\
- .format(_("Solutions for tournament {tournament}.zip")
- .format(tournament=str(tournament)).replace(" ", "%20"))
- return resp
-
- return self.get(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- if self.request.user.is_authenticated:
- context["tournaments"] = \
- Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments
-
- return context
-
- def get_queryset(self):
- qs = super().get_queryset()
- if self.request.user.is_authenticated and not self.request.user.admin:
- if self.request.user in Tournament.get_final().organizers.all():
- qs = qs.filter(Q(team__tournament__organizers=self.request.user) | Q(pools__juries=self.request.user)
- | Q(final=True))
- else:
- qs = qs.filter(Q(team__tournament__organizers=self.request.user) | Q(pools__juries=self.request.user))
- elif not self.request.user.is_authenticated:
- qs = qs.filter(pools__extra_access_token=self.request.session["extra_access_token"])
- return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
- 'problem',).distinct()
-
-
-class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
- """
- Upload and view syntheses for a team.
- """
- model = Synthesis
- table_class = SynthesisTable
- form_class = SynthesisForm
- template_name = "tournament/syntheses_list.html"
- extra_context = dict(title=_("Syntheses"))
-
- def post(self, request, *args, **kwargs):
- if "zip" in request.POST:
- syntheses = request.user.team.syntheses
-
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for synthesis in syntheses:
- zf.write(synthesis.file.path, str(synthesis) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}'\
- .format(_("Syntheses for team {team}.zip")
- .format(team=str(request.user.team)).replace(" ", "%20"))
- return resp
-
- return super().post(request, *args, **kwargs)
-
- def get_queryset(self):
- qs = super().get_queryset().filter(team=self.request.user.team)
- return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
- 'round', 'source',)
-
- def get_context_data(self, **kwargs):
- self.object_list = self.get_queryset()
- context = super().get_context_data(**kwargs)
- context["now"] = timezone.now()
- context["real_deadline_1"] = self.request.user.team.future_tournament.date_syntheses + timedelta(minutes=30)
- context["real_deadline_2"] = self.request.user.team.future_tournament.date_syntheses_2 + timedelta(minutes=30)
- return context
-
- def form_valid(self, form):
- synthesis = form.instance
- synthesis.team = self.request.user.team
- synthesis.final = synthesis.team.selected_for_final
-
- if synthesis.round == '1' and timezone.now() > (synthesis.tournament.date_syntheses + timedelta(minutes=30)):
- form.add_error('file', _("You can't publish your synthesis anymore for the first round."
- " Deadline: {date:%m-%d-%Y %H:%M}.")
- .format(date=timezone.localtime(synthesis.tournament.date_syntheses)))
- return super().form_invalid(form)
-
- if synthesis.round == '2' and timezone.now() > synthesis.tournament.date_syntheses_2 + timedelta(minutes=30):
- form.add_error('file', _("You can't publish your synthesis anymore for the second round."
- " Deadline: {date:%m-%d-%Y %H:%M}.")
- .format(date=timezone.localtime(synthesis.tournament.date_syntheses_2)))
- return super().form_invalid(form)
-
- prev_syn = Synthesis.objects.filter(team=synthesis.team, round=synthesis.round, source=synthesis.source,
- final=synthesis.final)
- for syn in prev_syn.all():
- syn.delete()
- alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
- id = ""
- for i in range(64):
- id += random.choice(alphabet)
- synthesis.file.name = id
- synthesis.save()
-
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse_lazy("tournament:syntheses")
-
-
-class SynthesesOrgaListView(OrgaMixin, SingleTableView):
- """
- View all syntheses sent by teams for the organized tournaments. Juries can view syntheses of their pools.
- Organizers can download a ZIP archive for each organized tournament.
- """
- model = Synthesis
- table_class = SynthesisTable
- template_name = "tournament/syntheses_orga_list.html"
- extra_context = dict(title=_("All syntheses"))
-
- def post(self, request, *args, **kwargs):
- if "tournament_zip" in request.POST:
- tournament = Tournament.objects.get(pk=request.POST["tournament_zip"])
- syntheses = tournament.syntheses
- if not request.user.admin and request.user not in tournament.organizers.all():
- raise PermissionDenied
-
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for synthesis in syntheses:
- zf.write(synthesis.file.path, str(synthesis) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}'\
- .format(_("Syntheses for tournament {tournament}.zip")
- .format(tournament=str(tournament)).replace(" ", "%20"))
- return resp
-
- return self.get(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- if self.request.user.is_authenticated:
- context["tournaments"] = \
- Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments
-
- return context
-
- def get_queryset(self):
- qs = super().get_queryset()
- if self.request.user.is_authenticated and not self.request.user.admin:
- if self.request.user in Tournament.get_final().organizers.all():
- qs = qs.filter(Q(team__tournament__organizers=self.request.user)
- | Q(team__pools__juries=self.request.user)
- | Q(final=True))
- else:
- qs = qs.filter(Q(team__tournament__organizers=self.request.user)
- | Q(team__pools__juries=self.request.user))
- elif not self.request.user.is_authenticated:
- pool = Pool.objects.filter(extra_access_token=self.request.session["extra_access_token"])
- if pool.exists():
- pool = pool.get()
- qs = qs.filter(team__pools=pool, final=pool.tournament.final)
- else:
- qs = qs.none()
- return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
- 'round', 'source',).distinct()
-
-
-class PoolListView(SingleTableView):
- """
- View the list of visible pools.
- Admins see all, juries see their own pools, organizers see the pools of their tournaments.
- """
- model = Pool
- table_class = PoolTable
- extra_context = dict(title=_("Pools"))
-
- def get_queryset(self):
- qs = super().get_queryset()
- user = self.request.user
- if user.is_authenticated:
- if not user.admin and user.organizes:
- qs = qs.filter(Q(juries=user) | Q(teams__tournament__organizers=user))
- elif user.participates:
- qs = qs.filter(teams=user.team)
- else:
- qs = qs.filter(extra_access_token=self.request.session["extra_access_token"])
- qs = qs.distinct().order_by('id')
- return qs
-
-
-class PoolCreateView(AdminMixin, CreateView):
- """
- Create a pool manually.
- This page should not be used: prefer send automatically data from the drawing bot.
- """
- model = Pool
- form_class = PoolForm
- extra_context = dict(title=_("Create pool"))
-
- def get_success_url(self):
- return reverse_lazy("tournament:pools")
-
-
-class PoolDetailView(DetailView):
- """
- See the detail of a pool.
- Teams and juries can download here defended solutions of the pool.
- If this is the second round, teams can't download solutions of the other teams before the date when they
- should be available.
- Juries see also syntheses. They see of course solutions immediately.
- This is also true for organizers and admins.
- All can be downloaded as a ZIP archive.
- """
- model = Pool
- extra_context = dict(title=_("Pool detail"))
-
- def get_queryset(self):
- qs = super().get_queryset()
- user = self.request.user
- if user.is_authenticated:
- if not user.admin and user.organizes:
- qs = qs.filter(Q(juries=user) | Q(teams__tournament__organizers=user))
- elif user.participates:
- qs = qs.filter(teams=user.team)
- else:
- qs = qs.filter(extra_access_token=self.request.session["extra_access_token"])
- return qs.distinct()
-
- def post(self, request, *args, **kwargs):
- user = request.user
- pool = self.get_object()
-
- if "solutions_zip" in request.POST:
- if user.is_authenticated and user.participates and pool.round == 2\
- and pool.tournament.date_solutions_2 > timezone.now():
- raise PermissionDenied
-
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for solution in pool.solutions.all():
- zf.write(solution.file.path, str(solution) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}' \
- .format(_("Solutions of a pool for the round {round} of the tournament {tournament}.zip")
- .format(round=pool.round, tournament=str(pool.tournament)).replace(" ", "%20"))
- return resp
- elif "syntheses_zip" in request.POST and (not user.is_authenticated or user.organizes):
- out = BytesIO()
- zf = zipfile.ZipFile(out, "w")
-
- for synthesis in pool.syntheses.all():
- zf.write(synthesis.file.path, str(synthesis) + ".pdf")
-
- zf.close()
-
- resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
- resp['Content-Disposition'] = 'attachment; filename={}' \
- .format(_("Syntheses of a pool for the round {round} of the tournament {tournament}.zip")
- .format(round=pool.round, tournament=str(pool.tournament)).replace(" ", "%20"))
- return resp
-
- return self.get(request, *args, **kwargs)
diff --git a/entrypoint.sh b/entrypoint.sh
deleted file mode 100755
index ffb586e..0000000
--- a/entrypoint.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-python manage.py compilemessages
-python manage.py migrate
-
-nginx
-
-if [ "$TFJM_STAGE" = "prod" ]; then
- gunicorn -b 0.0.0.0:8000 --workers=2 --threads=4 --worker-class=gthread tfjm.wsgi --access-logfile '-' --error-logfile '-';
-else
- ./manage.py runserver 0.0.0.0:8000;
-fi
diff --git a/index.html b/index.html
deleted file mode 100644
index da8c197..0000000
--- a/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Erreur
-
-
-
-Le mode Rewrite n'est pas activé.
-
-
\ No newline at end of file
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
deleted file mode 100644
index 7c2977f..0000000
--- a/locale/fr/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,1221 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR yohann.danello@animath.fr, 2020.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: TFJM2\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-05-25 18:23+0200\n"
-"PO-Revision-Date: 2020-04-29 02:30+0000\n"
-"Last-Translator: Yohann D'ANELLO \n"
-"Language-Team: fr \n"
-"Language: fr\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: apps/api/apps.py:10
-msgid "API"
-msgstr "API"
-
-#: apps/member/apps.py:10
-msgid "member"
-msgstr "membre"
-
-#: apps/member/forms.py:18
-msgid "Choose a role..."
-msgstr "Choisir un rôle ..."
-
-#: apps/member/forms.py:19 apps/member/models.py:138
-msgid "Participant"
-msgstr "Participant"
-
-#: apps/member/forms.py:20 apps/member/models.py:137
-msgid "Coach"
-msgstr "Encadrant"
-
-#: apps/member/models.py:21 templates/member/tfjmuser_detail.html:35
-msgid "email"
-msgstr "Adresse électronique"
-
-#: apps/member/models.py:22
-msgid "This should be valid and will be controlled."
-msgstr "Elle doit être valide et sera contrôlée."
-
-#: apps/member/models.py:30 apps/member/models.py:244 apps/member/models.py:263
-#: apps/member/models.py:306 apps/tournament/models.py:286
-#: apps/tournament/models.py:400 templates/member/tfjmuser_detail.html:16
-msgid "team"
-msgstr "équipe"
-
-#: apps/member/models.py:31
-msgid "Concerns only coaches and participants."
-msgstr "Concerne uniquement les encadrants et participants."
-
-#: apps/member/models.py:37 templates/member/tfjmuser_detail.html:21
-msgid "birth date"
-msgstr "date de naissance"
-
-#: apps/member/models.py:45
-msgid "Male"
-msgstr "Homme"
-
-#: apps/member/models.py:46
-msgid "Female"
-msgstr "Femme"
-
-#: apps/member/models.py:47
-msgid "Non binary"
-msgstr "Non binaire"
-
-#: apps/member/models.py:49 templates/member/tfjmuser_detail.html:26
-msgid "gender"
-msgstr "genre"
-
-#: apps/member/models.py:56 templates/member/tfjmuser_detail.html:31
-msgid "address"
-msgstr "adresse"
-
-#: apps/member/models.py:62
-msgid "postal code"
-msgstr "code postal"
-
-#: apps/member/models.py:69
-msgid "city"
-msgstr "ville"
-
-#: apps/member/models.py:76
-msgid "country"
-msgstr "pays"
-
-#: apps/member/models.py:84 templates/member/tfjmuser_detail.html:39
-msgid "phone number"
-msgstr "numéro de téléphone"
-
-#: apps/member/models.py:91 templates/member/tfjmuser_detail.html:44
-msgid "school"
-msgstr "école"
-
-#: apps/member/models.py:97
-msgid "Seconde or less"
-msgstr "Seconde ou inférieur"
-
-#: apps/member/models.py:98
-msgid "Première"
-msgstr "Première"
-
-#: apps/member/models.py:99
-msgid "Terminale"
-msgstr "Terminale"
-
-#: apps/member/models.py:110 templates/member/tfjmuser_detail.html:51
-msgid "responsible name"
-msgstr "nom du responsable"
-
-#: apps/member/models.py:117 templates/member/tfjmuser_detail.html:56
-msgid "responsible phone"
-msgstr "téléphone du responsable"
-
-#: apps/member/models.py:123 templates/member/tfjmuser_detail.html:61
-msgid "responsible email"
-msgstr "email du responsable"
-
-#: apps/member/models.py:129 apps/tournament/models.py:45
-#: templates/member/tfjmuser_detail.html:67
-#: templates/tournament/tournament_detail.html:42
-msgid "description"
-msgstr "description"
-
-#: apps/member/models.py:135
-msgid "Admin"
-msgstr "Administrateur"
-
-#: apps/member/models.py:136
-msgid "Organizer"
-msgstr "Organisateur"
-
-#: apps/member/models.py:144 apps/tournament/models.py:90
-#: apps/tournament/models.py:215
-msgid "year"
-msgstr "année"
-
-#: apps/member/models.py:171 apps/member/models.py:214
-#: apps/tournament/models.py:393
-msgid "user"
-msgstr "utilisateur"
-
-#: apps/member/models.py:172
-msgid "users"
-msgstr "utilisateurs"
-
-#: apps/member/models.py:189
-msgid "file"
-msgstr "fichier"
-
-#: apps/member/models.py:194
-msgid "uploaded at"
-msgstr "téléversé le"
-
-#: apps/member/models.py:198
-msgid "document"
-msgstr "document"
-
-#: apps/member/models.py:199
-msgid "documents"
-msgstr "documents"
-
-#: apps/member/models.py:220
-msgid "Parental consent"
-msgstr "Autorisation parentale"
-
-#: apps/member/models.py:221
-msgid "Photo consent"
-msgstr "Autorisation de droit à l'image"
-
-#: apps/member/models.py:222
-msgid "Sanitary plug"
-msgstr "Fiche sanitaire"
-
-#: apps/member/models.py:223 apps/tournament/models.py:411
-msgid "Scholarship"
-msgstr "Bourse"
-
-#: apps/member/models.py:225
-msgid "type"
-msgstr "type"
-
-#: apps/member/models.py:229
-msgid "authorization"
-msgstr "autorisation"
-
-#: apps/member/models.py:230
-msgid "authorizations"
-msgstr "autorisations"
-
-#: apps/member/models.py:233
-#, python-brace-format
-msgid "{authorization} for user {user}"
-msgstr "{authorization} pour l'utilisateur {user}"
-
-#: apps/member/models.py:248
-msgid "motivation letter"
-msgstr "lettre de motivation"
-
-#: apps/member/models.py:249
-msgid "motivation letters"
-msgstr "lettres de motivation"
-
-#: apps/member/models.py:252
-#, python-brace-format
-msgid "Motivation letter of team {team} ({trigram})"
-msgstr "Lettre de motivation de l'équipe {team} ({trigram})"
-
-#: apps/member/models.py:267
-msgid "problem"
-msgstr "problème"
-
-#: apps/member/models.py:272
-msgid "final solution"
-msgstr "solution pour la finale"
-
-#: apps/member/models.py:285
-msgid "solution"
-msgstr "solution"
-
-#: apps/member/models.py:286 apps/tournament/models.py:325
-msgid "solutions"
-msgstr "solutions"
-
-#: apps/member/models.py:291
-#, python-brace-format
-msgid "Solution of team {trigram} for problem {problem} for final"
-msgstr ""
-"Solution de l'équipe {trigram} pour le problème {problem} pour la finale"
-
-#: apps/member/models.py:294
-#, python-brace-format
-msgid "Solution of team {trigram} for problem {problem}"
-msgstr "Solution de l'équipe {trigram} pour le problème {problem}"
-
-#: apps/member/models.py:312
-msgid "Opponent"
-msgstr "Opposant"
-
-#: apps/member/models.py:313
-msgid "Rapporteur"
-msgstr "Rapporteur"
-
-#: apps/member/models.py:315
-msgid "source"
-msgstr "source"
-
-#: apps/member/models.py:320 apps/tournament/models.py:330
-msgid "Round 1"
-msgstr "Tour 1"
-
-#: apps/member/models.py:321 apps/tournament/models.py:331
-msgid "Round 2"
-msgstr "Tour 2"
-
-#: apps/member/models.py:323 apps/tournament/models.py:333
-#: templates/tournament/pool_detail.html:18
-msgid "round"
-msgstr "tour"
-
-#: apps/member/models.py:328
-msgid "final synthesis"
-msgstr "synthèse pour la finale"
-
-#: apps/member/models.py:341
-msgid "synthesis"
-msgstr "synthèse"
-
-#: apps/member/models.py:342
-msgid "syntheses"
-msgstr "synthèses"
-
-#: apps/member/models.py:346
-#, python-brace-format
-msgid ""
-"Synthesis of team {trigram} that is {source} for the round {round} of "
-"tournament {tournament}"
-msgstr ""
-"Synthèse de l'équipe {trigram} qui est {source} pour le tour {round} du "
-"tournoi {tournament}"
-
-#: apps/member/models.py:358
-msgid "key"
-msgstr "clé"
-
-#: apps/member/models.py:363
-msgid "value"
-msgstr "valeur"
-
-#: apps/member/models.py:367
-msgid "configuration"
-msgstr "configuration"
-
-#: apps/member/models.py:368
-msgid "configurations"
-msgstr "configurations"
-
-#: apps/member/views.py:105 apps/member/views.py:145
-msgid "You can't organize and participate at the same time."
-msgstr "Vous ne pouvez pas organiser et participer en même temps."
-
-#: apps/member/views.py:109 apps/member/views.py:149
-msgid "You are already in a team."
-msgstr "Vous êtes déjà dans une équipe."
-
-#: apps/member/views.py:153
-msgid "This team is full of coachs."
-msgstr "Cette équipe est pleine en encadrants."
-
-#: apps/member/views.py:157
-msgid "This team is full of participants."
-msgstr "Cette équipe est pleine en participants."
-
-#: apps/member/views.py:161
-msgid "This team is already validated or waiting for validation."
-msgstr "L'équipe est déjà en attente de validation."
-
-#: apps/member/views.py:194
-#, python-format
-msgid "No %(verbose_name)s found matching the query"
-msgstr ""
-
-#: apps/member/views.py:244 templates/base.html:81
-msgid "All profiles"
-msgstr "Tous les profils"
-
-#: apps/member/views.py:256 templates/base.html:80
-msgid "Orphaned profiles"
-msgstr "Profils orphelins"
-
-#: apps/member/views.py:268 apps/tournament/forms.py:23 templates/base.html:83
-msgid "Organizers"
-msgstr "Organisateurs"
-
-#: apps/tournament/apps.py:10 apps/tournament/models.py:135
-#: apps/tournament/models.py:183 apps/tournament/tables.py:110
-#: templates/tournament/pool_detail.html:21
-#: templates/tournament/team_detail.html:21
-msgid "tournament"
-msgstr "tournoi"
-
-#: apps/tournament/forms.py:31
-msgid "This tournament already exists."
-msgstr "Ce tournoi existe déjà."
-
-#: apps/tournament/forms.py:33
-msgid "The final tournament was already defined."
-msgstr "Le tournoi de la finale est déjà défini."
-
-#: apps/tournament/forms.py:65
-msgid "This organizer already exist."
-msgstr "Cet organisateur existe déjà."
-
-#: apps/tournament/forms.py:94
-msgid "The trigram must be composed of three upcase letters."
-msgstr "Le trigramme doit être composé de trois lettres en majuscules."
-
-#: apps/tournament/forms.py:98
-msgid "This trigram is already used."
-msgstr "Ce trigramme est déjà utilisé."
-
-#: apps/tournament/forms.py:101
-msgid "This name is already used."
-msgstr "Ce nom est déjà utilisé."
-
-#: apps/tournament/forms.py:104
-msgid "This tournament is already closed."
-msgstr "Ce tournoi est déjà fermé."
-
-#: apps/tournament/forms.py:115
-msgid "Access code"
-msgstr "Code d'accès"
-
-#: apps/tournament/forms.py:123
-msgid "The access code must be composed of 6 alphanumeric characters."
-msgstr "Le code d'accès doit être composé de 6 caractères alphanumériques."
-
-#: apps/tournament/forms.py:127
-msgid "This access code is invalid."
-msgstr "Ce code d'accès est invalide."
-
-#: apps/tournament/forms.py:130
-msgid "The team is already validated."
-msgstr "L'équipe est déjà validée."
-
-#: apps/tournament/forms.py:142
-msgid "Problem"
-msgstr "Problème"
-
-#: apps/tournament/forms.py:143
-#, python-format
-msgid "Problem #%(problem)d"
-msgstr "Problème n°%(problem)d"
-
-#: apps/tournament/forms.py:152 apps/tournament/forms.py:176
-#, python-format
-msgid ""
-"Please keep filesize under %(max_size)s. Current filesize %(current_size)s"
-msgstr ""
-"Merci de ne pas dépasser les %(max_size)s. Le fichier envoyé pèse "
-"%(current_size)s."
-
-#: apps/tournament/forms.py:157 apps/tournament/forms.py:181
-msgid "The file should be a PDF file."
-msgstr "Ce fichier doit être au format PDF."
-
-#: apps/tournament/forms.py:197 apps/tournament/forms.py:210
-#: apps/tournament/forms.py:223
-msgid "Choose a team..."
-msgstr "Choisir une équipe ..."
-
-#: apps/tournament/forms.py:198
-msgid "Team 1"
-msgstr "Équipe 1"
-
-#: apps/tournament/forms.py:205
-msgid "Problem defended by team 1"
-msgstr "Problème défendu par l'équipe 1"
-
-#: apps/tournament/forms.py:211
-msgid "Team 2"
-msgstr "Équipe 2"
-
-#: apps/tournament/forms.py:218
-msgid "Problem defended by team 2"
-msgstr "Problème défendu par l'équipe 2"
-
-#: apps/tournament/forms.py:224
-msgid "Team 3"
-msgstr "Équipe 3"
-
-#: apps/tournament/forms.py:231
-msgid "Problem defended by team 3"
-msgstr "Problème défendu par l'équipe 3"
-
-#: apps/tournament/models.py:19 apps/tournament/models.py:170
-#: templates/tournament/team_detail.html:12
-msgid "name"
-msgstr "nom"
-
-#: apps/tournament/models.py:25 templates/tournament/tournament_detail.html:12
-msgid "organizers"
-msgstr "organisateurs"
-
-#: apps/tournament/models.py:26
-msgid ""
-"List of all organizers that can see and manipulate data of the tournament "
-"and the teams."
-msgstr ""
-"Liste des organisateurs qui peuvent manipuler les données du tournoi et des "
-"équipes."
-
-#: apps/tournament/models.py:30 templates/tournament/tournament_detail.html:15
-msgid "size"
-msgstr "taille"
-
-#: apps/tournament/models.py:31
-msgid "Number of teams that are allowed to join the tournament."
-msgstr "Nombre d'équipes qui sont autorisées à rejoindre le tournoi."
-
-#: apps/tournament/models.py:36 templates/tournament/tournament_detail.html:18
-msgid "place"
-msgstr "lieu"
-
-#: apps/tournament/models.py:40 templates/tournament/tournament_detail.html:21
-msgid "price"
-msgstr "prix"
-
-#: apps/tournament/models.py:41
-msgid "Price asked to participants. Free with a scholarship."
-msgstr "Prix demandé par participant. Gratuit pour les boursiers."
-
-#: apps/tournament/models.py:50
-msgid "date start"
-msgstr "date de début"
-
-#: apps/tournament/models.py:55
-msgid "date end"
-msgstr "date de fin"
-
-#: apps/tournament/models.py:60 templates/tournament/tournament_detail.html:27
-msgid "date of registration closing"
-msgstr "date de clôture des inscriptions"
-
-#: apps/tournament/models.py:65 templates/tournament/tournament_detail.html:30
-msgid "date of maximal solution submission"
-msgstr "date d'envoi maximal des solutions"
-
-#: apps/tournament/models.py:70 templates/tournament/tournament_detail.html:33
-msgid "date of maximal syntheses submission for the first round"
-msgstr "date d'envoi maximal des notes de synthèses du premier tour"
-
-#: apps/tournament/models.py:75 templates/tournament/tournament_detail.html:36
-msgid "date when solutions of round 2 are available"
-msgstr "date à partir de laquelle les solutions du tour 2 sont disponibles"
-
-#: apps/tournament/models.py:80 templates/tournament/tournament_detail.html:39
-msgid "date of maximal syntheses submission for the second round"
-msgstr "date d'envoi maximal des notes de synthèses pour le second tour"
-
-#: apps/tournament/models.py:84
-msgid "final tournament"
-msgstr "finale"
-
-#: apps/tournament/models.py:85
-msgid "It should be only one final tournament."
-msgstr "Il ne doit y avoir qu'une seule finale."
-
-#: apps/tournament/models.py:136
-msgid "tournaments"
-msgstr "tournois"
-
-#: apps/tournament/models.py:175 templates/tournament/team_detail.html:15
-msgid "trigram"
-msgstr "trigramme"
-
-#: apps/tournament/models.py:176
-msgid ""
-"The trigram should be composed of 3 capitalize letters, that is a funny "
-"acronym for the team."
-msgstr ""
-"Le trigramme doit être composé de trois lettres en majuscule, qui doit être "
-"un acronyme amusant représentant l'équipe."
-
-#: apps/tournament/models.py:184
-msgid "The tournament where the team is registered."
-msgstr "Le tournoi où l'équipe est inscrite."
-
-#: apps/tournament/models.py:189
-msgid "inscription date"
-msgstr "date d'inscription"
-
-#: apps/tournament/models.py:195 apps/tournament/models.py:420
-msgid "Registration not validated"
-msgstr "Inscription non validée"
-
-#: apps/tournament/models.py:196 apps/tournament/models.py:421
-msgid "Waiting for validation"
-msgstr "En attente de validation"
-
-#: apps/tournament/models.py:197 apps/tournament/models.py:422
-msgid "Registration validated"
-msgstr "Inscription validée"
-
-#: apps/tournament/models.py:199 apps/tournament/models.py:424
-#: templates/tournament/team_detail.html:32
-msgid "validation status"
-msgstr "statut de validation"
-
-#: apps/tournament/models.py:204
-msgid "selected for final"
-msgstr "sélectionnée pour la finale"
-
-#: apps/tournament/models.py:210 templates/tournament/team_detail.html:18
-msgid "access code"
-msgstr "code d'accès"
-
-#: apps/tournament/models.py:287 apps/tournament/models.py:319
-#: templates/tournament/pool_detail.html:15
-msgid "teams"
-msgstr "équipes"
-
-#: apps/tournament/models.py:339 templates/tournament/pool_detail.html:12
-msgid "juries"
-msgstr "jurys"
-
-#: apps/tournament/models.py:345
-msgid "extra access token"
-msgstr "code d'accès spécial"
-
-#: apps/tournament/models.py:346
-msgid "Let other users access to the pool data without logging in."
-msgstr "Permet à d'autres utilisateurs d'accéder au contenu de la poule sans connexion."
-
-#: apps/tournament/models.py:380
-msgid "pool"
-msgstr "poule"
-
-#: apps/tournament/models.py:381
-msgid "pools"
-msgstr "poules"
-
-#: apps/tournament/models.py:406
-msgid "Not paid"
-msgstr "Non payé"
-
-#: apps/tournament/models.py:407
-msgid "Credit card"
-msgstr "Carte bancaire"
-
-#: apps/tournament/models.py:408
-msgid "Bank check"
-msgstr "Chèque bancaire"
-
-#: apps/tournament/models.py:409
-msgid "Bank transfer"
-msgstr "Virement bancaire"
-
-#: apps/tournament/models.py:410
-msgid "Cash"
-msgstr "Espèces"
-
-#: apps/tournament/models.py:414
-msgid "payment method"
-msgstr "moyen de paiement"
-
-#: apps/tournament/models.py:428
-msgid "payment"
-msgstr "paiement"
-
-#: apps/tournament/models.py:429
-msgid "payments"
-msgstr "paiements"
-
-#: apps/tournament/models.py:432
-#, python-brace-format
-msgid "Payment of {user}"
-msgstr "Paiement de {user}"
-
-#: apps/tournament/tables.py:22 templates/tournament/tournament_detail.html:24
-msgid "dates"
-msgstr "dates"
-
-#: apps/tournament/tables.py:26
-msgid "From {start:%b %d %Y} to {end:%b %d %Y}"
-msgstr "Du {start: %d %b %Y} au {end:%d %b %Y}"
-
-#: apps/tournament/tables.py:71 apps/tournament/tables.py:147
-msgid "Tournament"
-msgstr "Tournoi"
-
-#: apps/tournament/tables.py:85 apps/tournament/tables.py:124
-#: templates/tournament/team_detail.html:135
-#: templates/tournament/team_detail.html:144
-msgid "Download"
-msgstr "Télécharger"
-
-#: apps/tournament/tables.py:140
-msgid "Problems"
-msgstr "Problèmes"
-
-#: apps/tournament/views.py:68
-msgid "Tournaments list"
-msgstr "Liste des tournois"
-
-#: apps/tournament/views.py:91
-msgid "Add tournament"
-msgstr "Ajouter un tournoi"
-
-#: apps/tournament/views.py:108
-#, python-brace-format
-msgid "Tournament of {name}"
-msgstr "Tournoi de {name}"
-
-#: apps/tournament/views.py:146
-msgid "Update tournament"
-msgstr "Modifier le tournoi"
-
-#: apps/tournament/views.py:195 apps/tournament/views.py:323
-#, python-brace-format
-msgid "Solutions for team {team}.zip"
-msgstr "Solutions pour l'équipe {team}.zip"
-
-#: apps/tournament/views.py:251
-msgid "Information about team"
-msgstr "Informations sur l'équipe"
-
-#: apps/tournament/views.py:266
-msgid "Update team"
-msgstr "Modifier l'équipe"
-
-#: apps/tournament/views.py:284
-msgid "Add organizer"
-msgstr "Ajouter un organisateur"
-
-#: apps/tournament/views.py:307 templates/base.html:108 templates/base.html:118
-#: templates/base.html:132 templates/tournament/pool_detail.html:31
-msgid "Solutions"
-msgstr "Solutions"
-
-#: apps/tournament/views.py:347
-msgid ""
-"You can't publish your solution anymore. Deadline: {date:%m-%d-%Y %H:%M}."
-msgstr ""
-"Vous ne pouvez plus publier vos solutions. Deadline : {date:%d/%m/%Y %H:%M}."
-
-#: apps/tournament/views.py:376
-msgid "All solutions"
-msgstr "Toutes les solutions"
-
-#: apps/tournament/views.py:395
-#, python-brace-format
-msgid "Solutions for tournament {tournament}.zip"
-msgstr "Solutions pour le tournoi {tournament}.zip"
-
-#: apps/tournament/views.py:432 templates/base.html:111 templates/base.html:121
-#: templates/base.html:135 templates/tournament/pool_detail.html:57
-msgid "Syntheses"
-msgstr "Synthèses"
-
-#: apps/tournament/views.py:448
-#, python-brace-format
-msgid "Syntheses for team {team}.zip"
-msgstr "Notes de synthèse de l'équipe {team}.zip"
-
-#: apps/tournament/views.py:473
-msgid ""
-"You can't publish your synthesis anymore for the first round. Deadline: "
-"{date:%m-%d-%Y %H:%M}."
-msgstr ""
-"Vous ne pouvez plus envoyer vos notes de synthèse pour le premier tour. "
-"Deadline : {date:%d/%m/%Y %h:%M}."
-
-#: apps/tournament/views.py:479
-msgid ""
-"You can't publish your synthesis anymore for the second round. Deadline: "
-"{date:%m-%d-%Y %H:%M}."
-msgstr ""
-"Vous ne pouvez plus envoyer vos notes de synthèse pour le second tour. "
-"Deadline : {date:%d/%m/%Y %h:%M}."
-
-#: apps/tournament/views.py:509
-msgid "All syntheses"
-msgstr "Toutes les notes de synthèses"
-
-#: apps/tournament/views.py:528
-#, python-brace-format
-msgid "Syntheses for tournament {tournament}.zip"
-msgstr "Notes de synthèse pour le tournoi {tournament}.zip"
-
-#: apps/tournament/views.py:571 templates/base.html:125 templates/base.html:138
-msgid "Pools"
-msgstr "Poules"
-
-#: apps/tournament/views.py:594
-msgid "Create pool"
-msgstr "Créer une poule"
-
-#: apps/tournament/views.py:611
-msgid "Pool detail"
-msgstr "Détails d'une poule"
-
-#: apps/tournament/views.py:644
-#, python-brace-format
-msgid ""
-"Solutions of a pool for the round {round} of the tournament {tournament}.zip"
-msgstr "Solutions d'une poule du tour {round} du tournoi {tournament}.zip"
-
-#: apps/tournament/views.py:658
-#, python-brace-format
-msgid ""
-"Syntheses of a pool for the round {round} of the tournament {tournament}.zip"
-msgstr "Synthèse d'une poule du tour {round} du tournoi {tournament}.zip"
-
-#: templates/400.html:6
-msgid "Bad request"
-msgstr "Requête invalide"
-
-#: templates/400.html:7
-msgid ""
-"Sorry, your request was bad. Don't know what could be wrong. An email has "
-"been sent to webmasters with the details of the error. You can now drink a "
-"coke."
-msgstr ""
-"Désolé, votre requête comporte une erreur. Aucune idée de ce qui a pu se "
-"passer. Un email a été envoyé au développeur avec les détails de l'erreur. "
-"Vous pouvez désormais aller chercher un coca."
-
-#: templates/403.html:6
-msgid "Permission denied"
-msgstr "Accès refusé"
-
-#: templates/403.html:7
-msgid "You don't have the right to perform this request."
-msgstr "Vous n'avez pas la permission d'effectuer cette requête."
-
-#: templates/403.html:10 templates/404.html:10
-msgid "Exception message:"
-msgstr "Message d'erreur :"
-
-#: templates/404.html:6
-msgid "Page not found"
-msgstr "Page non trouvée"
-
-#: templates/404.html:7
-#, python-format
-msgid ""
-"The requested path %(request_path)s was not found on the server."
-msgstr ""
-"Le chemin demandé %(request_path)s n'a pas été trouvé sur le "
-"serveur."
-
-#: templates/500.html:6
-msgid "Server error"
-msgstr "Erreur du serveur"
-
-#: templates/500.html:7
-msgid ""
-"Sorry, an error occurred when processing your request. An email has been "
-"sent to webmasters with the detail of the error, and this will be fixed "
-"soon. You can now drink a beer."
-msgstr ""
-"Désolé, votre requête comporte une erreur. Aucune idée de ce qui a pu se "
-"passer. Un email a été envoyé au développeur avec les détails de l'erreur. "
-"Vous pouvez désormais aller chercher une bière."
-
-#: templates/base.html:11
-msgid "The inscription site of the TFJM²."
-msgstr "Le site d'inscription au TFJM²."
-
-#: templates/base.html:73
-msgid "Home"
-msgstr "Accueil"
-
-#: templates/base.html:76
-msgid "Tournament list"
-msgstr "Liste des tournois"
-
-#: templates/base.html:89
-msgid "My account"
-msgstr "Mon compte"
-
-#: templates/base.html:94
-msgid "Add a team"
-msgstr "Ajouter une équipe"
-
-#: templates/base.html:97
-msgid "Join a team"
-msgstr "Rejoindre une équipe"
-
-#: templates/base.html:101
-msgid "My team"
-msgstr "Mon équipe"
-
-#: templates/base.html:144
-msgid "Make a gift"
-msgstr "Faire un don"
-
-#: templates/base.html:148
-msgid "Administration"
-msgstr "Administration"
-
-#: templates/base.html:155
-msgid "Return to admin view"
-msgstr "Retour à l'interface administrateur"
-
-#: templates/base.html:160 templates/registration/login.html:7
-#: templates/registration/login.html:8 templates/registration/login.html:22
-#: templates/registration/password_reset_complete.html:10
-msgid "Log in"
-msgstr "Connexion"
-
-#: templates/base.html:163 templates/registration/signup.html:5
-#: templates/registration/signup.html:8 templates/registration/signup.html:14
-msgid "Sign up"
-msgstr "S'inscrire"
-
-#: templates/base.html:167
-msgid "Log out"
-msgstr "Déconnexion"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Filtres"
-
-#: templates/django_filters/rest_framework/form.html:5
-#: templates/member/my_account.html:9 templates/tournament/add_organizer.html:9
-#: templates/tournament/pool_form.html:9
-#: templates/tournament/solutions_list.html:24
-#: templates/tournament/syntheses_list.html:40
-#: templates/tournament/team_form.html:9
-#: templates/tournament/tournament_form.html:9
-msgid "Submit"
-msgstr "Envoyer"
-
-#: templates/member/my_account.html:14
-msgid "Update my password"
-msgstr "Changer mon mot de passe"
-
-#: templates/member/profile_list.html:9
-msgid "Add an organizer"
-msgstr "Ajouter un organisateur"
-
-#: templates/member/tfjmuser_detail.html:12
-msgid "role"
-msgstr "rôle"
-
-#: templates/member/tfjmuser_detail.html:47
-msgid "class"
-msgstr "classe"
-
-#: templates/member/tfjmuser_detail.html:76
-#: templates/tournament/team_detail.html:129
-msgid "Documents"
-msgstr "Documents"
-
-#: templates/member/tfjmuser_detail.html:84
-#, python-format
-msgid "View site as %(tfjmuser)s"
-msgstr "Voir le site en tant que %(tfjmuser)s"
-
-#: templates/registration/email_validation_complete.html:6
-msgid "Your email have successfully been validated."
-msgstr "Votre adresse e-mail a bien été validée."
-
-#: templates/registration/email_validation_complete.html:8
-#, python-format
-msgid "You can now log in."
-msgstr "Vous pouvez désormais vous connecter"
-
-#: templates/registration/email_validation_complete.html:13
-msgid ""
-"The link was invalid. The token may have expired. Please send us an email to "
-"activate your account."
-msgstr ""
-"Le lien est invalide. Le jeton a du expirer. Merci de nous envoyer un mail "
-"afin d'activer votre compte."
-
-#: templates/registration/logged_out.html:8
-msgid "Thanks for spending some quality time with the Web site today."
-msgstr "Merci d'avoir utilisé la plateforme du TFJM²."
-
-#: templates/registration/logged_out.html:9
-msgid "Log in again"
-msgstr "Se connecter à nouveau"
-
-#: templates/registration/login.html:13
-#, python-format
-msgid ""
-"You are authenticated as %(user)s, but are not authorized to access this "
-"page. Would you like to login to a different account?"
-msgstr ""
-"Vous êtes déjà connecté sous le nom %(user)s, mais vous n'êtes pas autorisés "
-"à accéder à cette page. Souhaitez-vous vous connecter sous un compte "
-"différent ?"
-
-#: templates/registration/login.html:23
-msgid "Forgotten your password or username?"
-msgstr "Mot de passe oublié ?"
-
-#: templates/registration/mails/email_validation_email.html:3
-msgid "Hi"
-msgstr "Bonjour"
-
-#: templates/registration/mails/email_validation_email.html:9
-msgid ""
-"This link is only valid for a couple of days, after that you will need to "
-"contact us to validate your email."
-msgstr ""
-
-#: templates/registration/mails/email_validation_email.html:13
-msgid "Thanks"
-msgstr "Merci"
-
-#: templates/registration/password_change_done.html:8
-msgid "Your password was changed."
-msgstr "Votre mot de passe a été changé"
-
-#: templates/registration/password_change_form.html:9
-msgid ""
-"Please enter your old password, for security's sake, and then enter your new "
-"password twice so we can verify you typed it in correctly."
-msgstr ""
-"Veuillez entrer votre ancien mot de passe, pour des raisons de sécurité, "
-"puis entrer votre mot de passe deux fois afin de vérifier que vous l'avez "
-"tapé correctement."
-
-#: templates/registration/password_change_form.html:11
-#: templates/registration/password_reset_confirm.html:12
-msgid "Change my password"
-msgstr "Changer mon mot de passe"
-
-#: templates/registration/password_reset_complete.html:8
-msgid "Your password has been set. You may go ahead and log in now."
-msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter."
-
-#: templates/registration/password_reset_confirm.html:9
-msgid ""
-"Please enter your new password twice so we can verify you typed it in "
-"correctly."
-msgstr ""
-"Veuillez taper votre nouveau mot de passe deux fois afin de s'assurer que "
-"vous l'ayez tapé correctement."
-
-#: templates/registration/password_reset_confirm.html:15
-msgid ""
-"The password reset link was invalid, possibly because it has already been "
-"used. Please request a new password reset."
-msgstr ""
-"Le lien de réinitialisation du mot de passe est invalide, sans doute parce "
-"qu'il a été déjà utilisé. Veuillez demander une nouvelle demande de "
-"réinitialisation."
-
-#: templates/registration/password_reset_done.html:8
-msgid ""
-"We've emailed you instructions for setting your password, if an account "
-"exists with the email you entered. You should receive them shortly."
-msgstr ""
-"Nous vous avons envoyé des instructions pour réinitialiser votre mot de "
-"passe, si un compte existe avec l'adresse email entrée. Vous devriez les "
-"recevoir d'ici peu."
-
-#: templates/registration/password_reset_done.html:9
-msgid ""
-"If you don't receive an email, please make sure you've entered the address "
-"you registered with, and check your spam folder."
-msgstr ""
-"Si vous n'avez pas reçu d'email, merci de vérifier que vous avez entré "
-"l'adresse avec laquelle vous êtes inscrits, et vérifier vos spams."
-
-#: templates/registration/password_reset_form.html:8
-msgid ""
-"Forgotten your password? Enter your email address below, and we'll email "
-"instructions for setting a new one."
-msgstr ""
-"Mot de passe oublié ? Entrez votre adresse email ci-dessous, et nous vous "
-"enverrons des instructions pour en définir un nouveau."
-
-#: templates/registration/password_reset_form.html:11
-msgid "Reset my password"
-msgstr "Réinitialiser mon mot de passe"
-
-#: templates/tournament/pool_detail.html:36
-msgid "Solutions will be available here for teams from:"
-msgstr "Les solutions seront disponibles ici pour les équipes à partir du :"
-
-#: templates/tournament/pool_detail.html:49
-#: templates/tournament/pool_detail.html:73
-msgid "Download ZIP archive"
-msgstr "Télécharger l'archive ZIP"
-
-#: templates/tournament/pool_detail.html:61
-#: templates/tournament/syntheses_list.html:7
-msgid "Templates for syntheses are available here:"
-msgstr "Le modèle de note de synthèse est disponible ici :"
-
-#: templates/tournament/pool_detail.html:83
-msgid "Pool list"
-msgstr "Liste des poules"
-
-#: templates/tournament/pool_detail.html:89
-msgid ""
-"Give this link to juries to access this page (warning: should stay "
-"confidential and only given to juries of this pool):"
-msgstr ""
-"Donnez ce lien aux jurys pour leur permettre d'accéder à cette page "
-"(attention : ce lien doit rester confidentiel et ne doit être donné "
-"exclusivement qu'à des jurys) :"
-
-#: templates/tournament/pool_list.html:10
-msgid "Add pool"
-msgstr "Ajouter une poule"
-
-#: templates/tournament/solutions_list.html:9
-#, python-format
-msgid "You can upload your solutions until %(deadline)s."
-msgstr "Vous pouvez envoyer vos solutions jusqu'au %(deadline)s."
-
-#: templates/tournament/solutions_list.html:14
-msgid ""
-"The deadline to send your solutions is reached. However, you have an extra "
-"time of 30 minutes to send your papers, no panic :)"
-msgstr ""
-"La date limite pour envoyer vos solutions est dépassée. Toutefois, vous avez "
-"droit à un délai supplémentaire de 30 minutes pour envoyer vos papiers, pas "
-"de panique :)"
-
-#: templates/tournament/solutions_list.html:16
-msgid "You can't upload your solutions anymore."
-msgstr "Vous ne pouvez plus publier vos solutions."
-
-#: templates/tournament/solutions_orga_list.html:14
-#: templates/tournament/syntheses_orga_list.html:14
-#, python-format
-msgid "%(tournament)s — ZIP"
-msgstr "%(tournament)s — ZIP"
-
-#: templates/tournament/syntheses_list.html:14
-#: templates/tournament/syntheses_list.html:26
-#, python-format
-msgid "You can upload your syntheses for round %(round)s until %(deadline)s."
-msgstr ""
-"Vous pouvez envoyer vos notes de synthèses pour le tour %(round)s jusqu'au "
-"%(deadline)s."
-
-#: templates/tournament/syntheses_list.html:18
-#: templates/tournament/syntheses_list.html:30
-#, python-format
-msgid ""
-"The deadline to send your syntheses for the round %(round)s is reached. "
-"However, you have an extra time of 30 minutes to send your papers, no "
-"panic :)"
-msgstr ""
-"La date limite pour envoyer vos notes de synthèses pour le tour %(round)s "
-"est dépassée. Toutefois, vous avez droit à un délai supplémentaire de 30 "
-"minutes pour envoyer vos papiers, pas de panique :)"
-
-#: templates/tournament/syntheses_list.html:22
-#: templates/tournament/syntheses_list.html:34
-#, python-format
-msgid "You can't upload your syntheses for the round %(round)s anymore."
-msgstr ""
-"Vous ne pouvez plus publier vos notes de synthèses pour le tour %(round)s."
-
-#: templates/tournament/team_detail.html:8
-msgid "Team"
-msgstr "Équipe"
-
-#: templates/tournament/team_detail.html:25
-msgid "coachs"
-msgstr "encadrants"
-
-#: templates/tournament/team_detail.html:28
-msgid "participants"
-msgstr "participants"
-
-#: templates/tournament/team_detail.html:39
-msgid "Send a mail to people in this team"
-msgstr "Envoyer un mail à toutes les personnes de cette équipe"
-
-#: templates/tournament/team_detail.html:49
-msgid "Edit team"
-msgstr "Modifier l'équipe"
-
-#: templates/tournament/team_detail.html:53
-msgid "Select for final"
-msgstr "Sélectionner pour la finale"
-
-#: templates/tournament/team_detail.html:59
-msgid "Delete team"
-msgstr "Supprimer l'équipe"
-
-#: templates/tournament/team_detail.html:61
-msgid "Leave this team"
-msgstr "Quitter l'équipe"
-
-#: templates/tournament/team_detail.html:105
-msgid "The team is waiting about validation."
-msgstr "L'équipe est en attente de validation"
-
-#: templates/tournament/team_detail.html:112
-msgid "Message addressed to the team:"
-msgstr "Message adressé à l'équipe :"
-
-#: templates/tournament/team_detail.html:114
-msgid "Message..."
-msgstr "Message ..."
-
-#: templates/tournament/team_detail.html:119
-msgid "Invalidate team"
-msgstr "Invalider l'équipe"
-
-#: templates/tournament/team_detail.html:120
-msgid "Validate team"
-msgstr "Valider l'équipe"
-
-#: templates/tournament/team_detail.html:133
-msgid "Motivation letter:"
-msgstr "Lettre de motivation :"
-
-#: templates/tournament/team_detail.html:152
-msgid "Download solutions as ZIP"
-msgstr "Télécharger les solutions en archive ZIP"
-
-#: templates/tournament/tournament_detail.html:22
-msgid "Free"
-msgstr "Gratuit"
-
-#: templates/tournament/tournament_detail.html:25
-msgid "From"
-msgstr "Du"
-
-#: templates/tournament/tournament_detail.html:25
-msgid "to"
-msgstr "à"
-
-#: templates/tournament/tournament_detail.html:48
-msgid "Send a mail to all people in this tournament"
-msgstr "Envoyer un mail à toutes les personnes du tournoi"
-
-#: templates/tournament/tournament_detail.html:49
-msgid "Send a mail to all people in this tournament that are in a valid team"
-msgstr ""
-"Envoyer un mail à toutes les personnes du tournoi dans une équipe valide"
-
-#: templates/tournament/tournament_detail.html:56
-msgid "Edit tournament"
-msgstr "Modifier le tournoi"
-
-#: templates/tournament/tournament_detail.html:63
-msgid "Teams"
-msgstr "Équipes"
-
-#: templates/tournament/tournament_list.html:8
-msgid "Send a mail to all people that are in a team"
-msgstr "Envoyer un mail à toutes les personnes dans une équipe"
-
-#: templates/tournament/tournament_list.html:9
-msgid "Send a mail to all people that are in a valid team"
-msgstr "Envoyer un mail à toutes les personnes dans une équipe validée"
-
-#: templates/tournament/tournament_list.html:15
-msgid "Add a tournament"
-msgstr "Ajouter un tournoi"
-
-#: tfjm/settings.py:147
-msgid "English"
-msgstr "Anglais"
-
-#: tfjm/settings.py:148
-msgid "French"
-msgstr "Français"
diff --git a/manage.py b/manage.py
deleted file mode 100755
index 2bf0cbf..0000000
--- a/manage.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python
-"""Django's command-line utility for administrative tasks."""
-import os
-import sys
-
-
-def main():
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
- try:
- from django.core.management import execute_from_command_line
- except ImportError as exc:
- raise ImportError(
- "Couldn't import Django. Are you sure it's installed and "
- "available on your PYTHONPATH environment variable? Did you "
- "forget to activate a virtual environment?"
- ) from exc
- execute_from_command_line(sys.argv)
-
-
-if __name__ == '__main__':
- main()
diff --git a/nginx_tfjm.conf b/nginx_tfjm.conf
deleted file mode 100644
index be143ce..0000000
--- a/nginx_tfjm.conf
+++ /dev/null
@@ -1,19 +0,0 @@
-upstream tfjm {
- server 127.0.0.1:8000;
-}
-
-server {
- listen 80;
- server_name tfjm;
-
- location / {
- proxy_pass http://tfjm;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $host;
- proxy_redirect off;
- }
-
- location /static {
- alias /code/static/;
- }
-}
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 093ea76..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-bcrypt
-Django~=3.0
-django-allauth
-django-bootstrap-datepicker-plus
-django-crispy-forms
-django-extensions
-django-filter
-django-mailer
-django-polymorphic
-django-tables2
-djangorestframework
-django-rest-polymorphic
-mysqlclient
-psycopg2-binary
-ptpython
-gunicorn
\ No newline at end of file
diff --git a/templates/400.html b/templates/400.html
deleted file mode 100644
index 3560652..0000000
--- a/templates/400.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-
-{% block content %}
-
{% trans "Bad request" %}
- {% blocktrans %}Sorry, your request was bad. Don't know what could be wrong. An email has been sent to webmasters with the details of the error. You can now drink a coke.{% endblocktrans %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/403.html b/templates/403.html
deleted file mode 100644
index 317865f..0000000
--- a/templates/403.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-
-{% block content %}
-
{% trans "Permission denied" %}
- {% blocktrans %}You don't have the right to perform this request.{% endblocktrans %}
- {% if exception %}
-
- {% trans "Exception message:" %} {{ exception }}
-
- {% endif %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/404.html b/templates/404.html
deleted file mode 100644
index 8477f91..0000000
--- a/templates/404.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-
-{% block content %}
-
{% trans "Page not found" %}
- {% blocktrans %}The requested path {{ request_path }} was not found on the server.{% endblocktrans %}
- {% if exception != "Resolver404" %}
-
- {% trans "Exception message:" %} {{ exception }}
-
- {% endif %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/500.html b/templates/500.html
deleted file mode 100644
index 7cc0063..0000000
--- a/templates/500.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-
-{% block content %}
-
{% trans "Server error" %}
- {% blocktrans %}Sorry, an error occurred when processing your request. An email has been sent to webmasters with the detail of the error, and this will be fixed soon. You can now drink a beer.{% endblocktrans %}
-{% endblock %}
diff --git a/templates/amount_input.html b/templates/amount_input.html
deleted file mode 100644
index 6ef4a53..0000000
--- a/templates/amount_input.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- €
-
-
\ No newline at end of file
diff --git a/templates/autocomplete_model.html b/templates/autocomplete_model.html
deleted file mode 100644
index 2236c6e..0000000
--- a/templates/autocomplete_model.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-Un compte organisateur vous a été créé par l'un des administrateurs. Avant de vous connecter, vous devez réinitialiser votre
-mot de passe sur le lien suivant : https://inscription.tfjm.org{% url "password_reset" %}.
-
-Une fois le mot de passe changé, vous pourrez vous connecter sur la plateforme.
-
-Merci beaucoup pour votre aide !
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/add_organizer.txt b/templates/mail_templates/add_organizer.txt
deleted file mode 100644
index 2a4cde4..0000000
--- a/templates/mail_templates/add_organizer.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Bonjour {{ user }},
-
-Vous recevez ce message (envoyé automatiquement) car vous êtes organisateur d'un des tournois du TFJM².
-
-Un compte organisateur vous a été créé par l'un des administrateurs. Avant de vous connecter, vous devez réinitialiser votre
-mot de passe sur le lien suivant : https://inscription.tfjm.org{% url "password_reset" %}.
-
-Une fois le mot de passe changé, vous pourrez vous connecter sur la plateforme : https://inscription.tfjm.org{% url "login" %}.
-
-Merci beaucoup pour votre aide !
-
-Le comité national d'organisation du TFJM²
diff --git a/templates/mail_templates/add_organizer_for_tournament.html b/templates/mail_templates/add_organizer_for_tournament.html
deleted file mode 100644
index ad9aa90..0000000
--- a/templates/mail_templates/add_organizer_for_tournament.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
- Organisateur du tournoi de {TOURNAMENT_NAME} – TFJM²
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Vous venez d'être promu organisateur du tournoi {TOURNAMENT_NAME} du TFJM2 {YEAR}.
-Ce message vous a été envoyé automatiquement. En cas de problème, merci de répondre à ce message.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/add_team.html b/templates/mail_templates/add_team.html
deleted file mode 100644
index bb69db0..0000000
--- a/templates/mail_templates/add_team.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- Nouvelle équipe TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Vous venez de créer l'équipe « {TEAM_NAME} » ({TRIGRAM}) pour le TFJM2 de {TOURNAMENT_NAME} et nous vous en remercions.
-Afin de permettre aux autres membres de votre équipe de vous rejoindre, veuillez leur transmettre le code d'accès :
-{ACCESS_CODE}
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/change_email_address.html b/templates/mail_templates/change_email_address.html
deleted file mode 100644
index d04ed90..0000000
--- a/templates/mail_templates/change_email_address.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
- Changement d'adresse e-mail – TFJM²
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Vous venez de changer votre adresse e-mail. Veuillez désormais la confirmer en cliquant ici : {URL_BASE}/confirmer_mail/{TOKEN}
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/change_password.html b/templates/mail_templates/change_password.html
deleted file mode 100644
index 673e80f..0000000
--- a/templates/mail_templates/change_password.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- Mot de passe changé – TFJM²
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Nous vous informons que votre mot de passe vient d'être modifié. Si vous n'êtes pas à l'origine de cette manipulation,
-veuillez immédiatement vérifier vos accès à votre boîte mail et changer votre mot de passe sur la plateforme
-d'inscription.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/confirm_email.html b/templates/mail_templates/confirm_email.html
deleted file mode 100644
index d247377..0000000
--- a/templates/mail_templates/confirm_email.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
- Inscription au TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Vous êtes inscrit au TFJM2 {YEAR} et nous vous en remercions.
-Pour valider votre adresse e-mail, veuillez cliquer sur le lien : {URL_BASE}/confirmer_mail/{TOKEN}
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/forgotten_password.html b/templates/mail_templates/forgotten_password.html
deleted file mode 100644
index 717cc8c..0000000
--- a/templates/mail_templates/forgotten_password.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
- Mot de passe oublié – TFJM²
-
-
-Bonjour,
-
-Vous avez indiqué avoir oublié votre mot de passe. Veuillez cliquer ici pour le réinitialiser : {URL_BASE}/connexion/reinitialiser_mdp/{TOKEN}
-
-Si vous n'êtes pas à l'origine de cette manipulation, vous pouvez ignorer ce message.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/join_team.html b/templates/mail_templates/join_team.html
deleted file mode 100644
index d5628c0..0000000
--- a/templates/mail_templates/join_team.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- Équipe rejointe – TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Vous venez de rejoindre l'équipe « {TEAM_NAME} » ({TRIGRAM}) pour le TFJM² de {TOURNAMENT_NAME} et nous vous en
-remercions.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/register.html b/templates/mail_templates/register.html
deleted file mode 100644
index bc4123b..0000000
--- a/templates/mail_templates/register.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
- Inscription au TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Vous venez de vous inscrire au TFJM2 {YEAR} et nous vous en remercions.
-Pour valider votre adresse e-mail, veuillez cliquer sur le lien : {URL_BASE}/confirmer_mail/{TOKEN}
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/request_payment_validation.html b/templates/mail_templates/request_payment_validation.html
deleted file mode 100644
index 913e490..0000000
--- a/templates/mail_templates/request_payment_validation.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
- Demande de validation de paiement pour le TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-{USER_FIRST_NAME} {USER_SURNAME} de l'équipe {TEAM_NAME} ({TRIGRAM}) annonce avoir réglé sa participation pour le tournoi {TOURNAMENT_NAME}.
-Les informations suivantes ont été communiquées :
-Équipe : {TEAM_NAME} ({TRIGRAM})
-Tournoi : {TOURNAMENT_NAME}
-Moyen de paiement : {PAYMENT_METHOD}
-Montant : {AMOUNT} €
-Informations sur le paiement : {PAYMENT_INFOS}
-
-Vous pouvez désormais vérifier ces informations, puis valider (ou non) le paiement sur
-la page associée à ce participant.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/request_validation.html b/templates/mail_templates/request_validation.html
deleted file mode 100644
index 7a05122..0000000
--- a/templates/mail_templates/request_validation.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
- Demande de validation - TFJM²
-
-
-Bonjour {{ user }},
-
-L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer au tournoi
-{{ tournament }} du TFJM². Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe :
-https://inscription.tfjm.org{% url "tournament:team_detail" pk=team.pk %}
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/request_validation.txt b/templates/mail_templates/request_validation.txt
deleted file mode 100644
index 88d463b..0000000
--- a/templates/mail_templates/request_validation.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Bonjour {{ user }},
-
-L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer au tournoi
-{{ tournament }} du TFJM². Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe :
-https://inscription.tfjm.org{% url "tournament:team_detail" pk=team.pk %}.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM²
diff --git a/templates/mail_templates/select_for_final.html b/templates/mail_templates/select_for_final.html
deleted file mode 100644
index 383b527..0000000
--- a/templates/mail_templates/select_for_final.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
- Sélection pour la finale - TFJM²
-
-
-Bonjour {{ user }},
-
-Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est sélectionnée pour la finale nationale !
-
-La finale aura lieu du {{ final.date_start }} au {{ final.date_end }}. Vous pouvez peaufiner vos solutions
-si vous le souhaitez jusqu'au {{ final.date_solutions }}.
-
-Bravo encore !
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM²
-
-
\ No newline at end of file
diff --git a/templates/mail_templates/select_for_final.txt b/templates/mail_templates/select_for_final.txt
deleted file mode 100644
index a000c22..0000000
--- a/templates/mail_templates/select_for_final.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Bonjour {{ user }},
-
-Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est sélectionnée pour la finale nationale !
-
-La finale aura lieu du {{ final.date_start }} au {{ final.date_end }}. Vous pouvez peaufiner vos solutions
-si vous le souhaitez jusqu'au {{ final.date_solutions }}.
-
-Bravo encore !
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM²
diff --git a/templates/mail_templates/unvalidate_payment.html b/templates/mail_templates/unvalidate_payment.html
deleted file mode 100644
index 7273282..0000000
--- a/templates/mail_templates/unvalidate_payment.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
- Non-validation du paiement pour le TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Votre paiement pour le TFJM² {YEAR} a malheureusement été rejeté. Pour rappel, vous aviez fourni ces informations :
-Équipe : {TEAM_NAME} ({TRIGRAM})
-Tournoi : {TOURNAMENT_NAME}
-Moyen de paiement : {PAYMENT_METHOD}
-Montant : {AMOUNT} €
-Informations sur le paiement : {PAYMENT_INFOS}
-
-{MESSAGE}
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/unvalidate_team.html b/templates/mail_templates/unvalidate_team.html
deleted file mode 100644
index 1ba3ce8..0000000
--- a/templates/mail_templates/unvalidate_team.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
- Équipe non validée – TFJM²
-
-
-Bonjour {{ user }},
-
-Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos autorisations sont correctes.
-{% if message %}
-
- Le CNO vous adresse le message suivant :
-
- {{ message }}
-
-
-{% endif %}
-
-N'hésitez pas à nous contacter à l'adresse contact@tfjm.org pour plus d'informations.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/unvalidate_team.txt b/templates/mail_templates/unvalidate_team.txt
deleted file mode 100644
index 88c1440..0000000
--- a/templates/mail_templates/unvalidate_team.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Bonjour {{ user }},
-
-Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos autorisations sont correctes.
-
-{% if message %}
-Le CNO vous adresse le message suivant :
-
-{{ message }}
-{% endif %}
-
-N'hésitez pas à nous contacter à l'adresse contact@tfjm.org pour plus d'informations.
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM²
diff --git a/templates/mail_templates/validate_payment.html b/templates/mail_templates/validate_payment.html
deleted file mode 100644
index 2743f17..0000000
--- a/templates/mail_templates/validate_payment.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
- Validation du paiement pour le TFJM² {YEAR}
-
-
-Bonjour {FIRST_NAME} {SURNAME},
-
-Votre paiement pour le TFJM² {YEAR} a bien été validé. Pour rappel, vous aviez fourni ces informations :
-Équipe : {TEAM_NAME} ({TRIGRAM})
-Tournoi : {TOURNAMENT_NAME}
-Moyen de paiement : {PAYMENT_METHOD}
-Montant : {AMOUNT} €
-Informations sur le paiement : {PAYMENT_INFOS}
-
-{MESSAGE}
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/validate_team.html b/templates/mail_templates/validate_team.html
deleted file mode 100644
index ef23e85..0000000
--- a/templates/mail_templates/validate_team.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
- Équipe validée – TFJM² {YEAR}
-
-
-Bonjour {{ user }},
-
-Félicitations ! Votre équipe « {{ team }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte à travailler sur
-vos problèmes et publier vos solutions sur la plateforme.
-{% if message %}
-
- Le CNO vous adresse le message suivant :
-
- {{ message }}
-
-
-{% endif %}
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM2
-
-
diff --git a/templates/mail_templates/validate_team.txt b/templates/mail_templates/validate_team.txt
deleted file mode 100644
index 8b4c95e..0000000
--- a/templates/mail_templates/validate_team.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Bonjour {{ user }},
-
-Félicitations ! Votre équipe « {{ team }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte à travailler sur
-vos problèmes et publier vos solutions sur la plateforme.
-
-{% if message %}
-Le CNO vous adresse le message suivant :
-{{ message }}
-{% endif %}
-
-Avec toute notre bienveillance,
-
-Le comité national d'organisation du TFJM²
diff --git a/templates/member/my_account.html b/templates/member/my_account.html
deleted file mode 100644
index 884bf2b..0000000
--- a/templates/member/my_account.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n crispy_forms_filters %}
-
-{% block content %}
-
-
-
-
- {% trans "Update my password" %}
-{% endblock %}
diff --git a/templates/member/profile_list.html b/templates/member/profile_list.html
deleted file mode 100644
index 7a6f85f..0000000
--- a/templates/member/profile_list.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "base.html" %}
-
-{% load django_tables2 i18n %}
-
-{% block content %}
- {% render_table table %}
- {% if type == "organizers" and user.admin %}
-
- {% trans "Add an organizer" %}
- {% endif %}
-{% endblock %}
diff --git a/templates/member/tfjmuser_detail.html b/templates/member/tfjmuser_detail.html
deleted file mode 100644
index 26e63b4..0000000
--- a/templates/member/tfjmuser_detail.html
+++ /dev/null
@@ -1,87 +0,0 @@
-{% extends "base.html" %}
-
-{% load getconfig i18n django_tables2 static %}
-
-{% block content %}
-
- {% blocktrans trimmed %}
- You are authenticated as {{ user }}, but are not authorized to
- access this page. Would you like to login to a different account?
- {% endblocktrans %}
-
- {% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
-
-
-
- {% trans "Thanks" %},
-
-
---
-
- {% trans "The CNO." %}
-
diff --git a/templates/registration/mails/email_validation_email.txt b/templates/registration/mails/email_validation_email.txt
deleted file mode 100644
index 2022294..0000000
--- a/templates/registration/mails/email_validation_email.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-{% load i18n %}
-
-{% trans "Hi" %} {{ user.username }},
-
-{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
-
-https://{{ domain }}{% url 'member:email_validation' uidb64=uid token=token %}
-
-{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
-
-{% trans "Thanks" %},
-
-{% trans "The CNO." %}
diff --git a/templates/registration/password_change_done.html b/templates/registration/password_change_done.html
deleted file mode 100644
index 150a00e..0000000
--- a/templates/registration/password_change_done.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends "base.html" %}
-{% comment %}
-SPDX-License-Identifier: GPL-3.0-or-later
-{% endcomment %}
-{% load i18n %}
-
-{% block content %}
-
{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}
-
{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}
- {% blocktrans with deadline=user.team.future_tournament.date_solutions %}You can upload your solutions until {{ deadline }}.{% endblocktrans %}
-
- {% else %}
-
- {% if now < real_deadline %}
- {% trans "The deadline to send your solutions is reached. However, you have an extra time of 30 minutes to send your papers, no panic :)" %}
- {% else %}
- {% trans "You can't upload your solutions anymore." %}
- {% endif %}
-
- {% trans "Templates for syntheses are available here:" %}
- PDF — TEX
-
-
- {% if form %}
- {% if now < user.team.future_tournament.date_syntheses %}
-
- {% blocktrans with deadline=user.team.future_tournament.date_syntheses round=1 %}You can upload your syntheses for round {{ round }} until {{ deadline }}.{% endblocktrans %}
-
- {% elif now < real_deadline_1 %}
-
- {% blocktrans with round=1 %}The deadline to send your syntheses for the round {{ round }} is reached. However, you have an extra time of 30 minutes to send your papers, no panic :){% endblocktrans %}
-
- {% elif now < user.team.future_tournament.date_solutions_2 %}
-
- {% blocktrans with round=1 %}You can't upload your syntheses for the round {{ round }} anymore.{% endblocktrans %}
-
- {% elif now < user.team.future_tournament.date_syntheses_2 %}
-
- {% blocktrans with deadline=user.team.future_tournament.date_syntheses_2 round=2 %}You can upload your syntheses for round {{ round }} until {{ deadline }}.{% endblocktrans %}
-
- {% elif now < real_deadline_2 %}
-
- {% blocktrans with round=2 %}The deadline to send your syntheses for the round {{ round }} is reached. However, you have an extra time of 30 minutes to send your papers, no panic :){% endblocktrans %}
-
- {% else %}
-
- {% blocktrans with round=2 %}You can't upload your syntheses for the round {{ round }} anymore.{% endblocktrans %}
-
- {% endif %}
-
- {% if user.admin or user in team.tournament.organizers.all or team == user.team %}
-
- {% endif %}
-
-
- {% if user.participates and team.invalid %}
-
- {% if team.can_validate %}
-
- {% else %}
-
- Pour demander à valider votre équipe, vous devez avoir au moins un encadrant, quatre participants
- et soumis une autorisation de droit à l'image, une fiche sanitaire et une autorisation
- parentale (si besoin) par participant, ainsi qu'une lettre de motivation à transmettre aux
- organisateurs.
- Les encadrants doivent également fournir une autorisation de droit à l'image.
-
- {% endif %}
-
-
- En raison du changement de format du TFJM² 2020, il n'y a plus de document obligatoire à envoyer. Les
- autorisations
- précédemment envoyées ont été détruites. Seules les lettres de motivation ont été conservées, mais leur
- envoi
- n'est plus obligatoire.
-
- {% endif %}
-
- {% if team.waiting %}
-
-
- {% trans "The team is waiting about validation." %}
-