Add extra access to juries

This commit is contained in:
Yohann D'ANELLO 2020-05-25 18:27:07 +02:00
parent 522ed088ef
commit 3d9e7136ac
8 changed files with 264 additions and 155 deletions

View File

@ -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,14 +194,11 @@ class DocumentView(LoginRequiredMixin, View):
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) 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, Synthesis) and request.user.organizes:
grant = True
grant = grant or doc.team == request.user.team or request.user in doc.tournament.organizers.all()
if isinstance(doc, Solution):
for pool in doc.pools.all():
@ -212,6 +209,23 @@ class DocumentView(LoginRequiredMixin, View):
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

View File

@ -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")

View File

@ -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,6 +401,7 @@ class SolutionsOrgaListView(OrgaMixin, SingleTableView):
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
@ -406,12 +409,14 @@ class SolutionsOrgaListView(OrgaMixin, SingleTableView):
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,6 +534,7 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView):
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
@ -536,7 +542,7 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView):
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 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 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")

View File

@ -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 <yohann.danello@animath.fr>\n"
"Language-Team: fr <LL@li.org>\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"

View File

@ -125,6 +125,20 @@
<a class="nav-link" href="{% url "tournament:pools" %}"><i class="fas fa-swimming-pool"></i> {% trans "Pools" %}</a>
</li>
{% endif %}
{% if not user.is_authenticated and request.session.extra_access_token %}
{# Juries can access to pool data without logging in. #}
<li class="nav-item active">
<a class="nav-link" href="{% url "tournament:all_solutions" %}"><i class="fas fa-lightbulb"></i> {% trans "Solutions" %}</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="{% url "tournament:all_syntheses" %}"><i class="fas fa-feather"></i> {% trans "Syntheses" %}</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="{% url "tournament:pools" %}"><i class="fas fa-swimming-pool"></i> {% trans "Pools" %}</a>
</li>
{% endif %}
<li class="nav-item active">
<a class="nav-link" href="https://www.helloasso.com/associations/animath/formulaires/5/widget"><i
class="fas fa-hand-holding-heart"></i> {% trans "Make a gift" %}</a>

View File

@ -61,13 +61,12 @@
{% trans "Templates for syntheses are available here:" %}
<a data-turbolinks="false" href="{% static "Fiche synthèse.pdf" %}">PDF</a> -- <a data-turbolinks="false" href="{% static "Fiche synthèse.tex" %}">TEX</a>
</div>
{% if user.organizes %}
{% if user.organizes or not user.is_authenticated %}
<ul>
{% for synthesis in pool.syntheses.all %}
<li><a data-turbolinks="false" href="{{ synthesis.file.url }}">{{ synthesis }}</a></li>
{% endfor %}
</ul>
</div>
<div class="card-footer text-center">
<form method="post">
{% csrf_token %}
@ -76,10 +75,20 @@
</div>
{% endif %}
</div>
</div>
<hr>
<div class="text-center">
<a class="btn btn-block btn-primary" href="{% url "tournament:pools" %}">{% trans "Pool list" %}</a>
</div>
{% if user.organizes or not user.is_authenticated %}
<hr>
<div class="alert alert-warning">
{% trans "Give this link to juries to access this page (warning: should stay confidential and only given to juries of this pool):" %}<br>
<a href="{% url "tournament:pool_detail" pk=pool.pk %}?extra_access_token={{ pool.extra_access_token }}">
https://{{ request.get_host }}{% url "tournament:pool_detail" pk=pool.pk %}?extra_access_token={{ pool.extra_access_token }}</a>
</div>
{% endif %}
{% endblock %}

View File

@ -64,6 +64,22 @@ class SessionMiddleware(object):
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"]
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,

View File

@ -71,6 +71,7 @@ MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'tfjm.middlewares.SessionMiddleware',
'tfjm.middlewares.ExtraAccessMiddleware',
'tfjm.middlewares.TurbolinksMiddleware',
]