From 3e46d068177c38944962d4b854d5aa2b8b1cb871 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 22 Apr 2022 18:02:27 +0200 Subject: [PATCH] Add CSV export for tournaments --- .../participation/tournament_detail.html | 1 + apps/participation/urls.py | 3 +- apps/participation/views.py | 52 +++++++++++--- locale/fr/LC_MESSAGES/django.po | 72 ++++++++++--------- 4 files changed, 85 insertions(+), 43 deletions(-) diff --git a/apps/participation/templates/participation/tournament_detail.html b/apps/participation/templates/participation/tournament_detail.html index e7429ad..2f6a875 100644 --- a/apps/participation/templates/participation/tournament_detail.html +++ b/apps/participation/templates/participation/tournament_detail.html @@ -62,6 +62,7 @@ {% if user.registration.is_admin or user.registration in tournament.organizers.all %} {% endif %} diff --git a/apps/participation/urls.py b/apps/participation/urls.py index e38a3f8..369a677 100644 --- a/apps/participation/urls.py +++ b/apps/participation/urls.py @@ -8,7 +8,7 @@ from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTe ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \ PoolUpdateTeamsView, PoolUpdateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, \ TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \ - TournamentDetailView, TournamentListView, TournamentUpdateView + TournamentDetailView, TournamentExportCSVView, TournamentListView, TournamentUpdateView app_name = "participation" @@ -31,6 +31,7 @@ urlpatterns = [ path("tournament/create/", TournamentCreateView.as_view(), name="tournament_create"), path("tournament//", TournamentDetailView.as_view(), name="tournament_detail"), path("tournament//update/", TournamentUpdateView.as_view(), name="tournament_update"), + path("tournament//csv/", TournamentExportCSVView.as_view(), name="tournament_csv"), path("pools/create/", PoolCreateView.as_view(), name="pool_create"), path("pools//", PoolDetailView.as_view(), name="pool_detail"), path("pools//update/", PoolUpdateView.as_view(), name="pool_update"), diff --git a/apps/participation/views.py b/apps/participation/views.py index f29d099..1718aaa 100644 --- a/apps/participation/views.py +++ b/apps/participation/views.py @@ -1,6 +1,6 @@ # 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 @@ -180,13 +180,13 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView) # 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. 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 + 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 @@ -346,6 +346,7 @@ 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}" @@ -459,6 +460,7 @@ 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 @@ -557,6 +559,40 @@ class TournamentDetailView(DetailView): 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 diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 3e20958..0d8f298 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: TFJM\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-11 17:03+0200\n" +"POT-Creation-Date: 2022-04-22 18:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Yohann D'ANELLO \n" "Language-Team: LANGUAGE \n" @@ -104,59 +104,59 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" msgid "valid" msgstr "valide" -#: apps/participation/forms.py:24 +#: apps/participation/forms.py:25 msgid "This name is already used." msgstr "Ce nom est déjà utilisé." -#: apps/participation/forms.py:31 apps/participation/models.py:41 +#: apps/participation/forms.py:32 apps/participation/models.py:41 msgid "The trigram must be composed of three uppercase letters." msgstr "Le trigramme doit être composé de trois lettres majuscules." -#: apps/participation/forms.py:34 +#: apps/participation/forms.py:35 msgid "This trigram is already used." msgstr "Ce trigramme est déjà utilisé." -#: apps/participation/forms.py:49 +#: apps/participation/forms.py:50 msgid "No team was found with this access code." msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." -#: apps/participation/forms.py:78 apps/participation/forms.py:213 +#: apps/participation/forms.py:79 apps/participation/forms.py:215 #: apps/registration/forms.py:117 apps/registration/forms.py:139 #: apps/registration/forms.py:161 apps/registration/forms.py:215 msgid "The uploaded file size must be under 2 Mo." msgstr "Le fichier envoyé doit peser moins de 2 Mo." -#: apps/participation/forms.py:80 apps/registration/forms.py:119 +#: apps/participation/forms.py:81 apps/registration/forms.py:119 #: apps/registration/forms.py:141 apps/registration/forms.py:163 #: apps/registration/forms.py:217 msgid "The uploaded file must be a PDF, PNG of JPEG file." msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG." -#: apps/participation/forms.py:98 +#: apps/participation/forms.py:99 msgid "I engage myself to participate to the whole TFJM²." msgstr "Je m'engage à participer à l'intégralité du TFJM²." -#: apps/participation/forms.py:113 +#: apps/participation/forms.py:114 msgid "Message to address to the team:" msgstr "Message à adresser à l'équipe :" -#: apps/participation/forms.py:150 +#: apps/participation/forms.py:152 msgid "The uploaded file size must be under 5 Mo." msgstr "Le fichier envoyé doit peser moins de 5 Mo." -#: apps/participation/forms.py:152 apps/participation/forms.py:215 +#: apps/participation/forms.py:154 apps/participation/forms.py:217 msgid "The uploaded file must be a PDF file." msgstr "Le fichier envoyé doit être au format PDF." -#: apps/participation/forms.py:156 +#: apps/participation/forms.py:158 msgid "The PDF file must not have more than 30 pages." msgstr "Le fichier PDF ne doit pas avoir plus de 30 pages." -#: apps/participation/forms.py:196 +#: apps/participation/forms.py:198 msgid "The defender, the opponent and the reporter must be different." msgstr "Le défenseur, l'opposant et le rapporteur doivent être différents." -#: apps/participation/forms.py:200 +#: apps/participation/forms.py:202 msgid "This defender did not work on this problem." msgstr "Ce défenseur ne travaille pas sur ce problème." @@ -750,7 +750,7 @@ msgid "BigBlueButton link:" msgstr "Lien BigBlueButton :" #: apps/participation/templates/participation/pool_detail.html:41 -#: apps/participation/templates/participation/tournament_detail.html:94 +#: apps/participation/templates/participation/tournament_detail.html:95 msgid "Ranking" msgstr "Classement" @@ -769,7 +769,7 @@ msgid "Passages" msgstr "Passages" #: apps/participation/templates/participation/pool_detail.html:68 -#: apps/participation/templates/participation/tournament_detail.html:108 +#: apps/participation/templates/participation/tournament_detail.html:109 msgid "Add" msgstr "Ajouter" @@ -910,7 +910,7 @@ msgid "Update team" msgstr "Modifier l'équipe" #: apps/participation/templates/participation/team_detail.html:181 -#: apps/participation/views.py:429 +#: apps/participation/views.py:430 msgid "Leave team" msgstr "Quitter l'équipe" @@ -984,20 +984,24 @@ msgstr "Pour contacter les équipes valides" msgid "Edit tournament" msgstr "Modifier le tournoi" -#: apps/participation/templates/participation/tournament_detail.html:71 +#: apps/participation/templates/participation/tournament_detail.html:65 +msgid "Export as CSV" +msgstr "Exporter en CSV" + +#: apps/participation/templates/participation/tournament_detail.html:72 #: tfjm/templates/base.html:65 msgid "Teams" msgstr "Équipes" -#: apps/participation/templates/participation/tournament_detail.html:79 +#: apps/participation/templates/participation/tournament_detail.html:80 msgid "Pools" msgstr "Poules" -#: apps/participation/templates/participation/tournament_detail.html:86 +#: apps/participation/templates/participation/tournament_detail.html:87 msgid "Add new pool" msgstr "Ajouter une nouvelle poule" -#: apps/participation/templates/participation/tournament_detail.html:107 +#: apps/participation/templates/participation/tournament_detail.html:108 msgid "Add pool" msgstr "Ajouter une poule" @@ -1036,12 +1040,12 @@ msgstr "Vous êtes déjà dans une équipe." msgid "Join team" msgstr "Rejoindre une équipe" -#: apps/participation/views.py:150 apps/participation/views.py:435 -#: apps/participation/views.py:468 +#: apps/participation/views.py:150 apps/participation/views.py:436 +#: apps/participation/views.py:470 msgid "You are not in a team." msgstr "Vous n'êtes pas dans une équipe." -#: apps/participation/views.py:151 apps/participation/views.py:469 +#: apps/participation/views.py:151 apps/participation/views.py:471 msgid "You don't participate, so you don't have any team." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." @@ -1086,49 +1090,49 @@ msgstr "Vous devez spécifier si vous validez l'inscription ou non." msgid "Update team {trigram}" msgstr "Mise à jour de l'équipe {trigram}" -#: apps/participation/views.py:365 apps/participation/views.py:415 +#: apps/participation/views.py:366 apps/participation/views.py:416 #, python-brace-format msgid "Motivation letter of {team}.{ext}" msgstr "Lettre de motivation de {team}.{ext}" -#: apps/participation/views.py:396 +#: apps/participation/views.py:397 #, python-brace-format msgid "Photo authorization of {participant}.{ext}" msgstr "Autorisation de droit à l'image de {participant}.{ext}" -#: apps/participation/views.py:402 +#: apps/participation/views.py:403 #, python-brace-format msgid "Parental authorization of {participant}.{ext}" msgstr "Autorisation parentale de {participant}.{ext}" -#: apps/participation/views.py:409 +#: apps/participation/views.py:410 #, python-brace-format msgid "Health sheet of {participant}.{ext}" msgstr "Fiche sanitaire de {participant}.{ext}" -#: apps/participation/views.py:419 +#: apps/participation/views.py:420 #, python-brace-format msgid "Photo authorizations of team {trigram}.zip" msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip" -#: apps/participation/views.py:437 +#: apps/participation/views.py:438 msgid "The team is already validated or the validation is pending." msgstr "La validation de l'équipe est déjà faite ou en cours." -#: apps/participation/views.py:483 +#: apps/participation/views.py:485 msgid "The team is not validated yet." msgstr "L'équipe n'est pas encore validée." -#: apps/participation/views.py:497 +#: apps/participation/views.py:499 #, python-brace-format msgid "Participation of team {trigram}" msgstr "Participation de l'équipe {trigram}" -#: apps/participation/views.py:589 +#: apps/participation/views.py:625 msgid "You can't upload a solution after the deadline." msgstr "Vous ne pouvez pas envoyer de solution après la date limite." -#: apps/participation/views.py:775 +#: apps/participation/views.py:811 msgid "You can't upload a synthesis after the deadline." msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."