Add CSV export for tournaments

This commit is contained in:
Yohann D'ANELLO 2022-04-22 18:02:27 +02:00
parent 0fd9222055
commit 3e46d06817
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
4 changed files with 85 additions and 43 deletions

View File

@ -62,6 +62,7 @@
{% if user.registration.is_admin or user.registration in tournament.organizers.all %} {% if user.registration.is_admin or user.registration in tournament.organizers.all %}
<div class="card-footer text-center"> <div class="card-footer text-center">
<a href="{% url "participation:tournament_update" pk=tournament.pk %}"><button class="btn btn-secondary">{% trans "Edit tournament" %}</button></a> <a href="{% url "participation:tournament_update" pk=tournament.pk %}"><button class="btn btn-secondary">{% trans "Edit tournament" %}</button></a>
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}"><button class="btn btn-success">{% trans "Export as CSV" %}</button></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -8,7 +8,7 @@ from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTe
ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \ ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \
PoolUpdateTeamsView, PoolUpdateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, \ PoolUpdateTeamsView, PoolUpdateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, \
TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \ TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \
TournamentDetailView, TournamentListView, TournamentUpdateView TournamentDetailView, TournamentExportCSVView, TournamentListView, TournamentUpdateView
app_name = "participation" app_name = "participation"
@ -31,6 +31,7 @@ urlpatterns = [
path("tournament/create/", TournamentCreateView.as_view(), name="tournament_create"), path("tournament/create/", TournamentCreateView.as_view(), name="tournament_create"),
path("tournament/<int:pk>/", TournamentDetailView.as_view(), name="tournament_detail"), path("tournament/<int:pk>/", TournamentDetailView.as_view(), name="tournament_detail"),
path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"), path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"),
path("tournament/<int:pk>/csv/", TournamentExportCSVView.as_view(), name="tournament_csv"),
path("pools/create/", PoolCreateView.as_view(), name="pool_create"), path("pools/create/", PoolCreateView.as_view(), name="pool_create"),
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"), path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"), path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),

View File

