diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index dc81d08..b5f7ac5 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-24 11:26+0100\n" +"POT-Creation-Date: 2024-03-24 15:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emmy D'Anello \n" "Language-Team: LANGUAGE \n" @@ -214,17 +214,17 @@ msgid "Pool {letter}{number}" msgstr "Poule {letter}{number}" #: draw/models.py:408 draw/models.py:435 participation/admin.py:136 -#: participation/admin.py:155 participation/models.py:583 -#: participation/models.py:592 participation/tables.py:84 +#: participation/admin.py:155 participation/models.py:597 +#: participation/models.py:606 participation/tables.py:84 msgid "pool" msgstr "poule" -#: draw/models.py:409 participation/models.py:584 +#: draw/models.py:409 participation/models.py:598 msgid "pools" msgstr "poules" -#: draw/models.py:421 participation/models.py:503 participation/models.py:753 -#: participation/models.py:783 participation/models.py:821 +#: draw/models.py:421 participation/models.py:503 participation/models.py:767 +#: participation/models.py:797 participation/models.py:835 msgid "participation" msgstr "participation" @@ -248,8 +248,8 @@ msgid "" msgstr "" "L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1." -#: draw/models.py:458 draw/models.py:481 participation/models.py:606 -#: participation/models.py:790 +#: draw/models.py:458 draw/models.py:481 participation/models.py:620 +#: participation/models.py:804 #, python-brace-format msgid "Problem #{problem}" msgstr "Problème n°{problem}" @@ -456,21 +456,21 @@ msgid "selected for final" msgstr "sélectionnée pour la finale" #: participation/admin.py:124 participation/admin.py:183 -#: participation/models.py:613 participation/tables.py:112 +#: participation/models.py:627 participation/tables.py:112 msgid "defender" msgstr "défenseur⋅se" -#: participation/admin.py:128 participation/models.py:620 -#: participation/models.py:833 +#: participation/admin.py:128 participation/models.py:634 +#: participation/models.py:847 msgid "opponent" msgstr "opposant⋅e" -#: participation/admin.py:132 participation/models.py:627 -#: participation/models.py:834 +#: participation/admin.py:132 participation/models.py:641 +#: participation/models.py:848 msgid "reporter" msgstr "rapporteur⋅e" -#: participation/admin.py:187 participation/models.py:788 +#: participation/admin.py:187 participation/models.py:802 msgid "problem" msgstr "numéro de problème" @@ -864,19 +864,23 @@ msgstr "Tour {round}" msgid "juries" msgstr "jurys" -#: participation/models.py:550 +#: participation/models.py:552 +msgid "president of the jury" +msgstr "président⋅e du jury" + +#: participation/models.py:559 msgid "BigBlueButton URL" msgstr "Lien BigBlueButton" -#: participation/models.py:551 +#: participation/models.py:560 msgid "The link of the BBB visio for this pool." msgstr "Le lien du salon BBB pour cette poule." -#: participation/models.py:556 +#: participation/models.py:565 msgid "results available" msgstr "résultats disponibles" -#: participation/models.py:557 +#: participation/models.py:566 msgid "" "Check this case when results become accessible to teams. They stay " "accessible to you. Only averages are given." @@ -885,28 +889,32 @@ msgstr "" "Ils restent toujours accessibles pour vous. Seules les moyennes sont " "communiquées." -#: participation/models.py:577 +#: participation/models.py:587 +msgid "The president of the jury must be part of the jury." +msgstr "Læ président⋅e du jury doit faire partie du jury." + +#: participation/models.py:591 #, python-brace-format msgid "Pool of day {round} for tournament {tournament} with teams {teams}" msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}" -#: participation/models.py:597 +#: participation/models.py:611 msgid "position" msgstr "position" -#: participation/models.py:604 +#: participation/models.py:618 msgid "defended solution" msgstr "solution défendue" -#: participation/models.py:637 +#: participation/models.py:651 msgid "observer" msgstr "observateur⋅rice" -#: participation/models.py:642 +#: participation/models.py:656 msgid "penalties" msgstr "pénalités" -#: participation/models.py:644 +#: participation/models.py:658 msgid "" "Number of penalties for the defender. The defender will loose a 0.5 " "coefficient per penalty." @@ -914,124 +922,124 @@ msgstr "" "Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 " "sur sa présentation orale par pénalité." -#: participation/models.py:720 participation/models.py:723 -#: participation/models.py:726 participation/models.py:729 +#: participation/models.py:734 participation/models.py:737 +#: participation/models.py:740 participation/models.py:743 #, python-brace-format msgid "Team {trigram} is not registered in the pool." msgstr "L'équipe {trigram} n'est pas inscrite dans la poule." -#: participation/models.py:734 +#: participation/models.py:748 #, python-brace-format msgid "Passage of {defender} for problem {problem}" msgstr "Passage de {defender} pour le problème {problem}" -#: participation/models.py:738 participation/models.py:747 -#: participation/models.py:828 participation/models.py:870 +#: participation/models.py:752 participation/models.py:761 +#: participation/models.py:842 participation/models.py:884 msgid "passage" msgstr "passage" -#: participation/models.py:739 +#: participation/models.py:753 msgid "passages" msgstr "passages" -#: participation/models.py:758 +#: participation/models.py:772 msgid "difference" msgstr "différence" -#: participation/models.py:759 +#: participation/models.py:773 msgid "Score to add/remove on the final score" msgstr "Score à ajouter/retrancher au score final" -#: participation/models.py:766 +#: participation/models.py:780 msgid "tweak" msgstr "harmonisation" -#: participation/models.py:767 +#: participation/models.py:781 msgid "tweaks" msgstr "harmonisations" -#: participation/models.py:795 +#: participation/models.py:809 msgid "solution for the final tournament" msgstr "solution pour la finale" -#: participation/models.py:800 participation/models.py:839 +#: participation/models.py:814 participation/models.py:853 msgid "file" msgstr "fichier" -#: participation/models.py:806 +#: participation/models.py:820 #, python-brace-format msgid "Solution of team {team} for problem {problem}" msgstr "Solution de l'équipe {team} pour le problème {problem}" -#: participation/models.py:808 +#: participation/models.py:822 msgid "for final" msgstr "pour la finale" -#: participation/models.py:811 +#: participation/models.py:825 msgid "solution" msgstr "solution" -#: participation/models.py:812 +#: participation/models.py:826 msgid "solutions" msgstr "solutions" -#: participation/models.py:845 +#: participation/models.py:859 #, python-brace-format msgid "Synthesis of {team} as {type} for problem {problem} of {defender}" msgstr "" "Note de synthèse de l'équipe {team} en tant que {type} pour le problème " "{problem} de {defender}" -#: participation/models.py:853 +#: participation/models.py:867 msgid "synthesis" msgstr "note de synthèse" -#: participation/models.py:854 +#: participation/models.py:868 msgid "syntheses" msgstr "notes de synthèse" -#: participation/models.py:863 +#: participation/models.py:877 msgid "jury" msgstr "jury" -#: participation/models.py:875 +#: participation/models.py:889 msgid "defender writing note" msgstr "note d'écrit de la défense" -#: participation/models.py:881 +#: participation/models.py:895 msgid "defender oral note" msgstr "note d'oral de la défense" -#: participation/models.py:887 +#: participation/models.py:901 msgid "opponent writing note" msgstr "note d'écrit de l'opposition" -#: participation/models.py:893 +#: participation/models.py:907 msgid "opponent oral note" msgstr "note d'oral de l'opposition" -#: participation/models.py:899 +#: participation/models.py:913 msgid "reporter writing note" msgstr "note d'écrit du rapportage" -#: participation/models.py:905 +#: participation/models.py:919 msgid "reporter oral note" msgstr "note d'oral du rapportage" -#: participation/models.py:911 +#: participation/models.py:925 msgid "observer note" msgstr "note de l'observation" -#: participation/models.py:944 +#: participation/models.py:958 #, python-brace-format msgid "Notes of {jury} for {passage}" msgstr "Notes de {jury} pour le {passage}" -#: participation/models.py:951 +#: participation/models.py:961 msgid "note" msgstr "note" -#: participation/models.py:952 +#: participation/models.py:962 msgid "notes" msgstr "notes" @@ -1357,7 +1365,41 @@ msgstr "Modifier la poule" msgid "Upload notes" msgstr "Envoyer les notes" -#: participation/templates/participation/pool_jury.html:44 +#: participation/templates/participation/pool_jury.html:9 +msgid "" +"On this page, you can manage the juries of the pool. You can add a new jury " +"by entering the email address of the jury. If the jury is not registered, " +"the account will be created automatically. If the jury already exists, its " +"account will be autocompleted and directly linked to the pool." +msgstr "" +"Sur cette page, vous pouvez gérer les juré⋅es de la poule. Vous pouvez " +"ajouter un⋅e juré⋅e en entrant son adresse e-mail. Si læ juré⋅e n'est pas " +"inscrit⋅e, le compte sera créé automatiquement. Si læ juré⋅e existe déjà, " +"son compte sera autocomplété et directement lié à la poule." + +#: participation/templates/participation/pool_jury.html:17 +msgid "" +"On this page, you can also define the president of the jury, who will have " +"the right to see all solutions and if necessary define the notes of other " +"jury members." +msgstr "" +"Sur cette page, vous pouvez aussi définir læ président⋅e du jury, qui aura le " +"droit de voir toutes les solutions et si nécessaire définir les notes des " +"autres membres du jury." + +#: participation/templates/participation/pool_jury.html:41 +msgid "PoJ" +msgstr "PDJ" + +#: participation/templates/participation/pool_jury.html:46 +msgid "Preside" +msgstr "Présider" + +#: participation/templates/participation/pool_jury.html:51 +msgid "Remove" +msgstr "Retirer" + +#: participation/templates/participation/pool_jury.html:74 msgid "Back to pool detail" msgstr "Retour aux détails de la poule" @@ -1789,15 +1831,20 @@ msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !" msgid "The jury {name} has been successfully removed!" msgstr "{name} a été retiré⋅e avec succès du jury !" -#: participation/views.py:911 +#: participation/views.py:906 +#, 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:934 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:925 +#: participation/views.py:948 msgid "Notes were successfully uploaded." msgstr "Les notes ont bien été envoyées." -#: participation/views.py:1589 +#: participation/views.py:1612 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." diff --git a/participation/admin.py b/participation/admin.py index 579f727..d3c22f6 100644 --- a/participation/admin.py +++ b/participation/admin.py @@ -43,7 +43,7 @@ class SynthesisInline(admin.TabularInline): class PoolInline(admin.TabularInline): model = Pool extra = 0 - autocomplete_fields = ('tournament', 'participations', 'juries',) + autocomplete_fields = ('tournament', 'participations', 'jury_president', 'juries',) show_change_link = True @@ -100,10 +100,10 @@ class ParticipationAdmin(admin.ModelAdmin): @admin.register(Pool) class PoolAdmin(admin.ModelAdmin): - list_display = ('__str__', 'tournament', 'round', 'letter', 'teams',) + list_display = ('__str__', 'tournament', 'round', 'letter', 'teams', 'jury_president',) list_filter = ('tournament', 'round', 'letter',) search_fields = ('participations__team__name', 'participations__team__trigram',) - autocomplete_fields = ('tournament', 'participations', 'juries',) + autocomplete_fields = ('tournament', 'participations', 'jury_president', 'juries',) inlines = (PassageInline, TweakInline,) @admin.display(description=_("teams")) diff --git a/participation/forms.py b/participation/forms.py index 982c8d5..06d37d1 100644 --- a/participation/forms.py +++ b/participation/forms.py @@ -218,19 +218,19 @@ class AddJuryForm(forms.ModelForm): Div( Div( Field('email', autofocus="autofocus", list="juries-email"), - css_class='col-md-5', + css_class='col-md-5 px-1', ), Div( Field('first_name', list="juries-first-name"), - css_class='col-md-3', + css_class='col-md-3 px-1', ), Div( Field('last_name', list="juries-last-name"), - css_class='col-md-3', + css_class='col-md-3 px-1', ), Div( Submit('submit', _("Add")), - css_class='col-md-1 py-md-4', + css_class='col-md-1 py-md-4 px-1', ), css_class='row', ) diff --git a/participation/migrations/0009_pool_jury_president.py b/participation/migrations/0009_pool_jury_president.py new file mode 100644 index 0000000..fabd2b6 --- /dev/null +++ b/participation/migrations/0009_pool_jury_president.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.2 on 2024-03-24 14:31 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("participation", "0008_alter_participation_options"), + ("registration", "0012_payment_token_alter_payment_type"), + ] + + operations = [ + migrations.AddField( + model_name="pool", + name="jury_president", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="pools_presided", + to="registration.volunteerregistration", + verbose_name="president of the jury", + ), + ), + ] diff --git a/participation/models.py b/participation/models.py index 3f6f28b..4617e1a 100644 --- a/participation/models.py +++ b/participation/models.py @@ -8,7 +8,7 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator from django.db import models -from django.db.models import Index +from django.db.models import F, Index, Q from django.urls import reverse_lazy from django.utils import timezone from django.utils.crypto import get_random_string @@ -543,6 +543,15 @@ class Pool(models.Model): verbose_name=_("juries"), ) + jury_president = models.ForeignKey( + VolunteerRegistration, + on_delete=models.SET_NULL, + null=True, + default=None, + related_name="pools_presided", + verbose_name=_("president of the jury"), + ) + bbb_url = models.CharField( max_length=255, blank=True, @@ -573,6 +582,11 @@ class Pool(models.Model): def get_absolute_url(self): return reverse_lazy("participation:pool_detail", args=(self.pk,)) + def validate_constraints(self, exclude=None): + if self.jury_president not in self.juries.all(): + raise ValidationError({'jury_president': _("The president of the jury must be part of the jury.")}) + return super().validate_constraints() + def __str__(self): return _("Pool of day {round} for tournament {tournament} with teams {teams}")\ .format(round=self.round, diff --git a/participation/templates/participation/pool_jury.html b/participation/templates/participation/pool_jury.html index 61d75ce..80ba846 100644 --- a/participation/templates/participation/pool_jury.html +++ b/participation/templates/participation/pool_jury.html @@ -4,23 +4,53 @@ {% load i18n %} {% block content %} +
+

