# Copyright (C) 2020 by Animath # SPDX-License-Identifier: GPL-3.0-or-later import csv from io import BytesIO import os import subprocess from tempfile import mkdtemp from zipfile import ZipFile from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.sites.models import Site from django.core.exceptions import PermissionDenied from django.core.mail import send_mail from django.db import transaction from django.http import FileResponse, Http404, HttpResponse from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View from django.views.generic.edit import FormMixin, ProcessFormView from django_tables2 import SingleTableView from magic import Magic from registration.models import StudentRegistration, VolunteerRegistration from tfjm.lists import get_sympa_client from tfjm.matrix import Matrix from tfjm.views import AdminMixin, VolunteerMixin from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \ PoolForm, PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \ UploadNotesForm, ValidateParticipationForm from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable class CreateTeamView(LoginRequiredMixin, CreateView): """ Display the page to create a team for new users. """ model = Team form_class = TeamForm extra_context = dict(title=_("Create team")) template_name = "participation/create_team.html" def dispatch(self, request, *args, **kwargs): user = request.user if not user.is_authenticated: return super().handle_no_permission() registration = user.registration if not registration.participates: raise PermissionDenied(_("You don't participate, so you can't create a team.")) elif registration.team: raise PermissionDenied(_("You are already in a team.")) return super().dispatch(request, *args, **kwargs) @transaction.atomic def form_valid(self, form): """ When a team is about to be created, the user automatically joins the team, a mailing list got created and the user is automatically subscribed to this mailing list, and finally a Matrix room is created and the user is invited in this room. """ ret = super().form_valid(form) # The user joins the team user = self.request.user registration = user.registration registration.team = form.instance registration.save() # Subscribe the user mail address to the team mailing list get_sympa_client().subscribe(user.email, f"equipe-{form.instance.trigram.lower()}", False, f"{user.first_name} {user.last_name}") # Invite the user in the team Matrix room Matrix.invite(f"#equipe-{form.instance.trigram.lower()}:tfjm.org", f"@{user.registration.matrix_username}:tfjm.org") return ret class JoinTeamView(LoginRequiredMixin, FormView): """ Participants can join a team with the access code of the team. """ model = Team form_class = JoinTeamForm extra_context = dict(title=_("Join team")) template_name = "participation/create_team.html" def dispatch(self, request, *args, **kwargs): user = request.user if not user.is_authenticated: return super().handle_no_permission() registration = user.registration if not registration.participates: raise PermissionDenied(_("You don't participate, so you can't create a team.")) elif registration.team: raise PermissionDenied(_("You are already in a team.")) return super().dispatch(request, *args, **kwargs) @transaction.atomic def form_valid(self, form): """ When a user joins a team, the user is automatically subscribed to the team mailing list,the user is invited in the team Matrix room. """ self.object = form.instance ret = super().form_valid(form) # Join the team user = self.request.user registration = user.registration registration.team = form.instance registration.save() # Subscribe to the team mailing list get_sympa_client().subscribe(user.email, f"equipe-{form.instance.trigram.lower()}", False, f"{user.first_name} {user.last_name}") # Invite the user in the team Matrix room Matrix.invite(f"#equipe-{form.instance.trigram.lower()}:tfjm.org", f"@{user.registration.matrix_username}:tfjm.org") return ret def get_success_url(self): return reverse_lazy("participation:team_detail", args=(self.object.pk,)) class TeamListView(AdminMixin, SingleTableView): """ Display the whole list of teams """ model = Team table_class = TeamTable ordering = ('trigram',) class MyTeamDetailView(LoginRequiredMixin, RedirectView): """ Redirect to the detail of the team in which the user is. """ def get_redirect_url(self, *args, **kwargs): user = self.request.user registration = user.registration if registration.participates: if registration.team: return reverse_lazy("participation:team_detail", args=(registration.team_id,)) raise PermissionDenied(_("You are not in a team.")) raise PermissionDenied(_("You don't participate, so you don't have any team.")) class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView): """ Display the detail of a team. """ model = Team def get(self, request, *args, **kwargs): user = request.user self.object = self.get_object() # Ensure that the user is an admin or a volunteer or a member of the team if user.registration.is_admin or user.registration.participates and \ user.registration.team and user.registration.team.pk == kwargs["pk"] \ or user.registration.is_volunteer \ and (self.object.participation.tournament in user.registration.interesting_tournaments or self.object.participation.final and Tournament.final_tournament() in user.registration.interesting_tournaments): return super().get(request, *args, **kwargs) raise PermissionDenied def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) team = self.get_object() context["title"] = _("Detail of team {trigram}").format(trigram=self.object.trigram) context["request_validation_form"] = RequestValidationForm(self.request.POST or None) context["validation_form"] = ValidateParticipationForm(self.request.POST or None) # A team is complete when there are at least 4 members plus a coache that have sent their authorizations, # their health sheet, they confirmed their email address and under-18 people sent their parental authorization. # TODO: Add vaccine sheets context["can_validate"] = team.students.count() >= 4 and team.coaches.exists() and \ team.participation.tournament and \ all(r.photo_authorization for r in team.participants.all()) and \ (team.participation.tournament.remote or all(r.health_sheet for r in team.students.all() if r.under_18)) and \ (team.participation.tournament.remote or all(r.parental_authorization for r in team.students.all() if r.under_18)) and \ team.motivation_letter return context def get_form_class(self): if not self.request.POST: return RequestValidationForm elif self.request.POST["_form_type"] == "RequestValidationForm": return RequestValidationForm elif self.request.POST["_form_type"] == "ValidateParticipationForm": return ValidateParticipationForm def form_valid(self, form): self.object = self.get_object() if isinstance(form, RequestValidationForm): return self.handle_request_validation(form) elif isinstance(form, ValidateParticipationForm): return self.handle_validate_participation(form) def handle_request_validation(self, form): """ A team requests to be validated """ if not self.request.user.registration.participates: form.add_error(None, _("You don't participate, so you can't request the validation of the team.")) return self.form_invalid(form) if self.object.participation.valid is not None: form.add_error(None, _("The validation of the team is already done or pending.")) return self.form_invalid(form) if not self.get_context_data()["can_validate"]: form.add_error(None, _("The team can't be validated: missing email address confirmations, " "authorizations, people, motivation letter or the tournament is not set.")) return self.form_invalid(form) self.object.participation.valid = False self.object.participation.save() mail_context = dict(team=self.object, domain=Site.objects.first().domain) mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context) mail_html = render_to_string("participation/mails/request_validation.html", mail_context) send_mail("[TFJM²] Validation d'équipe", mail_plain, settings.DEFAULT_FROM_EMAIL, [self.object.participation.tournament.organizers_email], html_message=mail_html) return super().form_valid(form) def handle_validate_participation(self, form): """ An admin validates the team (or not) """ if not self.request.user.registration.is_admin and \ (not self.object.participation.tournament or self.request.user.registration not in self.object.participation.tournament.organizers.all()): form.add_error(None, _("You are not an organizer of the tournament.")) return self.form_invalid(form) elif self.object.participation.valid is not False: form.add_error(None, _("This team has no pending validation.")) return self.form_invalid(form) if "validate" in self.request.POST: self.object.participation.valid = True self.object.participation.save() mail_context = dict(team=self.object, message=form.cleaned_data["message"]) mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context) mail_html = render_to_string("participation/mails/team_validated.html", mail_context) send_mail("[TFJM²] Équipe validée", mail_plain, None, [self.object.email], html_message=mail_html) if self.object.participation.tournament.price == 0: for registration in self.object.participants.all(): registration.payment.type = "free" registration.payment.valid = True registration.payment.save() else: for coach in self.object.coaches.all(): coach.payment.type = "free" coach.payment.valid = True coach.payment.save() elif "invalidate" in self.request.POST: self.object.participation.valid = None self.object.participation.save() mail_context = dict(team=self.object, message=form.cleaned_data["message"]) mail_plain = render_to_string("participation/mails/team_not_validated.txt", mail_context) mail_html = render_to_string("participation/mails/team_not_validated.html", mail_context) send_mail("[TFJM²] Équipe non validée", mail_plain, None, [self.object.email], html_message=mail_html) else: form.add_error(None, _("You must specify if you validate the registration or not.")) return self.form_invalid(form) return super().form_valid(form) def get_success_url(self): return self.request.path class TeamUpdateView(LoginRequiredMixin, UpdateView): """ Update the detail of a team """ model = Team form_class = TeamForm template_name = "participation/update_team.html" def dispatch(self, request, *args, **kwargs): user = request.user if not user.is_authenticated: return super().handle_no_permission() if user.registration.is_admin or user.registration.participates and \ user.registration.team and user.registration.team.pk == kwargs["pk"] \ or user.registration.is_volunteer \ and (self.get_object().participation.tournament in user.registration.interesting_tournaments or self.get_object().participation.final and Tournament.final_tournament() in user.registration.interesting_tournaments): return super().dispatch(request, *args, **kwargs) raise PermissionDenied def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["participation_form"] = ParticipationForm(data=self.request.POST or None, instance=self.object.participation) if not self.request.user.registration.is_volunteer: del context["participation_form"].fields['final'] context["title"] = _("Update team {trigram}").format(trigram=self.object.trigram) return context @transaction.atomic def form_valid(self, form): participation_form = ParticipationForm(data=self.request.POST or None, instance=self.object.participation) if not self.request.user.registration.is_volunteer: del participation_form.fields['final'] if not participation_form.is_valid(): return self.form_invalid(form) participation_form.save() return super().form_valid(form) class TeamUploadMotivationLetterView(LoginRequiredMixin, UpdateView): """ A team can send its motivation letter. """ model = Team form_class = MotivationLetterForm template_name = "participation/upload_motivation_letter.html" extra_context = dict(title=_("Upload motivation letter")) 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.registration.team != self.get_object(): return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) @transaction.atomic def form_valid(self, form): old_instance = Team.objects.get(pk=self.object.pk) if old_instance.motivation_letter: old_instance.motivation_letter.delete() old_instance.save() return super().form_valid(form) class MotivationLetterView(LoginRequiredMixin, View): """ Display the sent motivation letter. """ def get(self, request, *args, **kwargs): filename = kwargs["filename"] path = f"media/authorization/motivation_letters/{filename}" if not os.path.exists(path): raise Http404 team = Team.objects.get(motivation_letter__endswith=filename) user = request.user if not (user.registration in team.participants.all() or user.registration.is_admin or user.registration.is_volunteer and 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 = _("Motivation letter of {team}.{ext}").format(team=str(team), ext=ext) return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name) class TeamAuthorizationsView(LoginRequiredMixin, DetailView): """ Get as a ZIP archive all the authorizations that are sent """ model = Team def dispatch(self, request, *args, **kwargs): user = request.user if not user.is_authenticated: return super().handle_no_permission() if user.registration.is_admin or user.registration.is_volunteer \ and (self.get_object().participation.tournament in user.registration.interesting_tournaments or self.get_object().participation.final and Tournament.final_tournament() in user.registration.interesting_tournaments): return super().dispatch(request, *args, **kwargs) raise PermissionDenied def get(self, request, *args, **kwargs): team = self.get_object() magic = Magic(mime=True) output = BytesIO() zf = ZipFile(output, "w") for participant in team.participants.all(): if participant.photo_authorization: mime_type = magic.from_file("media/" + participant.photo_authorization.name) ext = mime_type.split("/")[1].replace("jpeg", "jpg") zf.write("media/" + participant.photo_authorization.name, _("Photo authorization of {participant}.{ext}").format(participant=str(participant), ext=ext)) if isinstance(participant, StudentRegistration) and participant.parental_authorization: mime_type = magic.from_file("media/" + participant.parental_authorization.name) ext = mime_type.split("/")[1].replace("jpeg", "jpg") zf.write("media/" + participant.parental_authorization.name, _("Parental authorization of {participant}.{ext}") .format(participant=str(participant), ext=ext)) if isinstance(participant, StudentRegistration) and participant.health_sheet: mime_type = magic.from_file("media/" + participant.health_sheet.name) ext = mime_type.split("/")[1].replace("jpeg", "jpg") zf.write("media/" + participant.health_sheet.name, _("Health sheet of {participant}.{ext}").format(participant=str(participant), ext=ext)) if isinstance(participant, StudentRegistration) and participant.vaccine_sheet: mime_type = magic.from_file("media/" + participant.vaccine_sheet.name) ext = mime_type.split("/")[1].replace("jpeg", "jpg") zf.write("media/" + participant.vaccine_sheet.name, _("Vaccine sheet of {participant}.{ext}").format(participant=str(participant), ext=ext)) if team.motivation_letter: mime_type = magic.from_file("media/" + team.motivation_letter.name) ext = mime_type.split("/")[1].replace("jpeg", "jpg") zf.write("media/" + team.motivation_letter.name, _("Motivation letter of {team}.{ext}").format(team=str(team), ext=ext)) zf.close() response = HttpResponse(content_type="application/zip") response["Content-Disposition"] = "attachment; filename=\"{filename}\"" \ .format(filename=_("Photo authorizations of team {trigram}.zip").format(trigram=team.trigram)) response.write(output.getvalue()) return response class TeamLeaveView(LoginRequiredMixin, TemplateView): """ A team member leaves a team """ template_name = "participation/team_leave.html" extra_context = dict(title=_("Leave team")) def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if not request.user.registration.participates or not request.user.registration.team: raise PermissionDenied(_("You are not in a team.")) if request.user.registration.team.participation.valid: raise PermissionDenied(_("The team is already validated or the validation is pending.")) return super().dispatch(request, *args, **kwargs) @transaction.atomic() def post(self, request, *args, **kwargs): """ When the team is left, the user is unsubscribed from the team mailing list and kicked from the team room. """ team = request.user.registration.team request.user.registration.team = None request.user.registration.save() get_sympa_client().unsubscribe(request.user.email, f"equipe-{team.trigram.lower()}", False) Matrix.kick(f"#equipe-{team.trigram.lower()}:tfjm.org", f"@{request.user.registration.matrix_username}:tfjm.org", "Équipe quittée") if team.students.count() + team.coaches.count() == 0: team.delete() return redirect(reverse_lazy("index")) class MyParticipationDetailView(LoginRequiredMixin, RedirectView): """ Redirects to the detail view of the participation of the team. """ def get_redirect_url(self, *args, **kwargs): user = self.request.user registration = user.registration if registration.participates: if registration.team: return reverse_lazy("participation:participation_detail", args=(registration.team.participation.id,)) raise PermissionDenied(_("You are not in a team.")) raise PermissionDenied(_("You don't participate, so you don't have any team.")) class ParticipationDetailView(LoginRequiredMixin, DetailView): """ Display detail about the participation of a team, and manage the solution submission. """ model = Participation def dispatch(self, request, *args, **kwargs): user = request.user if not user.is_authenticated: return super().handle_no_permission() if not self.get_object().valid: raise PermissionDenied(_("The team is not validated yet.")) if user.registration.is_admin or user.registration.participates \ and user.registration.team.participation \ and user.registration.team.participation.pk == kwargs["pk"] \ or user.registration.is_volunteer \ and (self.get_object().tournament in user.registration.interesting_tournaments or self.get_object().final and Tournament.final_tournament() in user.registration.interesting_tournaments): return super().dispatch(request, *args, **kwargs) raise PermissionDenied def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = lambda: _("Participation of team {trigram}").format(trigram=self.object.team.trigram) return context class TournamentListView(SingleTableView): """ Display the list of all tournaments. """ model = Tournament table_class = TournamentTable class TournamentCreateView(AdminMixin, CreateView): """ Create a new tournament. """ model = Tournament form_class = TournamentForm def get_success_url(self): return reverse_lazy("participation:tournament_detail", args=(self.object.pk,)) class TournamentUpdateView(VolunteerMixin, UpdateView): """ Update tournament detail. """ model = Tournament form_class = TournamentForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not self.request.user.registration.is_admin \ and not (self.request.user.registration.is_volunteer and self.request.user.registration.organized_tournaments.all()): return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) class TournamentDetailView(DetailView): """ Display tournament detail. """ model = Tournament def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["teams"] = ParticipationTable(self.object.participations.all()) context["pools"] = PoolTable(self.object.pools.order_by('id').all()) notes = dict() for participation in self.object.participations.all(): note = sum(pool.average(participation) for pool in self.object.pools.filter(participations=participation).all() if pool.results_available or (self.request.user.is_authenticated and self.request.user.registration.is_volunteer)) if note: notes[participation] = note context["notes"] = sorted(notes.items(), key=lambda x: x[1], reverse=True) return context class TournamentExportCSVView(VolunteerMixin, DetailView): """ Export team information in a CSV file. """ model = Tournament def get(self, request, *args, **kwargs): tournament = self.get_object() resp = HttpResponse( content_type='text/csv', headers={'Content-Disposition': f'attachment; filename="Tournoi de {tournament.name}.csv"'}, ) writer = csv.DictWriter(resp, ('Tournoi', 'Équipe', 'Trigramme', 'Nom', 'Prénom', 'Genre', 'Date de naissance')) writer.writeheader() for participation in tournament.participations.filter(valid=True).order_by('team__trigram').all(): for registration in participation.team.participants\ .order_by('coachregistration', 'user__last_name').all(): writer.writerow({ 'Tournoi': tournament.name, 'Équipe': participation.team.name, 'Trigramme': participation.team.trigram, 'Nom': registration.user.last_name, 'Prénom': registration.user.first_name, 'Genre': registration.get_gender_display() if isinstance(registration, StudentRegistration) else 'Encandrant⋅e', 'Date de naissance': registration.birth_date if isinstance(registration, StudentRegistration) else 'Encandrant⋅e', }) return resp class SolutionUploadView(LoginRequiredMixin, FormView): template_name = "participation/upload_solution.html" form_class = SolutionForm def dispatch(self, request, *args, **kwargs): qs = Participation.objects.filter(pk=self.kwargs["pk"]) if not qs.exists(): raise Http404 self.participation = qs.get() if not self.request.user.is_authenticated or not self.request.user.registration.is_admin \ and not (self.request.user.registration.participates and self.request.user.registration.team == self.participation.team): return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) @transaction.atomic def form_valid(self, form): """ When a solution is submitted, it replaces a previous solution if existing, otherwise it creates a new solution. It is discriminating whenever the team is selected for the final tournament or not. """ form_sol = form.instance sol_qs = Solution.objects.filter(participation=self.participation, problem=form_sol.problem, final_solution=self.participation.final) tournament = Tournament.final_tournament() if self.participation.final else self.participation.tournament if timezone.now() > tournament.solution_limit and sol_qs.exists(): form.add_error(None, _("You can't upload a solution after the deadline.")) return self.form_invalid(form) # Drop previous solution if existing for sol in sol_qs.all(): sol.file.delete() sol.save() sol.delete() form_sol.participation = self.participation form_sol.final_solution = self.participation.final form_sol.save() return super().form_valid(form) def get_success_url(self): return reverse_lazy("participation:participation_detail", args=(self.participation.pk,)) class PoolCreateView(AdminMixin, CreateView): model = Pool form_class = PoolForm class PoolDetailView(LoginRequiredMixin, DetailView): model = Pool def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if request.user.registration.is_admin or request.user.registration.participates \ and request.user.registration.team \ and request.user.registration.team.participation in self.get_object().participations.all() \ or request.user.registration.is_volunteer \ and self.get_object().tournament in request.user.registration.interesting_tournaments: return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["passages"] = PassageTable(self.object.passages.order_by('id').all()) if self.object.results_available or self.request.user.registration.is_volunteer: # Hide notes before the end of the turn notes = dict() for participation in self.object.participations.all(): note = self.object.average(participation) if note: notes[participation] = note context["notes"] = sorted(notes.items(), key=lambda x: x[1], reverse=True) return context class PoolUpdateView(VolunteerMixin, UpdateView): model = Pool form_class = PoolForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and (self.get_object().tournament in request.user.registration.organized_tournaments.all() or request.user.registration in self.get_object().juries.all()): return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() class PoolUpdateTeamsView(VolunteerMixin, UpdateView): model = Pool form_class = PoolTeamsForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and (self.get_object().tournament in request.user.registration.organized_tournaments.all() or request.user.registration in self.get_object().juries.all()): return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() class PoolAddJurysView(VolunteerMixin, FormView, DetailView): """ This view lets organizers set jurys for a pool, without multiplying clicks. """ model = Pool form_class = AddJuryForm template_name = 'participation/pool_add_jurys.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['title'] = _("Jurys of {pool}").format(pool=self.object) return context @transaction.atomic def form_valid(self, form): self.object = self.get_object() # Save the user object first form.save() user = form.instance # Create associated registration object to the new user reg = VolunteerRegistration.objects.create( user=user, professional_activity="Juré⋅e du tournoi " + self.object.tournament.name, ) # Add the user in the jury self.object.juries.add(reg) self.object.save() reg.send_email_validation_link() # Generate new password for the user password = get_random_string(16) user.set_password(password) user.save() # Send welcome mail subject = "[TFJM²] " + str(_("New TFJM² jury account")) site = Site.objects.first() message = render_to_string('registration/mails/add_organizer.txt', dict(user=user, inviter=self.request.user, password=password, domain=site.domain)) html = render_to_string('registration/mails/add_organizer.html', dict(user=user, inviter=self.request.user, password=password, domain=site.domain)) user.email_user(subject, message, html_message=html) # Add notification messages.success(self.request, _("The jury {name} has been successfully added!") .format(name=f"{user.first_name} {user.last_name}")) return super().form_valid(form) def form_invalid(self, form): # This is useful since we have a FormView + a DetailView self.object = self.get_object() return super().form_invalid(form) def get_success_url(self): return reverse_lazy('participation:pool_add_jurys', args=(self.kwargs['pk'],)) class PoolUploadNotesView(VolunteerMixin, FormView, DetailView): model = Pool form_class = UploadNotesForm template_name = 'participation/upload_notes.html' def dispatch(self, request, *args, **kwargs): self.object = self.get_object() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and (self.object.tournament in request.user.registration.organized_tournaments.all() or request.user.registration in self.object.juries.all()): return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() @transaction.atomic def form_valid(self, form): pool = self.get_object() parsed_notes = form.cleaned_data['parsed_notes'] for vr, notes in parsed_notes.items(): if vr not in pool.juries.all(): form.add_error('file', _("The following user is not registered as a jury:") + " " + str(vr)) for i, passage in enumerate(pool.passages.all()): note = Note.objects.get_or_create(jury=vr, passage=passage)[0] passage_notes = notes[6 * i:6 * (i + 1)] note.set_all(*passage_notes) note.save() messages.success(self.request, _("Notes were successfully uploaded.")) return super().form_valid(form) def get_success_url(self): return reverse_lazy('participation:pool_detail', args=(self.kwargs['pk'],)) class NotationSheetTemplateView(VolunteerMixin, DetailView): """ Generate a PDF from a LaTeX template for the notation papers. """ model = Pool def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['esp'] = self.object.passages.count() * '&' context['is_jury'] = self.request.user.registration in self.object.juries.all() \ and 'blank' not in self.request.GET context['tfjm_number'] = timezone.now().year - 2010 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(streaming_content=open(os.path.join(temp_dir, "texput.pdf"), "rb"), content_type="application/pdf", filename=self.template_name.split("/")[-1][:-3] + "pdf") class ScaleNotationSheetTemplateView(NotationSheetTemplateView): template_name = 'participation/tex/bareme.tex' class FinalNotationSheetTemplateView(NotationSheetTemplateView): template_name = 'participation/tex/finale.tex' class PassageCreateView(VolunteerMixin, CreateView): model = Passage form_class = PassageForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() qs = Pool.objects.filter(pk=self.kwargs["pk"]) if not qs.exists(): raise Http404 self.pool = qs.get() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and (self.pool.tournament in request.user.registration.organized_tournaments.all() or request.user.registration in self.pool.juries.all()): return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() def get_form(self, form_class=None): form = super().get_form(form_class) form.instance.pool = self.pool form.fields["defender"].queryset = self.pool.participations.all() form.fields["opponent"].queryset = self.pool.participations.all() form.fields["reporter"].queryset = self.pool.participations.all() return form class PassageDetailView(LoginRequiredMixin, DetailView): model = Passage def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and (self.get_object().pool.tournament in request.user.registration.organized_tournaments.all() or request.user.registration in self.get_object().pool.juries.all()) \ or request.user.registration.participates and request.user.registration.team \ and request.user.registration.team.participation in [self.get_object().defender, self.get_object().opponent, self.get_object().reporter]: return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.user.registration in self.object.pool.juries.all(): context["my_note"] = Note.objects.get_or_create(passage=self.object, jury=self.request.user.registration)[0] context["notes"] = NoteTable([note for note in self.object.notes.all() if note]) elif self.request.user.registration.is_admin: context["notes"] = NoteTable([note for note in self.object.notes.all() if note]) return context class PassageUpdateView(VolunteerMixin, UpdateView): model = Passage form_class = PassageForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and (self.get_object().pool.tournament in request.user.registration.organized_tournaments.all() or request.user.registration in self.get_object().pool.juries.all()): return super().dispatch(request, *args, **kwargs) return self.handle_no_permission() class SynthesisUploadView(LoginRequiredMixin, FormView): template_name = "participation/upload_synthesis.html" form_class = SynthesisForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not request.user.registration.participates: return self.handle_no_permission() qs = Passage.objects.filter(pk=self.kwargs["pk"]) if not qs.exists(): raise Http404 self.participation = self.request.user.registration.team.participation self.passage = qs.get() if self.participation not in [self.passage.opponent, self.passage.reporter]: return self.handle_no_permission() return super().dispatch(request, *args, **kwargs) def form_valid(self, form): """ When a solution is submitted, it replaces a previous solution if existing, otherwise it creates a new solution. It is discriminating whenever the team is selected for the final tournament or not. """ form_syn = form.instance form_syn.type = 1 if self.participation == self.passage.opponent else 2 syn_qs = Synthesis.objects.filter(participation=self.participation, passage=self.passage, type=form_syn.type).all() deadline = self.passage.pool.tournament.syntheses_first_phase_limit if self.passage.pool.round == 1 \ else self.passage.pool.tournament.syntheses_second_phase_limit if syn_qs.exists() and timezone.now() > deadline: form.add_error(None, _("You can't upload a synthesis after the deadline.")) return self.form_invalid(form) # Drop previous solution if existing for syn in syn_qs.all(): syn.file.delete() syn.save() syn.delete() form_syn.participation = self.participation form_syn.passage = self.passage form_syn.save() return super().form_valid(form) def get_success_url(self): return reverse_lazy("participation:passage_detail", args=(self.passage.pk,)) class NoteUpdateView(VolunteerMixin, UpdateView): model = Note form_class = NoteForm def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if request.user.registration.is_admin or request.user.registration.is_volunteer \ and self.get_object().jury == request.user.registration: return super().dispatch(request, *args, **kwargs) return self.handle_no_permission()