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 @@ - - -
    -
diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index 884633b..0000000 --- a/templates/base.html +++ /dev/null @@ -1,227 +0,0 @@ -{% load static i18n static getconfig %} - - - - - - - - {% block title %}{{ title }}{% endblock title %} - Inscription au TFJM² - - - - {# Favicon #} - - - {% if no_cache %} - - {% endif %} - - {# Bootstrap CSS #} - - - - - {# Custom CSS #} - - - {# JQuery, Bootstrap and Turbolinks JavaScript #} - - - - - - {# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #} - {% if form.media %} - {{ form.media }} - {% endif %} - - - - {% block extracss %}{% endblock %} - - -
- -
- {% block contenttitle %}

{{ title }}

{% endblock %} -
- {% block content %} -

Default content...

- {% endblock content %} -
-
- -
-
-
-
-
- - 𝕋𝔽𝕁𝕄² — - Nous contacter — - - {% csrf_token %} - - -
-
- -
-
-
- - - -{% block extrajavascript %} -{% endblock extrajavascript %} - - diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 9455dc1..0000000 --- a/templates/index.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% load getconfig %} - -{% block content %} - {% autoescape off %} - {{ "index_page"|get_config|safe }} - {% endautoescape %} -{% endblock %} diff --git a/templates/mail_templates/add_organizer.html b/templates/mail_templates/add_organizer.html deleted file mode 100644 index 06cc500..0000000 --- a/templates/mail_templates/add_organizer.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Organisateur du TFJM² - - -Bonjour {{ user }},
-
-Vous recevez ce message (envoyé automatiquement) car vous êtes organisateur d'un des tournois du TFJM2.

-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 %} -
- {% csrf_token %} - {{ form|crispy }} - -
- -
- - {% 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 %} -
-
-

{{ tfjmuser }}

-
-
-
-
{% trans 'role'|capfirst %}
-
{{ tfjmuser.get_role_display }}
- - {% if tfjmuser.team %} -
{% trans 'team'|capfirst %}
-
{{ tfjmuser.team }}
- {% endif %} - - {% if tfjmuser.birth_date %} -
{% trans 'birth date'|capfirst %}
-
{{ tfjmuser.birth_date }}
- {% endif %} - - {% if tfjmuser.participates %} -
{% trans 'gender'|capfirst %}
-
{{ tfjmuser.get_gender_display }}
- {% endif %} - - {% if tfjmuser.address %} -
{% trans 'address'|capfirst %}
-
{{ tfjmuser.address }}, {{ tfjmuser.postal_code }}, {{ tfjmuser.city }}{% if tfjmuser.country != "France" %}, {{ tfjmuser.country }}{% endif %}
- {% endif %} - -
{% trans 'email'|capfirst %}
-
{{ tfjmuser.email }}
- - {% if tfjmuser.phone_number %} -
{% trans 'phone number'|capfirst %}
-
{{ tfjmuser.phone_number }}
- {% endif %} - - {% if tfjmuser.role == '3participant' %} -
{% trans 'school'|capfirst %}
-
{{ tfjmuser.school }}
- -
{% trans 'class'|capfirst %}
-
{{ tfjmuser.get_student_class_display }}
- - {% if tfjmuser.responsible_name %} -
{% trans 'responsible name'|capfirst %}
-
{{ tfjmuser.responsible_name }}
- {% endif %} - - {% if tfjmuser.responsible_phone %} -
{% trans 'responsible phone'|capfirst %}
-
{{ tfjmuser.responsible_phone }}
- {% endif %} - - {% if tfjmuser.responsible_email %} -
{% trans 'responsible email'|capfirst %}
-
{{ tfjmuser.responsible_email }}
- {% endif %} - {% endif %} - - {% if tfjmuser.role != '3participant' %} -
{% trans 'description'|capfirst %}
-
{{ tfjmuser.description|default_if_none:"" }}
- {% endif %} -
-
-
- -
- -

{% trans "Documents" %}

- - {# TODO Display documents #} - - {% if request.user.is_superuser %} -
-
- {% csrf_token %} - -
- {% endif %} -{% endblock %} diff --git a/templates/registration/email_validation_complete.html b/templates/registration/email_validation_complete.html deleted file mode 100644 index f58a7e5..0000000 --- a/templates/registration/email_validation_complete.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n %} - -{% block content %} -
-

- {{ title }} -

-
- {% if validlink %} -

- {% trans "Your email have successfully been validated." %} -

-

- {% blocktrans %}You can now log in.{% endblocktrans %} -

- {% else %} -

- {% trans "The link was invalid. The token may have expired. Please send us an email to activate your account." %} -

- {% endif %} -
-
-{% endblock %} diff --git a/templates/registration/email_validation_email_sent.html b/templates/registration/email_validation_email_sent.html deleted file mode 100644 index adc0c02..0000000 --- a/templates/registration/email_validation_email_sent.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n %} - -{% block content %} -
-

- {% trans "Account activation" %} -

-
-

- {% trans "An email has been sent. Please click on the link to activate your account." %} -

-
-
-{% endblock %} diff --git a/templates/registration/logged_out.html b/templates/registration/logged_out.html deleted file mode 100644 index 3b044b7..0000000 --- a/templates/registration/logged_out.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n %} - -{% block content %} -

{% trans "Thanks for spending some quality time with the Web site today." %}

-

{% trans 'Log in again' %}

-{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html deleted file mode 100644 index 64c5c26..0000000 --- a/templates/registration/login.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-2.0-or-later -{% endcomment %} -{% load i18n crispy_forms_filters %} - -{% block title %}{% trans "Log in" %}{% endblock %} -{% block contenttitle %}

{% trans "Log in" %}

{% endblock %} - -{% block content %} - {% if user.is_authenticated %} -

- {% 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 %} -

- {% endif %} -
- {% csrf_token %} - {{ form | crispy }} - - {% trans 'Forgotten your password or username?' %} -
-{% endblock %} diff --git a/templates/registration/mails/email_validation_email.html b/templates/registration/mails/email_validation_email.html deleted file mode 100644 index 9d002d6..0000000 --- a/templates/registration/mails/email_validation_email.html +++ /dev/null @@ -1,36 +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/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 'Your password was changed.' %}

-{% endblock %} diff --git a/templates/registration/password_change_form.html b/templates/registration/password_change_form.html deleted file mode 100644 index 01133e4..0000000 --- a/templates/registration/password_change_form.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
{% csrf_token %} -

{% trans "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." %}

- {{ form | crispy }} - -
-{% endblock %} \ No newline at end of file diff --git a/templates/registration/password_reset_complete.html b/templates/registration/password_reset_complete.html deleted file mode 100644 index bb91a3c..0000000 --- a/templates/registration/password_reset_complete.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n %} - -{% block content %} -

{% trans "Your password has been set. You may go ahead and log in now." %}

-

- {% trans 'Log in' %} -

-{% endblock %} diff --git a/templates/registration/password_reset_confirm.html b/templates/registration/password_reset_confirm.html deleted file mode 100644 index 5db0e81..0000000 --- a/templates/registration/password_reset_confirm.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} - {% if validlink %} -

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

-
{% csrf_token %} - {{ form | crispy }} - -
- {% else %} -