+ {% blocktrans trimmed %} + On this page, you can manage the juries of the pool. You can add a new jury by entering the email address + of the jury. If the jury is not registered, the account will be created automatically. If the jury already + exists, its account will be autocompleted and directly linked to the pool. + {% endblocktrans %} +

+ +

+ {% blocktrans trimmed %} + On this page, you can also define the president of the jury, who will have the right to see all solutions + and if necessary define the notes of other jury members. + {% endblocktrans %} +

+
+
{% for jury in pool.juries.all %} -
-
+
+
-
+
-
+
-
- - Retirer - +
+
+ {% if jury == pool.jury_president %} + + {% else %} + + {% trans "Preside" %} + + {% endif %} + + {% trans "Remove" %} + +
{% endfor %} diff --git a/participation/urls.py b/participation/urls.py index 959b2f9..1ddbb0e 100644 --- a/participation/urls.py +++ b/participation/urls.py @@ -7,10 +7,10 @@ from django.views.generic import TemplateView from .views import CreateTeamView, FinalNotationSheetTemplateView, JoinTeamView, MyParticipationDetailView, \ MyTeamDetailView, NoteUpdateView, ParticipationDetailView, PassageCreateView, PassageDetailView, \ PassageUpdateView, PoolCreateView, PoolDetailView, PoolDownloadView, PoolJuryView, PoolNotesTemplateView, \ - PoolRemoveJuryView, PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, ScaleNotationSheetTemplateView, \ - SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \ - TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, \ - TournamentExportCSVView, TournamentListView, TournamentPaymentsView, TournamentUpdateView + PoolPresideJuryView, PoolRemoveJuryView, PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, \ + ScaleNotationSheetTemplateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, TeamDetailView, \ + TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \ + TournamentDetailView, TournamentExportCSVView, TournamentListView, TournamentPaymentsView, TournamentUpdateView app_name = "participation" @@ -45,6 +45,7 @@ urlpatterns = [ path("pools//update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"), path("pools//jury/", PoolJuryView.as_view(), name="pool_jury"), path("pools//jury/remove//", PoolRemoveJuryView.as_view(), name="pool_remove_jury"), + path("pools//jury/preside//", PoolPresideJuryView.as_view(), name="pool_preside"), path("pools//upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"), path("pools//upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"), path("pools/passages/add//", PassageCreateView.as_view(), name="passage_create"), diff --git a/participation/views.py b/participation/views.py index 024b461..7db55f4 100644 --- a/participation/views.py +++ b/participation/views.py @@ -885,6 +885,29 @@ class PoolRemoveJuryView(VolunteerMixin, DetailView): return redirect(reverse_lazy('participation:pool_jury', args=(pool.pk,))) +class PoolPresideJuryView(VolunteerMixin, 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.is_volunteer \ + and self.get_object().tournament in request.user.registration.organized_tournaments.all(): + return super().dispatch(request, *args, **kwargs) + return self.handle_no_permission() + + def get(self, request, *args, **kwargs): + pool = self.get_object() + if not pool.juries.filter(pk=kwargs['jury_id']).exists(): + raise Http404 + jury = pool.juries.get(pk=kwargs['jury_id']) + pool.jury_president = jury + pool.save() + messages.success(request, _("The jury {name} has been successfully promoted president!") + .format(name=f"{jury.user.first_name} {jury.user.last_name}")) + return redirect(reverse_lazy('participation:pool_jury', args=(pool.pk,))) + + class PoolUploadNotesView(VolunteerMixin, FormView, DetailView): model = Pool form_class = UploadNotesForm