@ -1,6 +1,6 @@
# Copyright (C) 2020 by Animath # Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import csv
from io import BytesIO from io import BytesIO
import os import os
from zipfile import ZipFile 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, # 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. # 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 \ context["can_validate"] = team.students.count() >= 4 and team.coaches.exists() and \
team.participation.tournament and \ team.participation.tournament and \
all(r.photo_authorization for r in team.participants.all()) and \ all(r.photo_authorization for r in team.participants.all()) and \
(team.participation.tournament.remote (team.participation.tournament.remote
or all(r.health_sheet for r in team.students.all() if r.under_18)) and \ or all(r.health_sheet for r in team.students.all() if r.under_18)) and \
(team.participation.tournament.remote (team.participation.tournament.remote
or all(r.parental_authorization for r in team.students.all() if r.under_18)) and \ or all(r.parental_authorization for r in team.students.all() if r.under_18)) and \
team.motivation_letter team.motivation_letter
return context return context
@ -346,6 +346,7 @@ class MotivationLetterView(LoginRequiredMixin, View):
""" """
Display the sent motivation letter. Display the sent motivation letter.
""" """
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
filename = kwargs["filename"] filename = kwargs["filename"]
path = f"media/authorization/motivation_letters/{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. Redirects to the detail view of the participation of the team.
""" """
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
user = self.request.user user = self.request.user
registration = user.registration registration = user.registration
@ -557,6 +559,40 @@ class TournamentDetailView(DetailView):
return context 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): class SolutionUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_solution.html" template_name = "participation/upload_solution.html"
form_class = SolutionForm form_class = SolutionForm

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TFJM\n" "Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n" "Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -104,59 +104,59 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}"
msgid "valid" msgid "valid"
msgstr "valide" msgstr "valide"
#: apps/participation/forms.py:24 #: apps/participation/forms.py:25
msgid "This name is already used." msgid "This name is already used."
msgstr "Ce nom est déjà utilisé." 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." msgid "The trigram must be composed of three uppercase letters."
msgstr "Le trigramme doit être composé de trois lettres majuscules." 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." msgid "This trigram is already used."
msgstr "Ce trigramme est déjà utilisé." 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." msgid "No team was found with this access code."
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." 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:117 apps/registration/forms.py:139
#: apps/registration/forms.py:161 apps/registration/forms.py:215 #: apps/registration/forms.py:161 apps/registration/forms.py:215
msgid "The uploaded file size must be under 2 Mo." msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 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:141 apps/registration/forms.py:163
#: apps/registration/forms.py:217 #: apps/registration/forms.py:217
msgid "The uploaded file must be a PDF, PNG of JPEG file." msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG." 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²." msgid "I engage myself to participate to the whole TFJM²."
msgstr "Je m'engage à participer à l'intégralité du 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:" msgid "Message to address to the team:"
msgstr "Message à adresser à l'équipe :" 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." msgid "The uploaded file size must be under 5 Mo."
msgstr "Le fichier envoyé doit peser moins de 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." msgid "The uploaded file must be a PDF file."
msgstr "Le fichier envoyé doit être au format PDF." 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." msgid "The PDF file must not have more than 30 pages."
msgstr "Le fichier PDF ne doit pas avoir plus de 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." msgid "The defender, the opponent and the reporter must be different."
msgstr "Le défenseur, l'opposant et le rapporteur doivent être différents." 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." msgid "This defender did not work on this problem."
msgstr "Ce défenseur ne travaille pas sur ce problème." msgstr "Ce défenseur ne travaille pas sur ce problème."
@ -750,7 +750,7 @@ msgid "BigBlueButton link:"
msgstr "Lien BigBlueButton :" msgstr "Lien BigBlueButton :"
#: apps/participation/templates/participation/pool_detail.html:41 #: 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" msgid "Ranking"
msgstr "Classement" msgstr "Classement"
@ -769,7 +769,7 @@ msgid "Passages"
msgstr "Passages" msgstr "Passages"
#: apps/participation/templates/participation/pool_detail.html:68 #: 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" msgid "Add"
msgstr "Ajouter" msgstr "Ajouter"
@ -910,7 +910,7 @@ msgid "Update team"
msgstr "Modifier l'équipe" msgstr "Modifier l'équipe"
#: apps/participation/templates/participation/team_detail.html:181 #: apps/participation/templates/participation/team_detail.html:181
#: apps/participation/views.py:429 #: apps/participation/views.py:430
msgid "Leave team" msgid "Leave team"
msgstr "Quitter l'équipe" msgstr "Quitter l'équipe"
@ -984,20 +984,24 @@ msgstr "Pour contacter les équipes valides"
msgid "Edit tournament" msgid "Edit tournament"
msgstr "Modifier le tournoi" 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 #: tfjm/templates/base.html:65
msgid "Teams" msgid "Teams"
msgstr "Équipes" msgstr "Équipes"
#: apps/participation/templates/participation/tournament_detail.html:79 #: apps/participation/templates/participation/tournament_detail.html:80
msgid "Pools" msgid "Pools"
msgstr "Poules" msgstr "Poules"
#: apps/participation/templates/participation/tournament_detail.html:86 #: apps/participation/templates/participation/tournament_detail.html:87
msgid "Add new pool" msgid "Add new pool"
msgstr "Ajouter une nouvelle poule" msgstr "Ajouter une nouvelle poule"
#: apps/participation/templates/participation/tournament_detail.html:107 #: apps/participation/templates/participation/tournament_detail.html:108
msgid "Add pool" msgid "Add pool"
msgstr "Ajouter une poule" msgstr "Ajouter une poule"
@ -1036,12 +1040,12 @@ msgstr "Vous êtes déjà dans une équipe."
msgid "Join team" msgid "Join team"
msgstr "Rejoindre une équipe" msgstr "Rejoindre une équipe"
#: apps/participation/views.py:150 apps/participation/views.py:435 #: apps/participation/views.py:150 apps/participation/views.py:436
#: apps/participation/views.py:468 #: apps/participation/views.py:470
msgid "You are not in a team." msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe." 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." msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." 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}" msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {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 #, python-brace-format
msgid "Motivation letter of {team}.{ext}" msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}" msgstr "Lettre de motivation de {team}.{ext}"
#: apps/participation/views.py:396 #: apps/participation/views.py:397
#, python-brace-format #, python-brace-format
msgid "Photo authorization of {participant}.{ext}" msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}" msgstr "Autorisation de droit à l'image de {participant}.{ext}"
#: apps/participation/views.py:402 #: apps/participation/views.py:403
#, python-brace-format #, python-brace-format
msgid "Parental authorization of {participant}.{ext}" msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}" msgstr "Autorisation parentale de {participant}.{ext}"
#: apps/participation/views.py:409 #: apps/participation/views.py:410
#, python-brace-format #, python-brace-format
msgid "Health sheet of {participant}.{ext}" msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}" msgstr "Fiche sanitaire de {participant}.{ext}"
#: apps/participation/views.py:419 #: apps/participation/views.py:420
#, python-brace-format #, python-brace-format
msgid "Photo authorizations of team {trigram}.zip" msgid "Photo authorizations of team {trigram}.zip"
msgstr "Autorisations de droit à l'image de l'équipe {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." msgid "The team is already validated or the validation is pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." 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." msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée." msgstr "L'équipe n'est pas encore validée."
#: apps/participation/views.py:497 #: apps/participation/views.py:499
#, python-brace-format #, python-brace-format
msgid "Participation of team {trigram}" msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {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." msgid "You can't upload a solution after the deadline."
msgstr "Vous ne pouvez pas envoyer de solution après la date limite." 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." 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." msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."