{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}

- {% endif %} -{% endblock %} diff --git a/templates/registration/password_reset_done.html b/templates/registration/password_reset_done.html deleted file mode 100644 index a215ab9..0000000 --- a/templates/registration/password_reset_done.html +++ /dev/null @@ -1,10 +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." %}

-{% endblock %} diff --git a/templates/registration/password_reset_form.html b/templates/registration/password_reset_form.html deleted file mode 100644 index 61adaa9..0000000 --- a/templates/registration/password_reset_form.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -

{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}

-
{% csrf_token %} - {{ form | crispy }} - -
-{% endblock %} diff --git a/templates/registration/signup.html b/templates/registration/signup.html deleted file mode 100644 index ed100d0..0000000 --- a/templates/registration/signup.html +++ /dev/null @@ -1,17 +0,0 @@ - -{% extends 'base.html' %} -{% load crispy_forms_filters %} -{% load i18n %} -{% block title %}{% trans "Sign up" %}{% endblock %} - -{% block content %} -

{% trans "Sign up" %}

- -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} diff --git a/templates/tournament/add_organizer.html b/templates/tournament/add_organizer.html deleted file mode 100644 index 21daee8..0000000 --- a/templates/tournament/add_organizer.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% load i18n crispy_forms_filters %} - -{% block content %} -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} diff --git a/templates/tournament/pool_detail.html b/templates/tournament/pool_detail.html deleted file mode 100644 index d20c2b8..0000000 --- a/templates/tournament/pool_detail.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "base.html" %} - -{% load getconfig i18n django_tables2 static %} - -{% block content %} -
-
-

{{ title }}

-
-
-
-
{% trans 'juries'|capfirst %}
-
{{ pool.juries.all|join:", " }}
- -
{% trans 'teams'|capfirst %}
-
{{ pool.teams.all|join:", " }}
- -
{% trans 'round'|capfirst %}
-
{{ pool.round }}
- -
{% trans 'tournament'|capfirst %}
-
{{ pool.tournament }}
-
-
-
- -
- -
-
-

{% trans "Solutions" %}

-
-
- {% if pool.round == 2 %} -
- {% trans "Solutions will be available here for teams from:" %} {{ pool.tournament.date_solutions_2 }} -
- {% endif %} - -
    - {% for solution in pool.solutions.all %} -
  • {{ solution }}
  • - {% endfor %} -
-
- -
-
- -
-
-

{% trans "Syntheses" %}

-
-
-
- {% trans "Templates for syntheses are available here:" %} - PDFTEX -
- {% if user.organizes or not user.is_authenticated %} -
    - {% for synthesis in pool.syntheses.all %} -
  • {{ synthesis }}
  • - {% endfor %} -
- - {% endif %} -
-
- -
- - - - {% if user.organizes or not user.is_authenticated %} -
-
- {% trans "Give this link to juries to access this page (warning: should stay confidential and only given to juries of this pool):" %}
- - https://{{ request.get_host }}{% url "tournament:pool_detail" pk=pool.pk %}?extra_access_token={{ pool.extra_access_token }} -
- {% endif %} -{% endblock %} diff --git a/templates/tournament/pool_form.html b/templates/tournament/pool_form.html deleted file mode 100644 index 21daee8..0000000 --- a/templates/tournament/pool_form.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% load i18n crispy_forms_filters %} - -{% block content %} -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} diff --git a/templates/tournament/pool_list.html b/templates/tournament/pool_list.html deleted file mode 100644 index 9a15348..0000000 --- a/templates/tournament/pool_list.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} - -{% load i18n django_tables2 %} - -{% block content %} - {% render_table table %} - - {% if user.admin %} -
- - {% endif %} -{% endblock %} diff --git a/templates/tournament/solutions_list.html b/templates/tournament/solutions_list.html deleted file mode 100644 index 6005d11..0000000 --- a/templates/tournament/solutions_list.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} - -{% load i18n crispy_forms_filters django_tables2 %} - -{% block content %} - {% if form %} - {% if now < user.team.future_tournament.date_solutions %} -
- {% 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 %} -
- {% endif %} - -
- {% csrf_token %} - {{ form|crispy }} - -
-
- {% endif %} - {% render_table table %} -{% endblock %} diff --git a/templates/tournament/solutions_orga_list.html b/templates/tournament/solutions_orga_list.html deleted file mode 100644 index fe83b4f..0000000 --- a/templates/tournament/solutions_orga_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% load i18n django_tables2 %} - -{% block content %} - {% render_table table %} - -
- -
- {% csrf_token %} -
- {% for tournament in tournaments.all %} - - {% endfor %} -
-
-{% endblock %} diff --git a/templates/tournament/syntheses_list.html b/templates/tournament/syntheses_list.html deleted file mode 100644 index b1cf355..0000000 --- a/templates/tournament/syntheses_list.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "base.html" %} - -{% load i18n crispy_forms_filters django_tables2 static %} - -{% block content %} -
- {% trans "Templates for syntheses are available here:" %} - PDFTEX -
- - {% 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 %} -
- {% csrf_token %} - {{ form|crispy }} - -
-
- {% endif %} - {% render_table table %} -{% endblock %} diff --git a/templates/tournament/syntheses_orga_list.html b/templates/tournament/syntheses_orga_list.html deleted file mode 100644 index fe83b4f..0000000 --- a/templates/tournament/syntheses_orga_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% load i18n django_tables2 %} - -{% block content %} - {% render_table table %} - -
- -
- {% csrf_token %} -
- {% for tournament in tournaments.all %} - - {% endfor %} -
-
-{% endblock %} diff --git a/templates/tournament/team_detail.html b/templates/tournament/team_detail.html deleted file mode 100644 index 21dbada..0000000 --- a/templates/tournament/team_detail.html +++ /dev/null @@ -1,156 +0,0 @@ -{% extends "base.html" %} - -{% load getconfig i18n django_tables2 static %} - -{% block content %} -
-
-

{% trans "Team" %} {{ team.name }}

-
-
-
-
{% trans 'name'|capfirst %}
-
{{ team.name }}
- -
{% trans 'trigram'|capfirst %}
-
{{ team.trigram }}
- -
{% trans 'access code'|capfirst %}
-
{{ team.access_code }}
- -
{% trans 'tournament'|capfirst %}
-
{{ team.tournament }}
- -
{% trans 'coachs'|capfirst %}
-
{% autoescape off %}{{ team.linked_coaches|join:", " }}{% endautoescape %}
- -
{% trans 'participants'|capfirst %}
-
- {% autoescape off %}{{ team.linked_participants|join:", " }}{% endautoescape %}
- -
{% trans 'validation status'|capfirst %}
-
{{ team.get_validation_status_display }}
-
-
- - {% if user.is_authenticated and user.admin %} - - {% 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 %} -
- {% csrf_token %} - - -
- Attention ! Une fois votre équipe validée, vous ne pourrez plus modifier le nom - de l'équipe, le trigramme ou la composition de l'équipe. -
- -
- {% 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." %} -
- - {% if user.admin %} -
- {% csrf_token %} -
- - -
- -
-
- - -
-
-
- {% endif %} - {% endif %} - -
- -

{% trans "Documents" %}

- - {% if team.motivation_letters.count %} -
- {% blocktrans %}Motivation letter:{% endblocktrans %} - {% trans "Download" %} -
- {% endif %} - - {% if team.solutions.count %} -
- -
-
-
- {% csrf_token %} - -
-
- {% endif %} -{% endblock %} diff --git a/templates/tournament/team_form.html b/templates/tournament/team_form.html deleted file mode 100644 index 21daee8..0000000 --- a/templates/tournament/team_form.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% load i18n crispy_forms_filters %} - -{% block content %} -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} diff --git a/templates/tournament/tournament_detail.html b/templates/tournament/tournament_detail.html deleted file mode 100644 index 2c3b617..0000000 --- a/templates/tournament/tournament_detail.html +++ /dev/null @@ -1,67 +0,0 @@ -{% extends "base.html" %} - -{% load getconfig i18n django_tables2 %} - -{% block content %} -
-
-

