plateforme-tfjm2/apps/registration/views.py

624 lines
25 KiB
Python
Raw Normal View History

2020-12-27 10:49:54 +00:00
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import subprocess
from tempfile import mkdtemp
2020-12-27 10:49:54 +00:00
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
2020-12-27 10:49:54 +00:00
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import transaction
2021-01-17 11:40:23 +00:00
from django.db.models import Q
2020-12-27 10:49:54 +00:00
from django.http import FileResponse, Http404
from django.shortcuts import redirect, resolve_url
from django.template.loader import render_to_string
2020-12-27 10:49:54 +00:00
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.http import urlsafe_base64_decode
2020-12-27 10:49:54 +00:00
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View
from django_tables2 import SingleTableView
from magic import Magic
from participation.models import Passage, Solution, Synthesis, Tournament
2020-12-28 18:19:01 +00:00
from tfjm.tokens import email_validation_token
2021-02-06 18:26:19 +00:00
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
2020-12-27 10:49:54 +00:00
2021-01-14 20:07:09 +00:00
from .forms import AddOrganizerForm, AdminRegistrationForm, CoachRegistrationForm, HealthSheetForm, \
ParentalAuthorizationForm, PaymentForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \
2021-01-14 20:07:09 +00:00
VolunteerRegistrationForm
from .models import ParticipantRegistration, Payment, Registration, StudentRegistration
2020-12-27 10:49:54 +00:00
from .tables import RegistrationTable
class SignupView(CreateView):
"""
Signup, as a participant or a coach.
"""
model = User
form_class = SignupForm
2021-01-22 22:24:35 +00:00
template_name = "registration/signup.html"
2020-12-27 10:49:54 +00:00
extra_context = dict(title=_("Sign up"))
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["student_registration_form"] = StudentRegistrationForm(self.request.POST or None)
context["coach_registration_form"] = CoachRegistrationForm(self.request.POST or None)
del context["student_registration_form"].fields["team"]
del context["student_registration_form"].fields["email_confirmed"]
del context["coach_registration_form"].fields["team"]
del context["coach_registration_form"].fields["email_confirmed"]
return context
@transaction.atomic
def form_valid(self, form):
role = form.cleaned_data["role"]
if role == "participant":
registration_form = StudentRegistrationForm(self.request.POST)
else:
registration_form = CoachRegistrationForm(self.request.POST)
del registration_form.fields["team"]
del registration_form.fields["email_confirmed"]
if not registration_form.is_valid():
return self.form_invalid(form)
ret = super().form_valid(form)
registration = registration_form.instance
registration.user = form.instance
registration.save()
2021-01-17 11:40:23 +00:00
registration.send_email_validation_link()
2020-12-27 10:49:54 +00:00
return ret
def get_success_url(self):
return reverse_lazy("registration:email_validation_sent")
2021-01-17 11:40:23 +00:00
class AddOrganizerView(VolunteerMixin, CreateView):
2021-01-14 20:07:09 +00:00
model = User
form_class = AddOrganizerForm
template_name = "registration/add_organizer.html"
extra_context = dict(title=_("Add organizer"))
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["volunteer_registration_form"] = VolunteerRegistrationForm(self.request.POST or None)
context["admin_registration_form"] = AdminRegistrationForm(self.request.POST or None)
del context["volunteer_registration_form"].fields["email_confirmed"]
del context["admin_registration_form"].fields["email_confirmed"]
2021-01-17 11:40:23 +00:00
if not self.request.user.registration.is_admin:
del context["form"].fields["type"]
del context["admin_registration_form"]
2021-01-14 20:07:09 +00:00
return context
@transaction.atomic
def form_valid(self, form):
role = form.cleaned_data["type"]
if role == "admin":
registration_form = AdminRegistrationForm(self.request.POST)
else:
registration_form = VolunteerRegistrationForm(self.request.POST)
del registration_form.fields["email_confirmed"]
if not registration_form.is_valid():
return self.form_invalid(form)
ret = super().form_valid(form)
registration = registration_form.instance
registration.user = form.instance
registration.save()
2021-01-17 11:40:23 +00:00
registration.send_email_validation_link()
2021-01-14 20:07:09 +00:00
password = get_random_string(16)
form.instance.set_password(password)
form.instance.save()
subject = "[TFJM²] " + str(_("New TFJM² organizer account"))
site = Site.objects.first()
message = render_to_string('registration/mails/add_organizer.txt', dict(user=registration.user,
inviter=self.request.user,
password=password,
domain=site.domain))
html = render_to_string('registration/mails/add_organizer.html', dict(user=registration.user,
inviter=self.request.user,
password=password,
domain=site.domain))
registration.user.email_user(subject, message, html_message=html)
2021-01-21 21:54:23 +00:00
if registration.is_admin:
registration.user.is_superuser = True
registration.user.save()
2021-01-14 20:07:09 +00:00
return ret
def get_success_url(self):
return reverse_lazy("registration:email_validation_sent")
2020-12-27 10:49:54 +00:00
class UserValidateView(TemplateView):
"""
A view to validate the email address.
"""
title = _("Email validation")
template_name = 'registration/email_validation_complete.html'
extra_context = dict(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.registration.email_confirmed = True
user.registration.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 = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.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 = dict(title=_('Email validation email sent'))
class UserResendValidationEmailView(LoginRequiredMixin, DetailView):
"""
Rensend the email validation link.
"""
model = User
extra_context = dict(title=_("Resend email validation link"))
def get(self, request, *args, **kwargs):
user = self.get_object()
user.registration.send_email_validation_link()
return redirect('registration:email_validation_sent')
class MyAccountDetailView(LoginRequiredMixin, RedirectView):
"""
Redirect to our own profile detail page.
"""
def get_redirect_url(self, *args, **kwargs):
return reverse_lazy("registration:user_detail", args=(self.request.user.pk,))
2021-01-23 10:02:26 +00:00
class UserDetailView(LoginRequiredMixin, DetailView):
2020-12-27 10:49:54 +00:00
"""
Display the detail about a user.
"""
model = User
context_object_name = "user_object"
template_name = "registration/user_detail.html"
2021-01-17 11:40:23 +00:00
def dispatch(self, request, *args, **kwargs):
me = request.user
if not me.is_authenticated:
return self.handle_no_permission()
user = self.get_object()
if user == me or me.registration.is_admin or me.registration.is_volunteer \
and user.registration.participates and user.registration.team \
2021-01-29 09:24:00 +00:00
and user.registration.team.participation.tournament in me.registration.organized_tournaments.all() \
2021-01-17 11:40:23 +00:00
or user.registration.is_volunteer and me.registration.is_volunteer \
and me.registration.interesting_tournaments.intersection(user.registration.intersting_tournaments):
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
2020-12-27 10:49:54 +00:00
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = _("Detail of user {user}").format(user=str(self.object.registration))
return context
2021-02-06 18:26:19 +00:00
class UserListView(VolunteerMixin, SingleTableView):
2020-12-27 10:49:54 +00:00
"""
Display the list of all registered users.
"""
model = Registration
table_class = RegistrationTable
template_name = "registration/user_list.html"
2020-12-29 15:14:56 +00:00
class UserUpdateView(UserMixin, UpdateView):
2020-12-27 10:49:54 +00:00
"""
Update the detail about a user and its registration.
"""
model = User
form_class = UserForm
template_name = "registration/update_user.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.get_object()
context["title"] = _("Update user {user}").format(user=str(self.object.registration))
context["registration_form"] = user.registration.form_class(data=self.request.POST or None,
instance=self.object.registration)
2020-12-28 20:42:01 +00:00
if not self.request.user.registration.is_admin:
2020-12-27 10:49:54 +00:00
if "team" in context["registration_form"].fields:
del context["registration_form"].fields["team"]
del context["registration_form"].fields["email_confirmed"]
return context
@transaction.atomic
def form_valid(self, form):
user = form.instance
registration_form = user.registration.form_class(data=self.request.POST or None,
instance=self.object.registration)
2020-12-28 20:42:01 +00:00
if not self.request.user.registration.is_admin:
2020-12-27 10:49:54 +00:00
if "team" in registration_form.fields:
del registration_form.fields["team"]
del registration_form.fields["email_confirmed"]
if not registration_form.is_valid():
return self.form_invalid(form)
registration_form.save()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("registration:user_detail", args=(self.object.pk,))
2021-01-23 10:02:26 +00:00
class UserUploadPhotoAuthorizationView(UserRegistrationMixin, UpdateView):
2020-12-27 10:49:54 +00:00
"""
A participant can send its photo authorization.
"""
model = ParticipantRegistration
2020-12-27 10:49:54 +00:00
form_class = PhotoAuthorizationForm
template_name = "registration/upload_photo_authorization.html"
extra_context = dict(title=_("Upload photo authorization"))
@transaction.atomic
def form_valid(self, form):
old_instance = ParticipantRegistration.objects.get(pk=self.object.pk)
2020-12-27 10:49:54 +00:00
if old_instance.photo_authorization:
old_instance.photo_authorization.delete()
old_instance.save()
2020-12-27 10:49:54 +00:00
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
2021-01-23 10:02:26 +00:00
class UserUploadHealthSheetView(UserRegistrationMixin, UpdateView):
2020-12-29 15:14:56 +00:00
"""
A participant can send its health sheet.
"""
model = StudentRegistration
form_class = HealthSheetForm
template_name = "registration/upload_health_sheet.html"
extra_context = dict(title=_("Upload health sheet"))
@transaction.atomic
def form_valid(self, form):
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
if old_instance.health_sheet:
old_instance.health_sheet.delete()
old_instance.save()
2020-12-29 15:14:56 +00:00
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
2021-01-23 10:02:26 +00:00
class UserUploadParentalAuthorizationView(UserRegistrationMixin, UpdateView):
2020-12-29 15:14:56 +00:00
"""
A participant can send its parental authorization.
"""
model = StudentRegistration
form_class = ParentalAuthorizationForm
template_name = "registration/upload_parental_authorization.html"
extra_context = dict(title=_("Upload parental authorization"))
@transaction.atomic
def form_valid(self, form):
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
if old_instance.parental_authorization:
old_instance.parental_authorization.delete()
old_instance.save()
2020-12-29 15:14:56 +00:00
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
class AuthorizationTemplateView(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if "registration_id" in self.request.GET:
registration = Registration.objects.get(pk=self.request.GET.get("registration_id"))
# Don't get unwanted information
if registration.user == self.request.user \
or self.request.user.is_authenticated and self.request.user.registration.is_admin:
context["registration"] = registration
if "tournament_id" in self.request.GET and self.request.GET.get("tournament_id").isnumeric():
if not Tournament.objects.filter(pk=self.request.GET.get("tournament_id")).exists():
raise PermissionDenied("Ce tournoi n'existe pas.")
context["tournament"] = Tournament.objects.get(pk=self.request.GET.get("tournament_id"))
else:
raise PermissionDenied("Merci d'indiquer un tournoi.")
return context
def render_to_response(self, context, **response_kwargs):
tex = render_to_string(self.template_name, context=context, request=self.request)
temp_dir = mkdtemp()
with open(os.path.join(temp_dir, "texput.tex"), "w") as f:
f.write(tex)
process = subprocess.Popen(["pdflatex", "-interaction=nonstopmode", f"-output-directory={temp_dir}",
os.path.join(temp_dir, "texput.tex"), ])
process.wait()
return FileResponse(open(os.path.join(temp_dir, "texput.pdf"), "rb"),
content_type="application/pdf",
filename=self.template_name.split("/")[-1][:-3] + "pdf")
class AdultPhotoAuthorizationTemplateView(AuthorizationTemplateView):
template_name = "registration/tex/Autorisation_droit_image_majeur.tex"
class ChildPhotoAuthorizationTemplateView(AuthorizationTemplateView):
template_name = "registration/tex/Autorisation_droit_image_mineur.tex"
class ParentalAuthorizationTemplateView(AuthorizationTemplateView):
template_name = "registration/tex/Autorisation_parentale.tex"
class InstructionsTemplateView(AuthorizationTemplateView):
template_name = "registration/tex/Instructions.tex"
class PaymentUpdateView(LoginRequiredMixin, UpdateView):
model = Payment
form_class = PaymentForm
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin \
and (self.request.user != self.get_object().registration.user
or self.get_object().valid is not False):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def get_form(self, form_class=None):
form = super().get_form(form_class)
if not self.request.user.registration.is_admin:
del form.fields["type"].widget.choices[-1]
del form.fields["valid"]
return form
def form_valid(self, form):
if not self.request.user.registration.is_admin:
form.instance.valid = None
return super().form_valid(form)
2020-12-27 10:49:54 +00:00
class PhotoAuthorizationView(LoginRequiredMixin, View):
"""
Display the sent photo authorization.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/photo/{filename}"
if not os.path.exists(path):
raise Http404
student = ParticipantRegistration.objects.get(photo_authorization__endswith=filename)
2020-12-27 10:49:54 +00:00
user = request.user
2021-01-17 11:40:23 +00:00
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
2020-12-27 10:49:54 +00:00
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = _("Photo authorization of {student}.{ext}").format(student=str(student), ext=ext)
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
class HealthSheetView(LoginRequiredMixin, View):
"""
Display the sent health sheet.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/health/{filename}"
if not os.path.exists(path):
raise Http404
student = StudentRegistration.objects.get(health_sheet__endswith=filename)
user = request.user
2021-01-17 11:40:23 +00:00
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = _("Health sheet of {student}.{ext}").format(student=str(student), ext=ext)
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
class ParentalAuthorizationView(LoginRequiredMixin, View):
"""
Display the sent parental authorization.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/parental/{filename}"
if not os.path.exists(path):
raise Http404
student = StudentRegistration.objects.get(parental_authorization__endswith=filename)
user = request.user
2021-01-17 11:40:23 +00:00
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = _("Parental authorization of {student}.{ext}").format(student=str(student), ext=ext)
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
2021-01-18 22:39:02 +00:00
class ScholarshipView(LoginRequiredMixin, View):
"""
Display the sent scholarship paper.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/scholarship/{filename}"
if not os.path.exists(path):
raise Http404
payment = Payment.objects.get(scholarship_file__endswith=filename)
user = request.user
if not (payment.registration.user == user or user.registration.is_admin):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = _("Scholarship attestation of {user}.{ext}").format(user=str(user.registration), ext=ext)
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
2021-01-12 16:56:40 +00:00
class SolutionView(LoginRequiredMixin, View):
"""
Display the sent solution.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/solutions/{filename}"
if not os.path.exists(path):
raise Http404
solution = Solution.objects.get(file__endswith=filename)
2021-01-17 11:40:23 +00:00
user = request.user
passage_participant_qs = Passage.objects.filter(Q(defender=user.registration.team.participation)
| Q(opponent=user.registration.team.participation)
| Q(reporter=user.registration.team.participation),
defender=solution.participation,
solution_number=solution.problem)
2021-01-17 11:40:23 +00:00
if not (user.registration.is_admin or user.registration.is_volunteer
and Passage.objects.filter(Q(pool__juries=user.registration)
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
defender=solution.participation,
solution_number=solution.problem).exists()
or user.registration.participates and user.registration.team
and (solution.participation.team == user.registration.team or
any(passage.pool.round == 1
or timezone.now() >= passage.pool.tournament.solutions_available_second_phase
for passage in passage_participant_qs.all()))):
2021-01-17 11:40:23 +00:00
raise PermissionDenied
2021-01-12 16:56:40 +00:00
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = str(solution) + f".{ext}"
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
class SynthesisView(LoginRequiredMixin, View):
"""
Display the sent synthesis.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
2021-01-14 16:26:08 +00:00
path = f"media/syntheses/{filename}"
2021-01-12 16:56:40 +00:00
if not os.path.exists(path):
raise Http404
2021-01-17 11:40:23 +00:00
synthesis = Synthesis.objects.get(file__endswith=filename)
user = request.user
if not (user.registration.is_admin or user.registration.is_volunteer
and (user.registration in synthesis.passage.pool.juries.all()
or user.registration in synthesis.passage.pool.tournament.organizers.all())
or user.registration.participates and user.registration.team == synthesis.participation.team):
raise PermissionDenied
2021-01-12 16:56:40 +00:00
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
2021-01-17 11:40:23 +00:00
true_file_name = str(synthesis) + f".{ext}"
2021-01-12 16:56:40 +00:00
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
2020-12-27 10:49:54 +00:00
class UserImpersonateView(LoginRequiredMixin, RedirectView):
"""
An administrator can log in through this page as someone else, and act as this other person.
"""
def dispatch(self, request, *args, **kwargs):
if self.request.user.registration.is_admin:
if not User.objects.filter(pk=kwargs["pk"]).exists():
raise Http404
session = request.session
session["admin"] = request.user.pk
session["_fake_user_id"] = kwargs["pk"]
return super().dispatch(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs):
return reverse_lazy("registration:user_detail", args=(kwargs["pk"],))
class ResetAdminView(LoginRequiredMixin, 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):
user = request.user
if not user.is_authenticated:
return self.handle_no_permission()
if "_fake_user_id" in request.session:
del request.session["_fake_user_id"]
return redirect(request.GET.get("path", reverse_lazy("index")))