Add button to update notes

Add jury president field for pools

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-03-24 15:36:51 +01:00
parent 6fa3a08a72
commit 0b9079b431
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
8 changed files with 220 additions and 78 deletions

View File

@ -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 <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\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."

View File

@ -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"))

View File

@ -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',
)

View File

@ -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",
),
),
]

View File

@ -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,

View File

@ -4,23 +4,53 @@
{% load i18n %}
{% block content %}
<div class="alert alert-info">
<p>
{% 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 %}
</p>
<p>
{% 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 %}
</p>
</div>
<hr>
{% for jury in pool.juries.all %}
<div class="row my-3">
<div class="col-md-5">
<div class="row my-3 px-0">
<div class="col-md-5 px-1">
<input type="email" class="form-control" value="{{ jury.user.email }}" disabled>
</div>
<div class="col-md-3">
<div class="col-md-3 px-1">
<input type="text" class="form-control" value="{{ jury.user.first_name }}" disabled>
</div>
<div class="col-md-3">
<div class="col-md-3 px-1">
<input type="text" class="form-control" value="{{ jury.user.last_name }}" disabled>
</div>
<div class="col-md-1">
<a href="{% url 'participation:pool_remove_jury' pk=pool.pk jury_id=jury.id %}" class="btn btn-danger">
Retirer
</a>
<div class="col-md-1 px-1">
<div class="btn-group-vertical btn-group-sm">
{% if jury == pool.jury_president %}
<button class="btn btn-success">
<i class="fas fa-crown"></i> {% trans "PoJ" %}
</button>
{% else %}
<a href="{% url 'participation:pool_preside' pk=pool.pk jury_id=jury.id %}"
class="btn btn-warning">
<i class="fas fa-crown"></i> {% trans "Preside" %}
</a>
{% endif %}
<a href="{% url 'participation:pool_remove_jury' pk=pool.pk jury_id=jury.id %}"
class="btn btn-danger">
<i class="fas fa-trash"></i> {% trans "Remove" %}
</a>
</div>
</div>
</div>
{% endfor %}

View File

@ -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/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
path("pools/<int:pk>/jury/", PoolJuryView.as_view(), name="pool_jury"),
path("pools/<int:pk>/jury/remove/<int:jury_id>/", PoolRemoveJuryView.as_view(), name="pool_remove_jury"),
path("pools/<int:pk>/jury/preside/<int:jury_id>/", PoolPresideJuryView.as_view(), name="pool_preside"),
path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"),
path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"),
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),

View File

@ -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