{{ title }}

-
-
-
-
{% trans 'organizers'|capfirst %}
-
{% autoescape off %}{{ tournament.linked_organizers|join:", " }}{% endautoescape %}
- -
{% trans 'size'|capfirst %}
-
{{ tournament.size }}
- -
{% trans 'place'|capfirst %}
-
{{ tournament.place }}
- -
{% trans 'price'|capfirst %}
-
{% if tournament.price %}{{ tournament.price }} €{% else %}{% trans "Free" %}{% endif %}
- -
{% trans 'dates'|capfirst %}
-
{% trans "From" %} {{ tournament.date_start }} {% trans "to" %} {{ tournament.date_end }}
- -
{% trans 'date of registration closing'|capfirst %}
-
{{ tournament.date_inscription }}
- -
{% trans 'date of maximal solution submission'|capfirst %}
-
{{ tournament.date_solutions }}
- -
{% trans 'date of maximal syntheses submission for the first round'|capfirst %}
-
{{ tournament.date_syntheses }}
- -
{% trans 'date when solutions of round 2 are available'|capfirst %}
-
{{ tournament.date_solutions_2 }}
- -
{% trans 'date of maximal syntheses submission for the second round'|capfirst %}
-
{{ tournament.date_syntheses_2 }}
- -
{% trans 'description'|capfirst %}
-
{{ tournament.description }}
-
- - {% if user.is_authenticated and user.admin %} - - {% endif %} -
- - {% if user.admin or user in tournament.organizers.all %} - - {% endif %} -
- -
- -

{% trans "Teams" %}

