mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-07-26 09:05:26 +02:00
Move apps in main directory
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
api
locale/fr/LC_MESSAGES
logs
participation
__init__.pyadmin.pytests.pyurls.pyviews.py
api
apps.pyforms.pymanagement
__init__.py
commands
migrations
models.pysearch_indexes.pysignals.pytables.pytemplates
participation
chat.htmlcreate_team.htmljoin_team.html
mails
request_validation.htmlrequest_validation.txtteam_not_validated.htmlteam_not_validated.txtteam_validated.htmlteam_validated.txt
note_form.htmlparticipation_detail.htmlpassage_detail.htmlpassage_form.htmlpool_detail.htmlpool_form.htmlteam_detail.htmlteam_leave.htmlteam_list.htmltournament_detail.htmltournament_form.htmltournament_list.htmlupdate_team.htmlupload_motivation_letter.htmlupload_notes.htmlupload_solution.htmlupload_synthesis.htmlsearch
indexes
registration
__init__.pyadmin.py
api
apps.pyauth.pyfixtures
forms.pymigrations
0001_initial.py0002_auto_20230110_2031.py0003_alter_participantregistration_zip_code.py0004_volunteer_admin.py0005_studentregistration_vaccine_sheet.py__init__.py
models.pysearch_indexes.pysignals.pytables.pytemplates
registration
add_organizer.htmlemail_validation_complete.htmlemail_validation_email_sent.html
mails
password_change_done.htmlpassword_change_form.htmlpassword_reset_complete.htmlpassword_reset_confirm.htmlpassword_reset_done.htmlpassword_reset_form.htmlpayment_form.htmlsignup.htmltex
Autorisation_droit_image_majeur.texAutorisation_droit_image_mineur.texAutorisation_parentale.texInstructions.tex
update_user.htmlupload_health_sheet.htmlupload_parental_authorization.htmlupload_photo_authorization.htmlupload_vaccine_sheet.htmluser_detail.htmluser_list.htmlsearch
templatetags
tests.pyurls.pyviews.pytfjm
tox.ini
889
participation/views.py
Normal file
889
participation/views.py
Normal file
@ -0,0 +1,889 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import csv
|
||||
from io import BytesIO
|
||||
import os
|
||||
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.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
|
||||
from tfjm.lists import get_sympa_client
|
||||
from tfjm.matrix import Matrix
|
||||
from tfjm.views import AdminMixin, VolunteerMixin
|
||||
|
||||
from .forms import 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 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 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()
|
Reference in New Issue
Block a user