From 3d9e7136ac395aed58e7da7f3136a1149fc36148 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 25 May 2020 18:27:07 +0200 Subject: [PATCH] Add extra access to juries --- apps/member/views.py | 52 ++++-- apps/tournament/models.py | 15 ++ apps/tournament/views.py | 67 ++++--- locale/fr/LC_MESSAGES/django.po | 241 ++++++++++++++------------ templates/base.html | 14 ++ templates/tournament/pool_detail.html | 13 +- tfjm/middlewares.py | 16 ++ tfjm/settings.py | 1 + 8 files changed, 264 insertions(+), 155 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index fa8aeeb..ac4b13e 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -1,6 +1,6 @@ import random -from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied from django.db.models import Q @@ -13,7 +13,7 @@ from django.views import View from django.views.generic import CreateView, UpdateView, DetailView, FormView from django_tables2 import SingleTableView from tournament.forms import TeamForm, JoinTeam -from tournament.models import Team, Tournament +from tournament.models import Team, Tournament, Pool from tournament.views import AdminMixin, TeamMixin, OrgaMixin from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm @@ -177,7 +177,7 @@ class MyTeamView(TeamMixin, View): return redirect("tournament:team_detail", pk=request.user.team.pk) -class DocumentView(LoginRequiredMixin, View): +class DocumentView(AccessMixin, View): """ View a PDF document, if we have the right. @@ -194,24 +194,38 @@ class DocumentView(LoginRequiredMixin, View): raise Http404(_("No %(verbose_name)s found matching the query") % {'verbose_name': Document._meta.verbose_name}) - grant = request.user.admin + if request.user.is_authenticated: + grant = request.user.admin - if isinstance(doc, Solution) or isinstance(doc, Synthesis) or isinstance(doc, MotivationLetter): - grant = grant or doc.team == request.user.team or request.user in doc.team.tournament.organizers.all() - grant = grant or (doc.team.selected_for_final and request.user in Tournament.get_final().organizers.all()) + if isinstance(doc, Solution) or isinstance(doc, Synthesis) or isinstance(doc, MotivationLetter): + grant = grant or doc.team == request.user.team or request.user in doc.tournament.organizers.all() - if isinstance(doc, Synthesis) and request.user.organizes: - grant = True - - 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 + 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 diff --git a/apps/tournament/models.py b/apps/tournament/models.py index ae26b7a..ed4a0fb 100644 --- a/apps/tournament/models.py +++ b/apps/tournament/models.py @@ -1,4 +1,5 @@ import os +import random from django.core.mail import send_mail from django.db import models @@ -338,6 +339,13 @@ class Pool(models.Model): 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): """ @@ -361,6 +369,13 @@ class Pool(models.Model): 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") diff --git a/apps/tournament/views.py b/apps/tournament/views.py index 97f877a..450351f 100644 --- a/apps/tournament/views.py +++ b/apps/tournament/views.py @@ -3,7 +3,7 @@ import zipfile from datetime import timedelta from io import BytesIO -from django.contrib.auth.mixins import LoginRequiredMixin +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 @@ -34,13 +34,15 @@ class AdminMixin(LoginRequiredMixin): return super().dispatch(request, *args, **kwargs) -class OrgaMixin(LoginRequiredMixin): +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 or not request.user.organizes: + 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) @@ -247,7 +249,7 @@ class TeamDetailView(LoginRequiredMixin, DetailView): context = super().get_context_data(**kwargs) context["title"] = _("Information about team") - context["ordered_solutions"] = self.object.solutions.order_by('problem').all() + 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 @@ -399,19 +401,22 @@ class SolutionsOrgaListView(OrgaMixin, SingleTableView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["tournaments"] = \ - Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments + 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 not self.request.user.admin: + 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() @@ -529,14 +534,15 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["tournaments"] = \ - Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments + 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 not self.request.user.admin: + 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) @@ -544,11 +550,18 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView): 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(LoginRequiredMixin, SingleTableView): +class PoolListView(SingleTableView): """ View the list of visible pools. Admins see all, juries see their own pools, organizers see the pools of their tournaments. @@ -560,10 +573,13 @@ class PoolListView(LoginRequiredMixin, SingleTableView): def get_queryset(self): qs = super().get_queryset() user = self.request.user - 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) + 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 @@ -581,7 +597,7 @@ class PoolCreateView(AdminMixin, CreateView): return reverse_lazy("tournament:pools") -class PoolDetailView(LoginRequiredMixin, DetailView): +class PoolDetailView(DetailView): """ See the detail of a pool. Teams and juries can download here defended solutions of the pool. @@ -597,10 +613,13 @@ class PoolDetailView(LoginRequiredMixin, DetailView): def get_queryset(self): qs = super().get_queryset() user = self.request.user - 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) + 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): @@ -608,7 +627,8 @@ class PoolDetailView(LoginRequiredMixin, DetailView): pool = self.get_object() if "solutions_zip" in request.POST: - if user.participates and pool.round == 2 and pool.tournament.date_solutions_2 > timezone.now(): + if user.is_authenticated and user.participates and pool.round == 2\ + and pool.tournament.date_solutions_2 > timezone.now(): raise PermissionDenied out = BytesIO() @@ -624,10 +644,7 @@ class PoolDetailView(LoginRequiredMixin, DetailView): .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 user.organizes: - if user.participates and pool.round == 2 and pool.tournament.date_solutions_2 > timezone.now(): - raise PermissionDenied - + elif "syntheses_zip" in request.POST and (not user.is_authenticated or user.organizes): out = BytesIO() zf = zipfile.ZipFile(out, "w") diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index ccc1229..07a9aaa 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: TFJM2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-05-12 18:25+0200\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" @@ -44,11 +44,11 @@ msgstr "Adresse électronique" #: apps/member/models.py:22 msgid "This should be valid and will be controlled." -msgstr "" +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:285 -#: apps/tournament/models.py:385 templates/member/tfjmuser_detail.html:16 +#: 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" @@ -124,7 +124,7 @@ msgstr "téléphone du responsable" msgid "responsible email" msgstr "email du responsable" -#: apps/member/models.py:129 apps/tournament/models.py:44 +#: apps/member/models.py:129 apps/tournament/models.py:45 #: templates/member/tfjmuser_detail.html:67 #: templates/tournament/tournament_detail.html:42 msgid "description" @@ -138,13 +138,13 @@ msgstr "Administrateur" msgid "Organizer" msgstr "Organisateur" -#: apps/member/models.py:144 apps/tournament/models.py:89 -#: apps/tournament/models.py:214 +#: 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:378 +#: apps/tournament/models.py:393 msgid "user" msgstr "utilisateur" @@ -180,7 +180,7 @@ msgstr "Autorisation de droit à l'image" msgid "Sanitary plug" msgstr "Fiche sanitaire" -#: apps/member/models.py:223 apps/tournament/models.py:396 +#: apps/member/models.py:223 apps/tournament/models.py:411 msgid "Scholarship" msgstr "Bourse" @@ -226,7 +226,7 @@ msgstr "solution pour la finale" msgid "solution" msgstr "solution" -#: apps/member/models.py:286 apps/tournament/models.py:324 +#: apps/member/models.py:286 apps/tournament/models.py:325 msgid "solutions" msgstr "solutions" @@ -253,15 +253,15 @@ msgstr "Rapporteur" msgid "source" msgstr "source" -#: apps/member/models.py:320 apps/tournament/models.py:329 +#: 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:330 +#: 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:332 +#: apps/member/models.py:323 apps/tournament/models.py:333 #: templates/tournament/pool_detail.html:18 msgid "round" msgstr "tour" @@ -303,40 +303,45 @@ msgstr "configuration" msgid "configurations" msgstr "configurations" -#: apps/member/views.py:100 apps/member/views.py:140 +#: 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:104 apps/member/views.py:144 +#: 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:148 +#: apps/member/views.py:153 msgid "This team is full of coachs." msgstr "Cette équipe est pleine en encadrants." -#: apps/member/views.py:152 +#: apps/member/views.py:157 msgid "This team is full of participants." msgstr "Cette équipe est pleine en participants." -#: apps/member/views.py:156 +#: 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:220 templates/base.html:81 +#: 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:232 templates/base.html:80 +#: apps/member/views.py:256 templates/base.html:80 msgid "Orphaned profiles" msgstr "Profils orphelins" -#: apps/member/views.py:244 apps/tournament/forms.py:23 templates/base.html:83 +#: 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:134 -#: apps/tournament/models.py:182 apps/tournament/tables.py:110 +#: 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" @@ -400,7 +405,8 @@ msgstr "Problème n°%(problem)d" 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." +"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." @@ -435,16 +441,16 @@ msgstr "Équipe 3" msgid "Problem defended by team 3" msgstr "Problème défendu par l'équipe 3" -#: apps/tournament/models.py:18 apps/tournament/models.py:169 +#: apps/tournament/models.py:19 apps/tournament/models.py:170 #: templates/tournament/team_detail.html:12 msgid "name" msgstr "nom" -#: apps/tournament/models.py:24 templates/tournament/tournament_detail.html:12 +#: apps/tournament/models.py:25 templates/tournament/tournament_detail.html:12 msgid "organizers" msgstr "organisateurs" -#: apps/tournament/models.py:25 +#: apps/tournament/models.py:26 msgid "" "List of all organizers that can see and manipulate data of the tournament " "and the teams." @@ -452,71 +458,71 @@ msgstr "" "Liste des organisateurs qui peuvent manipuler les données du tournoi et des " "équipes." -#: apps/tournament/models.py:29 templates/tournament/tournament_detail.html:15 +#: apps/tournament/models.py:30 templates/tournament/tournament_detail.html:15 msgid "size" msgstr "taille" -#: apps/tournament/models.py:30 +#: 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:35 templates/tournament/tournament_detail.html:18 +#: apps/tournament/models.py:36 templates/tournament/tournament_detail.html:18 msgid "place" msgstr "lieu" -#: apps/tournament/models.py:39 templates/tournament/tournament_detail.html:21 +#: apps/tournament/models.py:40 templates/tournament/tournament_detail.html:21 msgid "price" msgstr "prix" -#: apps/tournament/models.py:40 +#: 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:49 +#: apps/tournament/models.py:50 msgid "date start" msgstr "date de début" -#: apps/tournament/models.py:54 +#: apps/tournament/models.py:55 msgid "date end" msgstr "date de fin" -#: apps/tournament/models.py:59 templates/tournament/tournament_detail.html:27 +#: 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:64 templates/tournament/tournament_detail.html:30 +#: 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:69 templates/tournament/tournament_detail.html:33 +#: 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:74 templates/tournament/tournament_detail.html:36 +#: 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:79 templates/tournament/tournament_detail.html:39 +#: 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:83 +#: apps/tournament/models.py:84 msgid "final tournament" msgstr "finale" -#: apps/tournament/models.py:84 +#: 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:135 +#: apps/tournament/models.py:136 msgid "tournaments" msgstr "tournois" -#: apps/tournament/models.py:174 templates/tournament/team_detail.html:15 +#: apps/tournament/models.py:175 templates/tournament/team_detail.html:15 msgid "trigram" msgstr "trigramme" -#: apps/tournament/models.py:175 +#: apps/tournament/models.py:176 msgid "" "The trigram should be composed of 3 capitalize letters, that is a funny " "acronym for the team." @@ -524,89 +530,97 @@ 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:183 +#: 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:188 +#: apps/tournament/models.py:189 msgid "inscription date" msgstr "date d'inscription" -#: apps/tournament/models.py:194 apps/tournament/models.py:405 +#: apps/tournament/models.py:195 apps/tournament/models.py:420 msgid "Registration not validated" msgstr "Inscription non validée" -#: apps/tournament/models.py:195 apps/tournament/models.py:406 +#: apps/tournament/models.py:196 apps/tournament/models.py:421 msgid "Waiting for validation" msgstr "En attente de validation" -#: apps/tournament/models.py:196 apps/tournament/models.py:407 +#: apps/tournament/models.py:197 apps/tournament/models.py:422 msgid "Registration validated" msgstr "Inscription validée" -#: apps/tournament/models.py:198 apps/tournament/models.py:409 +#: 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:203 +#: apps/tournament/models.py:204 msgid "selected for final" msgstr "sélectionnée pour la finale" -#: apps/tournament/models.py:209 templates/tournament/team_detail.html:18 +#: apps/tournament/models.py:210 templates/tournament/team_detail.html:18 msgid "access code" msgstr "code d'accès" -#: apps/tournament/models.py:286 apps/tournament/models.py:318 +#: apps/tournament/models.py:287 apps/tournament/models.py:319 #: templates/tournament/pool_detail.html:15 msgid "teams" msgstr "équipes" -#: apps/tournament/models.py:338 templates/tournament/pool_detail.html:12 +#: apps/tournament/models.py:339 templates/tournament/pool_detail.html:12 msgid "juries" msgstr "jurys" -#: apps/tournament/models.py:365 +#: 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:366 +#: apps/tournament/models.py:381 msgid "pools" msgstr "poules" -#: apps/tournament/models.py:391 +#: apps/tournament/models.py:406 msgid "Not paid" msgstr "Non payé" -#: apps/tournament/models.py:392 +#: apps/tournament/models.py:407 msgid "Credit card" msgstr "Carte bancaire" -#: apps/tournament/models.py:393 +#: apps/tournament/models.py:408 msgid "Bank check" msgstr "Chèque bancaire" -#: apps/tournament/models.py:394 +#: apps/tournament/models.py:409 msgid "Bank transfer" msgstr "Virement bancaire" -#: apps/tournament/models.py:395 +#: apps/tournament/models.py:410 msgid "Cash" msgstr "Espèces" -#: apps/tournament/models.py:399 +#: apps/tournament/models.py:414 msgid "payment method" msgstr "moyen de paiement" -#: apps/tournament/models.py:413 +#: apps/tournament/models.py:428 msgid "payment" msgstr "paiement" -#: apps/tournament/models.py:414 +#: apps/tournament/models.py:429 msgid "payments" msgstr "paiements" -#: apps/tournament/models.py:417 +#: apps/tournament/models.py:432 #, python-brace-format msgid "Payment of {user}" msgstr "Paiement de {user}" @@ -633,71 +647,71 @@ msgstr "Télécharger" msgid "Problems" msgstr "Problèmes" -#: apps/tournament/views.py:66 +#: apps/tournament/views.py:68 msgid "Tournaments list" msgstr "Liste des tournois" -#: apps/tournament/views.py:89 +#: apps/tournament/views.py:91 msgid "Add tournament" msgstr "Ajouter un tournoi" -#: apps/tournament/views.py:106 +#: apps/tournament/views.py:108 #, python-brace-format msgid "Tournament of {name}" msgstr "Tournoi de {name}" -#: apps/tournament/views.py:140 +#: apps/tournament/views.py:146 msgid "Update tournament" msgstr "Modifier le tournoi" -#: apps/tournament/views.py:187 apps/tournament/views.py:315 +#: 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:243 +#: apps/tournament/views.py:251 msgid "Information about team" msgstr "Informations sur l'équipe" -#: apps/tournament/views.py:258 +#: apps/tournament/views.py:266 msgid "Update team" msgstr "Modifier l'équipe" -#: apps/tournament/views.py:276 +#: apps/tournament/views.py:284 msgid "Add organizer" msgstr "Ajouter un organisateur" -#: apps/tournament/views.py:299 templates/base.html:108 templates/base.html:126 -#: templates/tournament/pool_detail.html:31 +#: 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:339 +#: 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:368 +#: apps/tournament/views.py:376 msgid "All solutions" msgstr "Toutes les solutions" -#: apps/tournament/views.py:387 +#: 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:417 templates/base.html:111 templates/base.html:129 -#: templates/tournament/pool_detail.html:57 +#: 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:433 +#: 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:458 +#: apps/tournament/views.py:473 msgid "" "You can't publish your synthesis anymore for the first round. Deadline: " "{date:%m-%d-%Y %H:%M}." @@ -705,7 +719,7 @@ 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:464 +#: apps/tournament/views.py:479 msgid "" "You can't publish your synthesis anymore for the second round. Deadline: " "{date:%m-%d-%Y %H:%M}." @@ -713,34 +727,34 @@ 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:494 +#: apps/tournament/views.py:509 msgid "All syntheses" msgstr "Toutes les notes de synthèses" -#: apps/tournament/views.py:513 +#: 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:542 templates/base.html:133 +#: apps/tournament/views.py:571 templates/base.html:125 templates/base.html:138 msgid "Pools" msgstr "Poules" -#: apps/tournament/views.py:563 +#: apps/tournament/views.py:594 msgid "Create pool" msgstr "Créer une poule" -#: apps/tournament/views.py:580 +#: apps/tournament/views.py:611 msgid "Pool detail" msgstr "Détails d'une poule" -#: apps/tournament/views.py:609 +#: 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:626 +#: apps/tournament/views.py:658 #, python-brace-format msgid "" "Syntheses of a pool for the round {round} of the tournament {tournament}.zip" @@ -826,38 +840,30 @@ msgstr "Rejoindre une équipe" msgid "My team" msgstr "Mon équipe" -#: templates/base.html:118 -msgid "Add a tournament" -msgstr "Ajouter un tournoi" - -#: templates/base.html:121 -msgid "Add an organizer" -msgstr "Ajouter un organisateur" - -#: templates/base.html:138 +#: templates/base.html:144 msgid "Make a gift" msgstr "Faire un don" -#: templates/base.html:142 +#: templates/base.html:148 msgid "Administration" msgstr "Administration" -#: templates/base.html:149 +#: templates/base.html:155 msgid "Return to admin view" msgstr "Retour à l'interface administrateur" -#: templates/base.html:154 templates/registration/login.html:7 +#: 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:157 templates/registration/signup.html:5 +#: 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:161 +#: templates/base.html:167 msgid "Log out" msgstr "Déconnexion" @@ -867,7 +873,7 @@ msgid "Field filters" msgstr "Filtres" #: templates/django_filters/rest_framework/form.html:5 -#: templates/member/my_account.html:8 templates/tournament/add_organizer.html:9 +#: 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 @@ -876,10 +882,14 @@ msgstr "Filtres" msgid "Submit" msgstr "Envoyer" -#: templates/member/my_account.html:13 +#: 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" @@ -1045,7 +1055,7 @@ 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:74 +#: templates/tournament/pool_detail.html:73 msgid "Download ZIP archive" msgstr "Télécharger l'archive ZIP" @@ -1058,6 +1068,15 @@ msgstr "Le modèle de note de synthèse est disponible ici :" 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" @@ -1210,10 +1229,14 @@ msgstr "Envoyer un mail à toutes les personnes dans une équipe" 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" -#: tfjm/settings.py:146 +#: 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:147 +#: tfjm/settings.py:148 msgid "French" msgstr "Français" diff --git a/templates/base.html b/templates/base.html index 218321b..d52e7be 100644 --- a/templates/base.html +++ b/templates/base.html @@ -125,6 +125,20 @@ {% trans "Pools" %} {% endif %} + + {% if not user.is_authenticated and request.session.extra_access_token %} + {# Juries can access to pool data without logging in. #} + + + + {% endif %} +