-
- {% render_table teams %} -
-{% endblock %} diff --git a/templates/tournament/tournament_form.html b/templates/tournament/tournament_form.html deleted file mode 100644 index 21daee8..0000000 --- a/templates/tournament/tournament_form.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% load i18n crispy_forms_filters %} - -{% block content %} -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} diff --git a/templates/tournament/tournament_list.html b/templates/tournament/tournament_list.html deleted file mode 100644 index 6a3479a..0000000 --- a/templates/tournament/tournament_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "base.html" %} - -{% load django_tables2 getconfig i18n %} - -{% block content %} - {% if user.is_authenticated and user.admin %} - - {% endif %} - {% render_table table %} - {% if user.is_authenticated and user.admin %} -
- {% trans "Add a tournament" %} - {% endif %} -{% endblock %} diff --git a/tfjm.cron b/tfjm.cron deleted file mode 100644 index 113ed0d..0000000 --- a/tfjm.cron +++ /dev/null @@ -1,5 +0,0 @@ -# m h dom mon dow user command -# Envoyer les mails en attente - * * * * * root cd /code && python manage.py send_mail -c 1 - * * * * * root cd /code && python manage.py retry_deferred -c 1 - 00 0 * * * root cd /code && python manage.py purge_mail_log 7 -c 1 diff --git a/tfjm/__init__.py b/tfjm/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tfjm/asgi.py b/tfjm/asgi.py deleted file mode 100644 index a75729d..0000000 --- a/tfjm/asgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -ASGI config for tfjm project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ -""" - -import os - -from django.core.asgi import get_asgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings') - -application = get_asgi_application() diff --git a/tfjm/inputs.py b/tfjm/inputs.py deleted file mode 100644 index 67838fc..0000000 --- a/tfjm/inputs.py +++ /dev/null @@ -1,322 +0,0 @@ -from json import dumps as json_dumps - -from django.forms.widgets import DateTimeBaseInput, NumberInput, TextInput, Widget - - -class AmountInput(NumberInput): - """ - This input type lets the user type amounts in euros, but forms receive data in cents - """ - template_name = "amount_input.html" - - def format_value(self, value): - return None if value is None or value == "" else "{:.02f}".format(int(value) / 100, ) - - def value_from_datadict(self, data, files, name): - val = super().value_from_datadict(data, files, name) - return str(int(100 * float(val))) if val else val - - -class Autocomplete(TextInput): - template_name = "autocomplete_model.html" - - def __init__(self, model, attrs=None): - super().__init__(attrs) - - self.model = model - self.model_pk = None - - class Media: - """JS/CSS resources needed to render the date-picker calendar.""" - - js = ('js/autocomplete_model.js', ) - - def format_value(self, value): - if value: - self.attrs["model_pk"] = int(value) - return str(self.model.objects.get(pk=int(value))) - return "" - - -class ColorWidget(Widget): - """ - Pulled from django-colorfield. - Select a color. - """ - template_name = 'colorfield/color.html' - - class Media: - js = [ - 'colorfield/jscolor/jscolor.min.js', - 'colorfield/colorfield.js', - ] - - def format_value(self, value): - if value is None: - value = 0xFFFFFF - return "#{:06X}".format(value) - - def value_from_datadict(self, data, files, name): - val = super().value_from_datadict(data, files, name) - return int(val[1:], 16) - - -""" -The remaining of this file comes from the project `django-bootstrap-datepicker-plus` available on Github: -https://github.com/monim67/django-bootstrap-datepicker-plus -This is distributed under Apache License 2.0. - -This adds datetime pickers with bootstrap. -""" - -"""Contains Base Date-Picker input class for widgets of this package.""" - - -class DatePickerDictionary: - """Keeps track of all date-picker input classes.""" - - _i = 0 - items = dict() - - @classmethod - def generate_id(cls): - """Return a unique ID for each date-picker input class.""" - cls._i += 1 - return 'dp_%s' % cls._i - - -class BasePickerInput(DateTimeBaseInput): - """Base Date-Picker input class for widgets of this package.""" - - template_name = 'bootstrap_datepicker_plus/date_picker.html' - picker_type = 'DATE' - format = '%Y-%m-%d' - config = {} - _default_config = { - 'id': None, - 'picker_type': None, - 'linked_to': None, - 'options': {} # final merged options - } - options = {} # options extended by user - options_param = {} # options passed as parameter - _default_options = { - 'showClose': True, - 'showClear': True, - 'showTodayButton': True, - "locale": "fr", - } - - # source: https://github.com/tutorcruncher/django-bootstrap3-datetimepicker - # file: /blob/31fbb09/bootstrap3_datetime/widgets.py#L33 - format_map = ( - ('DDD', r'%j'), - ('DD', r'%d'), - ('MMMM', r'%B'), - ('MMM', r'%b'), - ('MM', r'%m'), - ('YYYY', r'%Y'), - ('YY', r'%y'), - ('HH', r'%H'), - ('hh', r'%I'), - ('mm', r'%M'), - ('ss', r'%S'), - ('a', r'%p'), - ('ZZ', r'%z'), - ) - - class Media: - """JS/CSS resources needed to render the date-picker calendar.""" - - js = ( - 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/' - 'moment-with-locales.min.js', - 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/' - '4.17.47/js/bootstrap-datetimepicker.min.js', - 'bootstrap_datepicker_plus/js/datepicker-widget.js' - ) - css = {'all': ( - 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/' - '4.17.47/css/bootstrap-datetimepicker.css', - 'bootstrap_datepicker_plus/css/datepicker-widget.css' - ), } - - @classmethod - def format_py2js(cls, datetime_format): - """Convert python datetime format to moment datetime format.""" - for js_format, py_format in cls.format_map: - datetime_format = datetime_format.replace(py_format, js_format) - return datetime_format - - @classmethod - def format_js2py(cls, datetime_format): - """Convert moment datetime format to python datetime format.""" - for js_format, py_format in cls.format_map: - datetime_format = datetime_format.replace(js_format, py_format) - return datetime_format - - def __init__(self, attrs=None, format=None, options=None): - """Initialize the Date-picker widget.""" - self.format_param = format - self.options_param = options if options else {} - self.config = self._default_config.copy() - self.config['id'] = DatePickerDictionary.generate_id() - self.config['picker_type'] = self.picker_type - self.config['options'] = self._calculate_options() - attrs = attrs if attrs else {} - if 'class' not in attrs: - attrs['class'] = 'form-control' - super().__init__(attrs, self._calculate_format()) - - def _calculate_options(self): - """Calculate and Return the options.""" - _options = self._default_options.copy() - _options.update(self.options) - if self.options_param: - _options.update(self.options_param) - return _options - - def _calculate_format(self): - """Calculate and Return the datetime format.""" - _format = self.format_param if self.format_param else self.format - if self.config['options'].get('format'): - _format = self.format_js2py(self.config['options'].get('format')) - else: - self.config['options']['format'] = self.format_py2js(_format) - return _format - - def get_context(self, name, value, attrs): - """Return widget context dictionary.""" - context = super().get_context( - name, value, attrs) - context['widget']['attrs']['dp_config'] = json_dumps(self.config) - return context - - def start_of(self, event_id): - """ - Set Date-Picker as the start-date of a date-range. - - Args: - - event_id (string): User-defined unique id for linking two fields - """ - DatePickerDictionary.items[str(event_id)] = self - return self - - def end_of(self, event_id, import_options=True): - """ - Set Date-Picker as the end-date of a date-range. - - Args: - - event_id (string): User-defined unique id for linking two fields - - import_options (bool): inherit options from start-date input, - default: TRUE - """ - event_id = str(event_id) - if event_id in DatePickerDictionary.items: - linked_picker = DatePickerDictionary.items[event_id] - self.config['linked_to'] = linked_picker.config['id'] - if import_options: - backup_moment_format = self.config['options']['format'] - self.config['options'].update(linked_picker.config['options']) - self.config['options'].update(self.options_param) - if self.format_param or 'format' in self.options_param: - self.config['options']['format'] = backup_moment_format - else: - self.format = linked_picker.format - # Setting useCurrent is necessary, see following issue - # https://github.com/Eonasdan/bootstrap-datetimepicker/issues/1075 - self.config['options']['useCurrent'] = False - self._link_to(linked_picker) - else: - raise KeyError( - 'start-date not specified for event_id "%s"' % event_id) - return self - - def _link_to(self, linked_picker): - """ - Executed when two date-inputs are linked together. - - This method for sub-classes to override to customize the linking. - """ - pass - - -class DatePickerInput(BasePickerInput): - """ - Widget to display a Date-Picker Calendar on a DateField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'DATE' - format = '%Y-%m-%d' - format_key = 'DATE_INPUT_FORMATS' - - -class TimePickerInput(BasePickerInput): - """ - Widget to display a Time-Picker Calendar on a TimeField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'TIME' - format = '%H:%M' - format_key = 'TIME_INPUT_FORMATS' - template_name = 'bootstrap_datepicker_plus/time_picker.html' - - -class DateTimePickerInput(BasePickerInput): - """ - Widget to display a DateTime-Picker Calendar on a DateTimeField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'DATETIME' - format = '%Y-%m-%d %H:%M' - format_key = 'DATETIME_INPUT_FORMATS' - - -class MonthPickerInput(BasePickerInput): - """ - Widget to display a Month-Picker Calendar on a DateField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'MONTH' - format = '01/%m/%Y' - format_key = 'DATE_INPUT_FORMATS' - - -class YearPickerInput(BasePickerInput): - """ - Widget to display a Year-Picker Calendar on a DateField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'YEAR' - format = '01/01/%Y' - format_key = 'DATE_INPUT_FORMATS' - - def _link_to(self, linked_picker): - """Customize the options when linked with other date-time input""" - yformat = self.config['options']['format'].replace('-01-01', '-12-31') - self.config['options']['format'] = yformat diff --git a/tfjm/middlewares.py b/tfjm/middlewares.py deleted file mode 100644 index c5b8987..0000000 --- a/tfjm/middlewares.py +++ /dev/null @@ -1,118 +0,0 @@ -from django.conf import settings -from django.contrib.auth.models import AnonymousUser - -from threading import local - -from django.contrib.sessions.backends.db import SessionStore - -from member.models import TFJMUser -from tournament.models import Pool - -USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user') -SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session') -IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip') - -_thread_locals = local() - - -def _set_current_user_and_ip(user=None, session=None, ip=None): - setattr(_thread_locals, USER_ATTR_NAME, user) - setattr(_thread_locals, SESSION_ATTR_NAME, session) - setattr(_thread_locals, IP_ATTR_NAME, ip) - - -def get_current_user() -> TFJMUser: - return getattr(_thread_locals, USER_ATTR_NAME, None) - - -def get_current_session() -> SessionStore: - return getattr(_thread_locals, SESSION_ATTR_NAME, None) - - -def get_current_ip() -> str: - return getattr(_thread_locals, IP_ATTR_NAME, None) - - -def get_current_authenticated_user(): - current_user = get_current_user() - if isinstance(current_user, AnonymousUser): - return None - return current_user - - -class SessionMiddleware(object): - """ - This middleware get the current user with his or her IP address on each request. - """ - - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - if "_fake_user_id" in request.session: - request.user = TFJMUser.objects.get(pk=request.session["_fake_user_id"]) - - user = request.user - if 'HTTP_X_FORWARDED_FOR' in request.META: - ip = request.META.get('HTTP_X_FORWARDED_FOR') - else: - ip = request.META.get('REMOTE_ADDR') - - _set_current_user_and_ip(user, request.session, ip) - response = self.get_response(request) - _set_current_user_and_ip(None, None, None) - - return response - - -class ExtraAccessMiddleware(object): - """ - This middleware allows some non authenticated people to access to pool data. - """ - - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - if "extra_access_token" in request.GET: - request.session["extra_access_token"] = request.GET["extra_access_token"] - if request.user.is_authenticated: - pool = Pool.objects.filter(extra_access_token=request.GET["extra_access_token"]) - if pool.exists(): - pool = pool.get() - pool.juries.add(request.user) - pool.save() - else: - request.session.setdefault("extra_access_token", "") - return self.get_response(request) - - -class TurbolinksMiddleware(object): - """ - Send the `Turbolinks-Location` header in response to a visit that was redirected, - and Turbolinks will replace the browser's topmost history entry. - """ - - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - response = self.get_response(request) - - is_turbolinks = request.META.get('HTTP_TURBOLINKS_REFERRER') - is_response_redirect = response.has_header('Location') - - if is_turbolinks: - if is_response_redirect: - location = response['Location'] - prev_location = request.session.pop('_turbolinks_redirect_to', None) - if prev_location is not None: - # relative subsequent redirect - if location.startswith('.'): - location = prev_location.split('?')[0] + location - request.session['_turbolinks_redirect_to'] = location - else: - if request.session.get('_turbolinks_redirect_to'): - location = request.session.pop('_turbolinks_redirect_to') - response['Turbolinks-Location'] = location - return response diff --git a/tfjm/settings.py b/tfjm/settings.py deleted file mode 100644 index ae7f788..0000000 --- a/tfjm/settings.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -Django settings for tfjm project. - -Generated by 'django-admin startproject' using Django 3.0.5. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.0/ref/settings/ -""" - -import os -import sys - -from django.utils.translation import gettext_lazy as _ - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -APPS_DIR = os.path.realpath(os.path.join(BASE_DIR, "apps")) -sys.path.append(APPS_DIR) - -ADMINS = [("Yohann D'ANELLO", "yohann.danello@animath.fr")] - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '6$wl1=ehfoiymin3m3i-wyx5d3t=1h7g4(j2izn*my)*yiq#he' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -SITE_ID = 1 - -ALLOWED_HOSTS = ['*'] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.forms', - - 'bootstrap_datepicker_plus', - 'crispy_forms', - 'django_extensions', - 'django_tables2', - 'mailer', - 'polymorphic', - 'rest_framework', - 'rest_framework.authtoken', - - 'member', - 'tournament', -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.contrib.sites.middleware.CurrentSiteMiddleware', - 'tfjm.middlewares.SessionMiddleware', - 'tfjm.middlewares.ExtraAccessMiddleware', - 'tfjm.middlewares.TurbolinksMiddleware', -] - -ROOT_URLCONF = 'tfjm.urls' - -LOGIN_REDIRECT_URL = "index" - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')] - , - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' - -WSGI_APPLICATION = 'tfjm.wsgi.application' - - -# Password validation -# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - -AUTH_USER_MODEL = 'member.TFJMUser' - -PASSWORD_HASHERS = [ - 'django.contrib.auth.hashers.PBKDF2PasswordHasher', - 'django.contrib.auth.hashers.BCryptPasswordHasher', -] - -REST_FRAMEWORK = { - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAdminUser' - ], - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.SessionAuthentication', - 'rest_framework.authentication.TokenAuthentication', - ], - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 50, -} - -# Internationalization -# https://docs.djangoproject.com/en/3.0/topics/i18n/ - -LANGUAGE_CODE = 'en' - -LANGUAGES = [ - ('en', _('English')), - ('fr', _('French')), -] - -TIME_ZONE = 'Europe/Paris' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - -LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")] - -FIXTURE_DIRS = [os.path.join(BASE_DIR, "tfjm/fixtures")] - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.0/howto/static-files/ - -STATIC_URL = '/static/' - -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "tfjm/static"), -] - -STATIC_ROOT = os.path.join(BASE_DIR, "static") - -MEDIA_URL = '/media/' - -MEDIA_ROOT = os.path.join(BASE_DIR, "media") - -CRISPY_TEMPLATE_PACK = 'bootstrap4' - -DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html' - -_db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower() - -if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2', - 'NAME': os.environ.get('DJANGO_DB_NAME', 'tfjm'), - 'USER': os.environ.get('DJANGO_DB_USER', 'tfjm'), - 'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'), - 'HOST': os.environ.get('DJANGO_DB_HOST', 'localhost'), - 'PORT': os.environ.get('DJANGO_DB_PORT', ''), # Use default port - } - } -else: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, os.getenv('DJANGO_DB_HOST', 'db.sqlite3')), - } - } - -if os.getenv("TFJM_STAGE", "dev") == "prod": - from .settings_prod import * -else: - from .settings_dev import * diff --git a/tfjm/settings_dev.py b/tfjm/settings_dev.py deleted file mode 100644 index a52990e..0000000 --- a/tfjm/settings_dev.py +++ /dev/null @@ -1,5 +0,0 @@ -# Database -# https://docs.djangoproject.com/en/3.0/ref/settings/#databases - -EMAIL_BACKEND = 'mailer.backend.DbBackend' -MAILER_EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/tfjm/settings_prod.py b/tfjm/settings_prod.py deleted file mode 100644 index 3d6a742..0000000 --- a/tfjm/settings_prod.py +++ /dev/null @@ -1,30 +0,0 @@ -import os - -# Break it, fix it! -DEBUG = False - -# Mandatory ! -ALLOWED_HOSTS = ['inscription.tfjm.org', 'plateforme.tfjm.org'] - -SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS') - -# Emails -EMAIL_BACKEND = 'mailer.backend.DbBackend' -MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_USE_SSL = True -EMAIL_HOST = os.getenv("SMTP_HOST") -EMAIL_PORT = os.getenv("SMTP_PORT") -EMAIL_HOST_USER = os.getenv("SMTP_HOST_USER") -EMAIL_HOST_PASSWORD = os.getenv("SMTP_HOST_PASSWORD") - -DEFAULT_FROM_EMAIL = os.getenv('FROM_EMAIL', 'Contact TFJM² ') -SERVER_EMAIL = os.getenv('SERVER_EMAIL', 'contact@tfjm.org') - -# Security settings -SECURE_CONTENT_TYPE_NOSNIFF = False -SECURE_BROWSER_XSS_FILTER = False -SESSION_COOKIE_SECURE = False -CSRF_COOKIE_SECURE = False -CSRF_COOKIE_HTTPONLY = False -X_FRAME_OPTIONS = 'DENY' -SESSION_COOKIE_AGE = 60 * 60 * 3 diff --git a/tfjm/static/Autorisation_droit_image_majeur.tex b/tfjm/static/Autorisation_droit_image_majeur.tex deleted file mode 100644 index 7cb1727..0000000 --- a/tfjm/static/Autorisation_droit_image_majeur.tex +++ /dev/null @@ -1,113 +0,0 @@ -\documentclass[a4paper,french,11pt]{article} - -\usepackage[T1]{fontenc} -\usepackage[utf8]{inputenc} -\usepackage{lmodern} -\usepackage[frenchb]{babel} - -\usepackage{fancyhdr} -\usepackage{graphicx} -\usepackage{amsmath} -\usepackage{amssymb} -%\usepackage{anyfontsize} -\usepackage{fancybox} -\usepackage{eso-pic,graphicx} -\usepackage{xcolor} - - -% Specials -\newcommand{\writingsep}{\vrule height 4ex width 0pt} - -% Page formating -\hoffset -1in -\voffset -1in -\textwidth 180 mm -\textheight 250 mm -\oddsidemargin 15mm -\evensidemargin 15mm -\pagestyle{fancy} - -% Headers and footers -\fancyfoot{} -\lhead{} -\rhead{} -\renewcommand{\headrulewidth}{0pt} -\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018} -\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.} - -\begin{document} - -\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}} - -\vfill - -\begin{center} - - -\LARGE -Autorisation d'enregistrement et de diffusion de l'image ({TOURNAMENT_NAME}) -\end{center} -\normalsize - - -\thispagestyle{empty} - -\bigskip - - - -Je soussign\'e {PARTICIPANT_NAME}\\ -demeurant au {ADDRESS} - -\medskip -Cochez la/les cases correspondantes.\\ -\medskip - - \fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ du {START_DATE} au {END_DATE} {YEAR} à : {PLACE}, \`a me photographier ou \`a me filmer et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion sur son site et sur les sites partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit d’utiliser mon image sur tous ses supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente, cessionnaire des droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\ - -\medskip -Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la publication et la diffusion de l'image ainsi que des commentaires l'accompagnant ne portent pas atteinte \`a la vie priv\'ee, \`a la dignit\'e et \`a la r\'eputation de la personne photographiée.\\ - -\medskip - \fbox{\textcolor{white}{A}} Autorise la diffusion dans les medias (Presse, T\'el\'evision, Internet) de photographies prises \`a l'occasion d’une \'eventuelle m\'ediatisation de cet événement.\\ - - \medskip - -Conform\'ement \`a la loi informatique et libert\'es du 6 janvier 1978, vous disposez d'un droit de libre acc\`es, de rectification, de modification et de suppression des donn\'ees qui vous concernent. -Cette autorisation est donc r\'evocable \`a tout moment sur volont\'e express\'ement manifest\'ee par lettre recommand\'ee avec accus\'e de r\'eception adress\'ee \`a Animath, IHP, 11 rue Pierre et Marie Curie, 75231 Paris cedex 05.\\ - -\medskip - \fbox{\textcolor{white}{A}} Autorise Animath à conserver mes données personnelles, dans le cadre défini par la loi n 78-17 du 6 janvier 1978 relative à l'informatique, aux fichiers et aux libertés et les textes la modifiant, pendant une durée de quatre ans à compter de ma dernière participation à un événement organisé par Animath.\\ - - \medskip - \fbox{\textcolor{white}{A}} J'accepte d'être tenu informé d'autres activités organisées par l'association et ses partenaires. - -\bigskip - -Signature pr\'ec\'ed\'ee de la mention \og lu et approuv\'e \fg{} - -\medskip - - - -\begin{minipage}[c]{0.5\textwidth} - -\underline{L'\'el\`eve :}\\ - -Fait \`a :\\ -le -\end{minipage} - - -\vfill -\vfill -\begin{minipage}[c]{0.5\textwidth} -\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018 -\end{minipage} -\begin{minipage}[c]{0.5\textwidth} -\footnotesize -\begin{flushright} -Association agréée par\\le Ministère de l'éducation nationale. -\end{flushright} -\end{minipage} -\end{document} diff --git a/tfjm/static/Autorisation_droit_image_mineur.tex b/tfjm/static/Autorisation_droit_image_mineur.tex deleted file mode 100644 index 4f14a43..0000000 --- a/tfjm/static/Autorisation_droit_image_mineur.tex +++ /dev/null @@ -1,122 +0,0 @@ -\documentclass[a4paper,french,11pt]{article} - -\usepackage[T1]{fontenc} -\usepackage[utf8]{inputenc} -\usepackage{lmodern} -\usepackage[frenchb]{babel} - -\usepackage{fancyhdr} -\usepackage{graphicx} -\usepackage{amsmath} -\usepackage{amssymb} -%\usepackage{anyfontsize} -\usepackage{fancybox} -\usepackage{eso-pic,graphicx} -\usepackage{xcolor} - - -% Specials -\newcommand{\writingsep}{\vrule height 4ex width 0pt} - -% Page formating -\hoffset -1in -\voffset -1in -\textwidth 180 mm -\textheight 250 mm -\oddsidemargin 15mm -\evensidemargin 15mm -\pagestyle{fancy} - -% Headers and footers -\fancyfoot{} -\lhead{} -\rhead{} -\renewcommand{\headrulewidth}{0pt} -\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018} -\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.} - -\begin{document} - -\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}} - -\vfill - -\begin{center} - - -\LARGE -Autorisation d'enregistrement et de diffusion de l'image -({TOURNAMENT_NAME}) -\end{center} -\normalsize - - -\thispagestyle{empty} - -\bigskip - - - -Je soussign\'e \dotfill (p\`ere, m\`ere, responsable l\'egal) \\ -agissant en qualit\'e de repr\'esentant de {PARTICIPANT_NAME}\\ -demeurant au {ADDRESS} - -\medskip -Cochez la/les cases correspondantes.\\ -\medskip - - \fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ du {START_DATE} au {END_DATE} {YEAR} à : {PLACE}, \`a photographier ou \`a filmer l'enfant et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion sur son site et sur les sites partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit d’utiliser l'image de l'enfant sur tous ses supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente, cessionnaire des droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\ - -\medskip -Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la publication et la diffusion de l'image de l'enfant ainsi que des commentaires l'accompagnant ne portent pas atteinte \`a la vie priv\'ee, \`a la dignit\'e et \`a la r\'eputation de l’enfant.\\ - -\medskip - \fbox{\textcolor{white}{A}} Autorise la diffusion dans les medias (Presse, T\'el\'evision, Internet) de photographies de mon enfant prises \`a l'occasion d’une \'eventuelle m\'ediatisation de cet événement.\\ - - \medskip - -Conform\'ement \`a la loi informatique et libert\'es du 6 janvier 1978, vous disposez d'un droit de libre acc\`es, de rectification, de modification et de suppression des donn\'ees qui vous concernent. -Cette autorisation est donc r\'evocable \`a tout moment sur volont\'e express\'ement manifest\'ee par lettre recommand\'ee avec accus\'e de r\'eception adress\'ee \`a Animath, IHP, 11 rue Pierre et Marie Curie, 75231 Paris cedex 05.\\ - -\medskip - \fbox{\textcolor{white}{A}} Autorise Animath à conserver mes données personnelles, dans le cadre défini par la loi n 78-17 du 6 janvier 1978 relative à l'informatique, aux fichiers et aux libertés et les textes la modifiant, pendant une durée de quatre ans à compter de ma dernière participation à un événement organisé par Animath.\\ - - \medskip - \fbox{\textcolor{white}{A}} J'accepte d'être tenu informé d'autres activités organisées par l'association et ses partenaires. - - \bigskip - -Signatures pr\'ec\'ed\'ees de la mention \og lu et approuv\'e \fg{} - -\medskip - - -\begin{minipage}[c]{0.5\textwidth} - -\underline{Le responsable l\'egal :}\\ - -Fait \`a :\\ -le : - -\end{minipage} -\begin{minipage}[c]{0.5\textwidth} - -\underline{L'\'el\`eve :}\\ - -Fait \`a :\\ -le -\end{minipage} - - -\vfill -\vfill -\begin{minipage}[c]{0.5\textwidth} -\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018 -\end{minipage} -\begin{minipage}[c]{0.5\textwidth} -\footnotesize -\begin{flushright} -Association agréée par\\le Ministère de l'éducation nationale. -\end{flushright} -\end{minipage} -\end{document} diff --git a/tfjm/static/Autorisation_parentale.tex b/tfjm/static/Autorisation_parentale.tex deleted file mode 100644 index 6c56ac4..0000000 --- a/tfjm/static/Autorisation_parentale.tex +++ /dev/null @@ -1,66 +0,0 @@ -\documentclass[a4paper,french,11pt]{article} - -\usepackage[T1]{fontenc} -\usepackage[utf8]{inputenc} -\usepackage{lmodern} -\usepackage[french]{babel} - -\usepackage{fancyhdr} -\usepackage{graphicx} -\usepackage{amsmath} -\usepackage{amssymb} -%\usepackage{anyfontsize} -\usepackage{fancybox} -\usepackage{eso-pic,graphicx} -\usepackage{xcolor} - - -% Specials -\newcommand{\writingsep}{\vrule height 4ex width 0pt} - -% Page formating -\hoffset -1in -\voffset -1in -\textwidth 180 mm -\textheight 250 mm -\oddsidemargin 15mm -\evensidemargin 15mm -\pagestyle{fancy} - -% Headers and footers -\fancyfoot{} -\lhead{} -\rhead{} -\renewcommand{\headrulewidth}{0pt} -\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018} -\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.} - -\begin{document} - -\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}} - -\vfill - -\begin{center} -\Large \bf Autorisation parentale pour les mineurs ({TOURNAMENT_NAME}) -\end{center} - -Je soussigné(e) \hrulefill,\\ -responsable légal, demeurant \writingsep\hrulefill\\ -\writingsep\hrulefill,\\ -\writingsep autorise {PARTICIPANT_NAME},\\ -né(e) le {BIRTHDAY}, -à participer au Tournoi Français des Jeunes Mathématiciennes et Mathématiciens ($\mathbb{TFJM}^2$) organisé \`a : {PLACE}, du {START_DATE} au {END_DATE} {YEAR}. - -{PRONOUN} se rendra au lieu indiqu\'e ci-dessus le vendredi matin et quittera les lieux l'après-midi du dimanche par ses propres moyens et sous la responsabilité du représentant légal. - - - -\vspace{8ex} - -Fait à \vrule width 10cm height 0pt depth 0.4pt, le \phantom{232323}/\phantom{XXX}/{YEAR}, - -\vfill -\vfill - -\end{document} diff --git a/tfjm/static/Fiche synthèse.pdf b/tfjm/static/Fiche synthèse.pdf deleted file mode 100644 index af8ed1c..0000000 Binary files a/tfjm/static/Fiche synthèse.pdf and /dev/null differ diff --git a/tfjm/static/Fiche synthèse.tex b/tfjm/static/Fiche synthèse.tex deleted file mode 100644 index bc2daa9..0000000 --- a/tfjm/static/Fiche synthèse.tex +++ /dev/null @@ -1,194 +0,0 @@ -\documentclass{article} - -\usepackage[utf8]{inputenc} -\usepackage[french]{babel} -\usepackage{graphicx} - -\usepackage[left=2cm,right=2cm,top=2cm,bottom=2cm]{geometry} % marges - -\usepackage{amsthm} -\usepackage{amsmath} -\usepackage{amsfonts} -\usepackage{amssymb} -\usepackage{tikz} - -\newcommand{\N}{{\bf N}} -\newcommand{\Z}{{\bf Z}} -\newcommand{\Q}{{\bf Q}} -\newcommand{\R}{{\bf R}} -\newcommand{\C}{{\bf C}} -\newcommand{\A}{{\bf A}} - -\newtheorem{theo}{Théorème} -\newtheorem{theo-defi}[theo]{Théorème-Définition} -\newtheorem{defi}[theo]{Définition} -\newtheorem{lemme}[theo]{Lemme} -\newtheorem{slemme}[theo]{Sous-lemme} -\newtheorem{prop}[theo]{Proposition} -\newtheorem{coro}[theo]{Corollaire} -\newtheorem{conj}[theo]{Conjecture} - -\title{Note de synthèse} - -\begin{document} -\pagestyle{empty} - -\begin{center} -\begin{Huge} -$\mathbb{TFJM}^2$ -\end{Huge} - -\bigskip - -\begin{Large} -NOTE DE SYNTHESE -\end{Large} -\end{center} - -Tour \underline{~~~~} poule \underline{~~~~} - -\medskip - -Problème \underline{~~~~} défendu par l'équipe \underline{~~~~~~~~~~~~~~~~~~~~~~~~} - -\medskip - -Synthèse par l'équipe \underline{~~~~~~~~~~~~~~~~~~~~~~~~} dans le rôle de : ~ $\square$ Opposant ~ $\square$ Rapporteur - -\section*{Questions traitées} - -\begin{tabular}{r c l} - \begin{tabular}{|c|c|c|c|c|c|} - \hline - Question ~ & ER & ~PR~ & QE & NT \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - \end{tabular} -& ~~ & - \begin{tabular}{|c|c|c|c|c|c|} - \hline - Question ~ & ER & ~PR~ & QE & NT \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - & & & & \\ - \hline - \end{tabular} \\ - - & & \\ - -ER : entièrement résolue & & PR : partiellement résolue \\ - -\smallskip - -QE : quelques éléments de réponse & & NT : non traitée -\end{tabular} - -~ - -\smallskip - -Remarque : il est possible de cocher entre les cases pour un cas intermédiaire. - -\section*{Evaluation qualitative de la solution} - -Donnez votre avis concernant la solution. Mettez notamment en valeur les points positifs (des idées -importantes, originales, etc.) et précisez ce qui aurait pu améliorer la solution. - -\vfill - -\textbf{Evaluation générale :} ~ $\square$ Excellente ~ $\square$ Bonne ~ $\square$ Suffisante ~ $\square$ Passable - -\newpage - -\section*{Erreurs et imprécisions} - -Listez ci-dessous les cinq erreurs et/ou imprécisions les plus importantes selon vous, par ordre d'importance, en précisant la -question concernée, la page, le paragraphe et le type de remarque. - -\bigskip - -1. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~} - -$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~} - -Description : - -\vfill - -2. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~} - -$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~} - -Description : - -\vfill - -3. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~} - -$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~} - -Description : - -\vfill - -4. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~} - -$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~} - -Description : - -\vfill - -5. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~} - -$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~} - -Description : - -\vfill - -\section*{Remarques formelles (facultatif)} - -Donnez votre avis concernant la présentation de la solution (lisibilité, etc.). - -\vfill - - - -\end{document} diff --git a/tfjm/static/Fiche_sanitaire.pdf b/tfjm/static/Fiche_sanitaire.pdf deleted file mode 100644 index b828b9d..0000000 Binary files a/tfjm/static/Fiche_sanitaire.pdf and /dev/null differ diff --git a/tfjm/static/Instructions.tex b/tfjm/static/Instructions.tex deleted file mode 100644 index da293ef..0000000 --- a/tfjm/static/Instructions.tex +++ /dev/null @@ -1,88 +0,0 @@ -\documentclass[a4paper,french,11pt]{article} - -\usepackage[T1]{fontenc} -\usepackage[utf8]{inputenc} -\usepackage{lmodern} -\usepackage[frenchb]{babel} - -\usepackage{fancyhdr} -\usepackage{graphicx} -\usepackage{amsmath} -\usepackage{amssymb} -%\usepackage{anyfontsize} -\usepackage{fancybox} -\usepackage{eso-pic,graphicx} -\usepackage{xcolor} -\usepackage{hyperref} - - -% Specials -\newcommand{\writingsep}{\vrule height 4ex width 0pt} - -% Page formating -\hoffset -1in -\voffset -1in -\textwidth 180 mm -\textheight 250 mm -\oddsidemargin 15mm -\evensidemargin 15mm -\pagestyle{fancy} - -% Headers and footers -\fancyfoot{} -\lhead{} -\rhead{} -\renewcommand{\headrulewidth}{0pt} -\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018} -\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.} - -\begin{document} - -\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{50pt}{50pt}{$\mathbb{TFJM}^2$}} - - - -\begin{center} -\Large \bf Instructions ({TOURNAMENT_NAME}) -\end{center} - -\section{Documents} -\subsection{Autorisation parentale} -Elle est nécessaire si l'élève est mineur au moment du tournoi (y compris si son anniversaire est pendant le tournoi). - -\subsection{Autorisation de prise de vue} -Si l'élève est mineur \textbf{au moment de la signature}, il convient de remplir l'autorisation pour les mineurs. En revanche, s'il est majeur \textbf{au moment de la signature}, il convient de remplir la fiche pour majeur. - -\subsection{Fiche sanitaire} -Elle est nécessaire si l'élève est mineur au moment du tournoi (y compris si son anniversaire est pendant le tournoi). - - -\section{Paiement} - -\subsection{Montant} -Les frais d'inscription sont fixés à {PRICE} euros. Vous devez vous en acquitter \textbf{avant le {END_PAYMENT_DATE} {YEAR}}. Si l'élève est boursier, il en est dispensé, vous devez alors fournir une copie de sa notification de bourse directement sur la plateforme \textbf{avant le {END_PAYMENT_DATE} {YEAR}}. - -\subsection{Procédure} - -Si le paiement de plusieurs élèves est fait en une seule opération, merci de contacter \href{mailto: contact@tfjm.org}{contact@tfjm.org} \textbf{avant le paiement} pour garantir l'identification de ce dernier - -\subsubsection*{Carte bancaire (uniquement les cartes françaises)} -Le paiement s'effectue en ligne via la plateforme à l'adresse : \url{https://www.helloasso.com/associations/animath/evenements/tfjm-2020} - -Vous devez impérativement indiquer dans le champ "Référence" la mention "TFJMpu" suivie des noms et prénoms \textbf{de l'élève}. - -\subsubsection*{Virement} -\textbf{Si vous ne pouvez pas utiliser le paiement par carte}, vous pouvez faire un virement sur le compte ci-dessous en indiquant bien dans le champ "motif" (ou autre champ propre à votre banque dont le contenu est communiqué au destinataire) la mention "TFJMpu" suivie des noms et prénoms \textbf{de l'élève}. - -IBAN FR76 1027 8065 0000 0206 4290 127 - -BIC CMCIFR2A - -\subsubsection*{Autre} - -Si aucune de ces procédures n'est possible pour vous, envoyez un mail à \href{mailto: contact@tfjm.org}{contact@tfjm.org} pour que nous trouvions une solution à vos difficultés. - - - - -\end{document} diff --git a/tfjm/static/favicon.ico b/tfjm/static/favicon.ico deleted file mode 100644 index 97757d3..0000000 Binary files a/tfjm/static/favicon.ico and /dev/null differ diff --git a/tfjm/static/logo.svg b/tfjm/static/logo.svg deleted file mode 100644 index 699316b..0000000 --- a/tfjm/static/logo.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/tfjm/static/logo_animath.png b/tfjm/static/logo_animath.png deleted file mode 100644 index da4533e..0000000 Binary files a/tfjm/static/logo_animath.png and /dev/null differ diff --git a/tfjm/static/style.css b/tfjm/static/style.css deleted file mode 100644 index 5c8d3ff..0000000 --- a/tfjm/static/style.css +++ /dev/null @@ -1,47 +0,0 @@ -html, body { - height: 100%; - margin: 0; -} - -:root { - --navbar-height: 32px; -} - -.container { - min-height: 78%; -} - -.inner { - margin: 20px; -} - -.alert { - text-align: justify; -} - - -footer .alert { - text-align: center; -} - -#navbar-logo { - height: var(--navbar-height); - display: block; -} - -ul .deroule { - display: none; - position: absolute; - background: #f8f9fa !important; - list-style-type: none; - padding: 20px; - z-index: 42; -} - -li:hover ul.deroule { - display:block; -} - -a.nav-link:hover { - background-color: #d8d9da; -} diff --git a/tfjm/urls.py b/tfjm/urls.py deleted file mode 100644 index 6e38c02..0000000 --- a/tfjm/urls.py +++ /dev/null @@ -1,54 +0,0 @@ -"""tfjm URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/3.0/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -import re - -from django.conf import settings -from django.contrib import admin -from django.contrib.staticfiles.views import serve -from django.urls import path, include, re_path -from django.views.defaults import bad_request, permission_denied, page_not_found, server_error -from django.views.generic import TemplateView, RedirectView - -from member.views import DocumentView - -urlpatterns = [ - path('', TemplateView.as_view(template_name="index.html"), name="index"), - path('i18n/', include('django.conf.urls.i18n')), - path('admin/doc/', include('django.contrib.admindocs.urls')), - path('admin/', admin.site.urls, name="admin"), - path('accounts/', include('django.contrib.auth.urls')), - - path('member/', include('member.urls')), - path('tournament/', include('tournament.urls')), - - path("media//", DocumentView.as_view(), name="document"), - - path('api/', include('api.urls')), - - # Supporting old paths - path('inscription/', RedirectView.as_view(pattern_name="member:signup")), - path('connexion/', RedirectView.as_view(pattern_name="login")), - path('tournois/', RedirectView.as_view(pattern_name="tournament:list")), - path('mon-compte/', RedirectView.as_view(pattern_name="member:my_account")), - path('mon-equipe/', RedirectView.as_view(pattern_name="member:my_team")), - path('solutions/', RedirectView.as_view(pattern_name="tournament:solutions")), - path('syntheses/', RedirectView.as_view(pattern_name="tournament:syntheses")), -] - -handler400 = bad_request -handler403 = permission_denied -handler404 = page_not_found -handler500 = server_error diff --git a/tfjm/wsgi.py b/tfjm/wsgi.py deleted file mode 100644 index 7fd654c..0000000 --- a/tfjm/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for tfjm project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings') - -application = get_wsgi_application()