From 1e7bd209a1d9681425b1b030486543faf01e852a Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Sat, 30 Mar 2024 20:38:13 +0100 Subject: [PATCH] Add harmonization view Signed-off-by: Emmy D'Anello --- locale/fr/LC_MESSAGES/django.po | 149 ++++++++++-------- participation/admin.py | 2 + .../participation/tournament_detail.html | 18 ++- .../participation/tournament_harmonize.html | 52 ++++++ participation/urls.py | 7 +- participation/views.py | 78 ++++++++- 6 files changed, 238 insertions(+), 68 deletions(-) create mode 100644 participation/templates/participation/tournament_harmonize.html diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 4dbdaf1..209d515 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: 2024-03-30 19:13+0100\n" +"POT-Creation-Date: 2024-03-30 20:33+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emmy D'Anello \n" "Language-Team: LANGUAGE \n" @@ -333,6 +333,7 @@ msgstr "Continuer le tirage" #: draw/templates/draw/tournament_content.html:216 participation/admin.py:167 #: participation/models.py:253 participation/models.py:653 +#: participation/templates/participation/tournament_harmonize.html:15 #: registration/models.py:156 registration/models.py:609 #: registration/tables.py:39 #: registration/templates/registration/payment_form.html:52 @@ -377,8 +378,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?" msgid "Close" msgstr "Fermer" -#: draw/views.py:31 participation/views.py:155 participation/views.py:469 -#: participation/views.py:500 +#: draw/views.py:31 participation/views.py:156 participation/views.py:470 +#: participation/views.py:501 msgid "You are not in a team." msgstr "Vous n'êtes pas dans une équipe." @@ -490,7 +491,7 @@ msgstr "Ce trigramme est déjà utilisé." msgid "No team was found with this access code." msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." -#: participation/forms.py:59 participation/views.py:471 +#: participation/forms.py:59 participation/views.py:472 msgid "The team is already validated or the validation is pending." msgstr "La validation de l'équipe est déjà faite ou en cours." @@ -1355,6 +1356,7 @@ msgstr "Aller à la page Google Sheets de la poule" #: participation/templates/participation/pool_detail.html:83 #: participation/templates/participation/tournament_detail.html:98 +#: participation/templates/participation/tournament_harmonize.html:8 msgid "Ranking" msgstr "Classement" @@ -1582,7 +1584,7 @@ msgid "Invalidate" msgstr "Invalider" #: participation/templates/participation/team_detail.html:209 -#: participation/views.py:326 +#: participation/views.py:327 msgid "Upload motivation letter" msgstr "Envoyer la lettre de motivation" @@ -1591,7 +1593,7 @@ msgid "Update team" msgstr "Modifier l'équipe" #: participation/templates/participation/team_detail.html:219 -#: participation/views.py:463 +#: participation/views.py:464 msgid "Leave team" msgstr "Quitter l'équipe" @@ -1685,10 +1687,41 @@ msgstr "Publier les notes pour le premier tour" msgid "Publish notes for second round" msgstr "Publier les notes pour le second tour" -#: participation/templates/participation/tournament_detail.html:133 +#: participation/templates/participation/tournament_detail.html:128 +#: participation/templates/participation/tournament_detail.html:132 +msgid "Harmonize" +msgstr "Harmoniser" + +#: participation/templates/participation/tournament_detail.html:128 +#: participation/templates/participation/tournament_detail.html:132 +msgid "Day" +msgstr "Jour" + +#: participation/templates/participation/tournament_detail.html:143 msgid "Files available for download" msgstr "Fichiers disponibles au téléchargement" +#: participation/templates/participation/tournament_harmonize.html:14 +msgid "Rank" +msgstr "Rang" + +#: participation/templates/participation/tournament_harmonize.html:16 +msgid "Note" +msgstr "Note" + +#: participation/templates/participation/tournament_harmonize.html:17 +msgid "Including bonus / malus" +msgstr "Dont bonus / malus" + +#: participation/templates/participation/tournament_harmonize.html:18 +msgid "Add bonus / malus" +msgstr "Ajouter bonus / malus" + +#: participation/templates/participation/tournament_harmonize.html:48 +#: participation/templates/participation/tournament_payments.html:9 +msgid "Back to tournament page" +msgstr "Retour à la page du tournoi" + #: participation/templates/participation/tournament_list.html:6 #: tfjm/templates/base.html:67 msgid "All tournaments" @@ -1698,10 +1731,6 @@ msgstr "Tous les tournois" msgid "Add tournament" msgstr "Ajouter un tournoi" -#: participation/templates/participation/tournament_payments.html:9 -msgid "Back to tournament page" -msgstr "Retour à la page du tournoi" - #: participation/templates/participation/upload_motivation_letter.html:6 msgid "Back to the team detail" msgstr "Retour aux détails de l'utilisateur⋅rice" @@ -1718,44 +1747,44 @@ msgstr "Modèles :" msgid "Warning: non-free format" msgstr "Attention : format non libre" -#: participation/views.py:55 tfjm/templates/base.html:79 +#: participation/views.py:56 tfjm/templates/base.html:79 #: tfjm/templates/navbar.html:35 msgid "Create team" msgstr "Créer une équipe" -#: participation/views.py:64 participation/views.py:105 +#: participation/views.py:65 participation/views.py:106 msgid "You don't participate, so you can't create a team." msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." -#: participation/views.py:66 participation/views.py:107 +#: participation/views.py:67 participation/views.py:108 msgid "You are already in a team." msgstr "Vous êtes déjà dans une équipe." -#: participation/views.py:96 tfjm/templates/base.html:74 +#: participation/views.py:97 tfjm/templates/base.html:74 #: tfjm/templates/navbar.html:40 msgid "Join team" msgstr "Rejoindre une équipe" -#: participation/views.py:156 participation/views.py:501 +#: participation/views.py:157 participation/views.py:502 msgid "You don't participate, so you don't have any team." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." -#: participation/views.py:182 +#: participation/views.py:183 #, python-brace-format msgid "Detail of team {trigram}" msgstr "Détails de l'équipe {trigram}" -#: participation/views.py:211 +#: participation/views.py:212 msgid "You don't participate, so you can't request the validation of the team." msgstr "" "Vous ne participez pas, vous ne pouvez pas demander la validation de " "l'équipe." -#: participation/views.py:214 +#: participation/views.py:215 msgid "The validation of the team is already done or pending." msgstr "La validation de l'équipe est déjà faite ou en cours." -#: participation/views.py:217 +#: participation/views.py:218 msgid "" "The team can't be validated: missing email address confirmations, " "authorizations, people, motivation letter or the tournament is not set." @@ -1764,158 +1793,163 @@ msgstr "" "d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre " "de motivation, soit le tournoi n'a pas été choisi." -#: participation/views.py:239 +#: participation/views.py:240 msgid "You are not an organizer of the tournament." msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi." -#: participation/views.py:242 +#: participation/views.py:243 msgid "This team has no pending validation." msgstr "L'équipe n'a pas de validation en attente." -#: participation/views.py:269 +#: participation/views.py:270 msgid "You must specify if you validate the registration or not." msgstr "Vous devez spécifier si vous validez l'inscription ou non." -#: participation/views.py:304 +#: participation/views.py:305 #, python-brace-format msgid "Update team {trigram}" msgstr "Mise à jour de l'équipe {trigram}" -#: participation/views.py:365 participation/views.py:448 +#: participation/views.py:366 participation/views.py:449 #, python-brace-format msgid "Motivation letter of {team}.{ext}" msgstr "Lettre de motivation de {team}.{ext}" -#: participation/views.py:397 +#: participation/views.py:398 #, python-brace-format msgid "Authorizations of team {trigram}.zip" msgstr "Autorisations de l'équipe {trigram}.zip" -#: participation/views.py:401 +#: participation/views.py:402 #, python-brace-format msgid "Authorizations of {tournament}.zip" msgstr "Autorisations du tournoi {tournament}.zip" -#: participation/views.py:417 +#: participation/views.py:418 #, python-brace-format msgid "Photo authorization of {participant}.{ext}" msgstr "Autorisation de droit à l'image de {participant}.{ext}" -#: participation/views.py:425 +#: participation/views.py:426 #, python-brace-format msgid "Parental authorization of {participant}.{ext}" msgstr "Autorisation parentale de {participant}.{ext}" -#: participation/views.py:433 +#: participation/views.py:434 #, python-brace-format msgid "Health sheet of {participant}.{ext}" msgstr "Fiche sanitaire de {participant}.{ext}" -#: participation/views.py:441 +#: participation/views.py:442 #, python-brace-format msgid "Vaccine sheet of {participant}.{ext}" msgstr "Carnet de vaccination de {participant}.{ext}" -#: participation/views.py:515 +#: participation/views.py:516 msgid "The team is not validated yet." msgstr "L'équipe n'est pas encore validée." -#: participation/views.py:529 +#: participation/views.py:530 #, python-brace-format msgid "Participation of team {trigram}" msgstr "Participation de l'équipe {trigram}" -#: participation/views.py:611 +#: participation/views.py:612 #, python-brace-format msgid "Payments of {tournament}" msgstr "Paiements de {tournament}" -#: participation/views.py:706 +#: participation/views.py:707 msgid "Notes published!" msgstr "Notes publiées !" -#: participation/views.py:742 +#: participation/views.py:738 +#, python-brace-format +msgid "Harmonize notes of {tournament} - Day {round}" +msgstr "Harmoniser les notes de {tournament} - Jour {round}" + +#: participation/views.py:818 msgid "You can't upload a solution after the deadline." msgstr "Vous ne pouvez pas envoyer de solution après la date limite." -#: participation/views.py:866 +#: participation/views.py:942 #, python-brace-format msgid "Solutions of team {trigram}.zip" msgstr "Solutions de l'équipe {trigram}.zip" -#: participation/views.py:866 +#: participation/views.py:942 #, python-brace-format msgid "Syntheses of team {trigram}.zip" msgstr "Notes de synthèse de l'équipe {trigram}.zip" -#: participation/views.py:883 participation/views.py:898 +#: participation/views.py:959 participation/views.py:974 #, python-brace-format msgid "Solutions of {tournament}.zip" msgstr "Solutions de {tournament}.zip" -#: participation/views.py:883 participation/views.py:898 +#: participation/views.py:959 participation/views.py:974 #, python-brace-format msgid "Syntheses of {tournament}.zip" msgstr "Notes de synthèse de {tournament}.zip" -#: participation/views.py:907 +#: participation/views.py:983 #, python-brace-format msgid "Solutions for pool {pool} of tournament {tournament}.zip" msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip" -#: participation/views.py:908 +#: participation/views.py:984 #, python-brace-format msgid "Syntheses for pool {pool} of tournament {tournament}.zip" msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip" -#: participation/views.py:950 +#: participation/views.py:1026 #, python-brace-format msgid "Jury of pool {pool} for {tournament} with teams {teams}" msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}" -#: participation/views.py:966 +#: participation/views.py:1042 #, python-brace-format msgid "The jury {name} is already in the pool!" msgstr "{name} est déjà dans la poule !" -#: participation/views.py:986 +#: participation/views.py:1062 msgid "New TFJM² jury account" msgstr "Nouveau compte de juré⋅e pour le TFJM²" -#: participation/views.py:1003 +#: participation/views.py:1079 #, python-brace-format msgid "The jury {name} has been successfully added!" msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !" -#: participation/views.py:1039 +#: participation/views.py:1115 #, python-brace-format msgid "The jury {name} has been successfully removed!" msgstr "{name} a été retiré⋅e avec succès du jury !" -#: participation/views.py:1065 +#: participation/views.py:1141 #, python-brace-format msgid "The jury {name} has been successfully promoted president!" msgstr "{name} a été nommé⋅e président⋅e du jury !" -#: participation/views.py:1093 +#: participation/views.py:1169 msgid "The following user is not registered as a jury:" msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :" -#: participation/views.py:1107 +#: participation/views.py:1183 msgid "Notes were successfully uploaded." msgstr "Les notes ont bien été envoyées." -#: participation/views.py:1740 +#: participation/views.py:1816 #, python-brace-format msgid "Notation sheets of pool {pool} of {tournament}.zip" msgstr "Feuilles de notations pour la poule {pool} du tournoi {tournament}.zip" -#: participation/views.py:1745 +#: participation/views.py:1821 #, python-brace-format msgid "Notation sheets of {tournament}.zip" msgstr "Feuilles de notation de {tournament}.zip" -#: participation/views.py:1924 +#: participation/views.py:2000 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." @@ -3385,12 +3419,3 @@ msgstr "Aucun résultat." #: tfjm/templates/sidebar.html:10 tfjm/templates/sidebar.html:21 msgid "Informations" msgstr "Informations" - -#~ msgid "Export as CSV" -#~ msgstr "Exporter en CSV" - -#~ msgid "Add new pool" -#~ msgstr "Ajouter une nouvelle poule" - -#~ msgid "Add pool" -#~ msgstr "Ajouter une poule" diff --git a/participation/admin.py b/participation/admin.py index 746f8fe..97adde2 100644 --- a/participation/admin.py +++ b/participation/admin.py @@ -201,4 +201,6 @@ class TournamentAdmin(admin.ModelAdmin): @admin.register(Tweak) class TweakAdmin(admin.ModelAdmin): list_display = ('participation', 'pool', 'diff',) + list_filter = ('pool__tournament', 'pool__round',) + search_fields = ('participation__team__name', 'participation__team__trigram',) autocomplete_fields = ('participation', 'pool',) diff --git a/participation/templates/participation/tournament_detail.html b/participation/templates/participation/tournament_detail.html index bd523d8..c8a3efb 100644 --- a/participation/templates/participation/tournament_detail.html +++ b/participation/templates/participation/tournament_detail.html @@ -104,9 +104,9 @@ {% endfor %} - {% if not available_notes_1 or not available_notes_2 %} - {% if user.registration.is_admin or user.registration in tournament.organizers.all %} - {% endif %} diff --git a/participation/templates/participation/tournament_harmonize.html b/participation/templates/participation/tournament_harmonize.html new file mode 100644 index 0000000..2c8954c --- /dev/null +++ b/participation/templates/participation/tournament_harmonize.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} + +{% load i18n %} + +{% block content %} +
+
+
{% trans "Ranking" %}
+
+
+ + + + + + + + + + + + {% for participation, note in notes %} + + + + + + + + {% endfor %} + +
{% trans "Rank" %}{% trans "team"|capfirst %}{% trans "Note" %}{% trans "Including bonus / malus" %}{% trans "Add bonus / malus" %}
{{ forloop.counter }}{{ participation.team }}{{ note.note|floatformat }}{% if note.tweak >= 0 %}+{% endif %}{{ note.tweak }} + +
+
+ +
+{% endblock %} diff --git a/participation/urls.py b/participation/urls.py index 23b8884..d58167a 100644 --- a/participation/urls.py +++ b/participation/urls.py @@ -11,7 +11,8 @@ from .views import CreateTeamView, FinalNotationSheetTemplateView, JoinTeamView, ScaleNotationSheetTemplateView, SolutionsDownloadView, SolutionUploadView, SynthesisUploadView, \ TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \ TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \ - TournamentListView, TournamentPaymentsView, TournamentPublishNotesView, TournamentUpdateView + TournamentHarmonizeView, TournamentHarmonizeNoteView, TournamentListView, TournamentPaymentsView, \ + TournamentPublishNotesView, TournamentUpdateView app_name = "participation" @@ -47,6 +48,10 @@ urlpatterns = [ name="tournament_notation_sheets"), path("tournament//publish-notes//", TournamentPublishNotesView.as_view(), name="tournament_publish_notes"), + path("tournament//harmonize//", TournamentHarmonizeView.as_view(), + name="tournament_harmonize"), + path("tournament//harmonize////", TournamentHarmonizeNoteView.as_view(), + name="tournament_harmonize_note"), 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/participation/views.py b/participation/views.py index 2af476c..b6fb4e4 100644 --- a/participation/views.py +++ b/participation/views.py @@ -9,6 +9,7 @@ from tempfile import mkdtemp from typing import Any, Dict from zipfile import ZipFile +import gspread from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin @@ -41,7 +42,7 @@ 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 .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable @@ -710,6 +711,81 @@ class TournamentPublishNotesView(VolunteerMixin, SingleObjectMixin, RedirectView return reverse_lazy("participation:tournament_detail", args=(kwargs['pk'],)) +class TournamentHarmonizeView(VolunteerMixin, DetailView): + """ + Harmonize the notes of a tournament. + """ + model = Tournament + template_name = "participation/tournament_harmonize.html" + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + tournament = self.get_object() + reg = request.user.registration + if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()): + return self.handle_no_permission() + if self.kwargs['round'] not in (1, 2): + raise Http404 + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + tournament = self.get_object() + context['round'] = self.kwargs['round'] + context['pools'] = tournament.pools.filter(round=context["round"]).all() + context['title'] = _("Harmonize notes of {tournament} - Day {round}") \ + .format(tournament=tournament, round=context["round"]) + + notes = dict() + for participation in self.object.participations.all(): + note = sum(pool.average(participation) for pool in context['pools']) + tweak = sum(tweak.diff for tweak in participation.tweaks.filter(pool__in=context['pools']).all()) + notes[participation] = {'note': note, 'tweak': tweak} + context["notes"] = sorted(notes.items(), key=lambda x: x[1]['note'], reverse=True) + + return context + + +class TournamentHarmonizeNoteView(VolunteerMixin, DetailView): + model = Tournament + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + tournament = self.get_object() + reg = request.user.registration + if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()): + return self.handle_no_permission() + if self.kwargs['round'] not in (1, 2) or self.kwargs['action'] not in ('add', 'remove') \ + or self.kwargs['trigram'] not in [p.team.trigram for p in tournament.participations.all()]: + raise Http404 + return super().dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + tournament = self.get_object() + participation = tournament.participations.get(team__trigram=kwargs['trigram']) + pool = tournament.pools.get(round=kwargs['round'], participations=participation) + tweak_qs = Tweak.objects.filter(participation=participation, pool=pool) + old_diff = tweak_qs.first().diff if tweak_qs.exists() else 0 + new_diff = old_diff + (1 if kwargs['action'] == 'add' else -1) + if new_diff == 0: + tweak_qs.delete() + else: + tweak_qs.update_or_create(defaults={'diff': new_diff}, + create_defaults={'diff': new_diff, 'participation': participation, 'pool': pool}) + + gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) + spreadsheet = gc.open_by_key(tournament.notes_sheet_id) + worksheet = spreadsheet.worksheet("Classement final") + column = 3 if kwargs['round'] == '1' else 5 + row = worksheet.find(f"{participation.team.name} ({participation.team.trigram})", in_column=1).row + worksheet.update_cell(row, column, new_diff) + + return redirect(reverse_lazy("participation:tournament_harmonize", args=(tournament.pk, kwargs['round'],))) + + class SolutionUploadView(LoginRequiredMixin, FormView): template_name = "participation/upload_solution.html" form_class = SolutionForm