# Copyright (C) 2020 by Animath # SPDX-License-Identifier: GPL-3.0-or-later import os from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied, ValidationError from django.db import transaction 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.http import urlsafe_base64_decode 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 from tfjm.tokens import email_validation_token from tfjm.views import AdminMixin, UserMixin, VolunteerMixin from .forms import AddOrganizerForm, AdminRegistrationForm, CoachRegistrationForm, HealthSheetForm, \ ParentalAuthorizationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \ VolunteerRegistrationForm from .models import ParticipantRegistration, Registration, StudentRegistration from .tables import RegistrationTable class SignupView(CreateView): """ Signup, as a participant or a coach. """ model = User form_class = SignupForm template_name = "registration/signup.html" 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() registration.send_email_validation_link() return ret def get_success_url(self): return reverse_lazy("registration:email_validation_sent") class AddOrganizerView(VolunteerMixin, CreateView): 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"] if not self.request.user.registration.is_admin: del context["form"].fields["type"] del context["admin_registration_form"] 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() registration.send_email_validation_link() return ret def get_success_url(self): return reverse_lazy("registration: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 = 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,)) class UserDetailView(UserMixin, DetailView): """ Display the detail about a user. """ model = User context_object_name = "user_object" template_name = "registration/user_detail.html" 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 \ and user.registration.team.participation.tournament in user.registration.organized_tournaments.all() \ 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 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 class UserListView(AdminMixin, SingleTableView): """ Display the list of all registered users. """ model = Registration table_class = RegistrationTable template_name = "registration/user_list.html" class UserUpdateView(UserMixin, UpdateView): """ Update the detail about a user and its registration. """ model = User form_class = UserForm template_name = "registration/update_user.html" 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(): return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) 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) if not self.request.user.registration.is_admin: 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) if not self.request.user.registration.is_admin: 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,)) class UserUploadPhotoAuthorizationView(UserMixin, UpdateView): """ A participant can send its photo authorization. """ model = StudentRegistration form_class = PhotoAuthorizationForm template_name = "registration/upload_photo_authorization.html" extra_context = dict(title=_("Upload photo authorization")) 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().user: return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) @transaction.atomic def form_valid(self, form): old_instance = StudentRegistration.objects.get(pk=self.object.pk) if old_instance.photo_authorization: old_instance.photo_authorization.delete() return super().form_valid(form) def get_success_url(self): return reverse_lazy("registration:user_detail", args=(self.object.user.pk,)) class UserUploadHealthSheetView(UserMixin, UpdateView): """ 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")) 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().user: return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) @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() return super().form_valid(form) def get_success_url(self): return reverse_lazy("registration:user_detail", args=(self.object.user.pk,)) class UserUploadParentalAuthorizationView(UserMixin, UpdateView): """ 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")) 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().user: return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) @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() return super().form_valid(form) def get_success_url(self): return reverse_lazy("registration:user_detail", args=(self.object.user.pk,)) 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) user = request.user 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 = _("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 = ParticipantRegistration.objects.get(health_sheet__endswith=filename) user = request.user 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 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) 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) user = request.user 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 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).exists()): 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 = 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"] path = f"media/syntheses/{filename}" if not os.path.exists(path): raise Http404 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 # 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(synthesis) + f".{ext}" return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name) 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")))