From d18f76cf8044c54a5f0f8a718fb505eb5861910a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 15 May 2022 12:23:17 +0200 Subject: [PATCH] Upload notes from a CSV sheet --- apps/participation/forms.py | 68 ++++ apps/participation/models.py | 9 + .../templates/participation/pool_detail.html | 12 + .../templates/participation/upload_notes.html | 14 + apps/participation/urls.py | 8 +- apps/participation/views.py | 41 ++- locale/fr/LC_MESSAGES/django.po | 291 ++++++++++-------- 7 files changed, 311 insertions(+), 132 deletions(-) create mode 100644 apps/participation/templates/participation/upload_notes.html diff --git a/apps/participation/forms.py b/apps/participation/forms.py index f2912bc..bcd2751 100644 --- a/apps/participation/forms.py +++ b/apps/participation/forms.py @@ -1,11 +1,16 @@ # Copyright (C) 2020 by Animath # SPDX-License-Identifier: GPL-3.0-or-later +import csv import re +from io import StringIO +from typing import Iterable from bootstrap_datepicker_plus.widgets import DatePickerInput, DateTimePickerInput from django import forms +from django.contrib.auth.models import User from django.core.exceptions import ValidationError +from django.core.validators import FileExtensionValidator from django.utils import formats from django.utils.translation import gettext_lazy as _ from PyPDF3 import PdfFileReader @@ -190,6 +195,69 @@ class PoolTeamsForm(forms.ModelForm): } +class UploadNotesForm(forms.Form): + file = forms.FileField( + label=_("CSV file:"), + validators=[FileExtensionValidator(allowed_extensions=["csv"])], + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['file'].widget.attrs['accept'] = 'text/csv' + + def clean(self): + cleaned_data = super().clean() + + if 'file' in cleaned_data: + file = cleaned_data['file'] + with file: + try: + csvfile = csv.reader(StringIO(file.read().decode())) + except UnicodeDecodeError: + self.add_error('file', _("This file contains non-UTF-8 content. " + "Please send your sheet as a CSV file.")) + + self.process(csvfile, cleaned_data) + + return cleaned_data + + def process(self, csvfile: Iterable[str], cleaned_data: dict): + parsed_notes = {} + for line in csvfile: + line = [s for s in line if s] + if len(line) < 19: + continue + name = line[0] + notes = line[1:19] + if not all(s.isnumeric() for s in notes): + continue + notes = list(map(int, notes)) + if max(notes) < 3 or min(notes) < 0: + continue + + max_notes = 3 * [20, 16, 9, 10, 9, 10] + for n, max_n in zip(notes, max_notes): + if n > max_n: + self.add_error('file', + _("The following note is higher of the maximum expected value:") + + str(n) + " > " + str(max_n)) + + first_name, last_name = tuple(name.split(' ', 1)) + + jury = User.objects.filter(first_name=first_name, last_name=last_name) + if jury.count() != 1: + self.form.add_error('file', _("The following user was not found:") + " " + name) + continue + jury = jury.get() + + vr = jury.registration + parsed_notes[vr] = notes + + cleaned_data['parsed_notes'] = parsed_notes + + return cleaned_data + + class PassageForm(forms.ModelForm): def clean(self): cleaned_data = super().clean() diff --git a/apps/participation/models.py b/apps/participation/models.py index d032cba..363b3dd 100644 --- a/apps/participation/models.py +++ b/apps/participation/models.py @@ -654,6 +654,15 @@ class Note(models.Model): default=0, ) + def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int, + reporter_writing: int, reporter_oral: int): + self.defender_writing = defender_writing + self.defender_oral = defender_oral + self.opponent_writing = opponent_writing + self.opponent_oral = opponent_oral + self.reporter_writing = reporter_writing + self.reporter_oral = reporter_oral + def get_absolute_url(self): return reverse_lazy("participation:passage_detail", args=(self.passage.pk,)) diff --git a/apps/participation/templates/participation/pool_detail.html b/apps/participation/templates/participation/pool_detail.html index 7f5662d..f80cac9 100644 --- a/apps/participation/templates/participation/pool_detail.html +++ b/apps/participation/templates/participation/pool_detail.html @@ -54,6 +54,7 @@ + {% endif %} @@ -78,6 +79,11 @@ {% trans "Update" as modal_button %} {% url "participation:pool_update_teams" pk=pool.pk as modal_action %} {% include "base_modal.html" with modal_id="updateTeams" %} + + {% trans "Upload notes" as modal_title %} + {% trans "Upload" as modal_button %} + {% url "participation:pool_upload_notes" pk=pool.pk as modal_action %} + {% include "base_modal.html" with modal_id="uploadNotes" modal_button_type="success" modal_enctype="multipart/form-data" %} {% endblock %} {% block extrajavascript %} @@ -100,6 +106,12 @@ if (!modalBody.html().trim()) modalBody.load("{% url "participation:passage_create" pk=pool.pk %} #form-content") }); + + $('button[data-target="#uploadNotesModal"]').click(function() { + let modalBody = $("#uploadNotesModal div.modal-body"); + if (!modalBody.html().trim()) + modalBody.load("{% url "participation:pool_upload_notes" pk=pool.pk %} #form-content") + }); }); {% endblock %} diff --git a/apps/participation/templates/participation/upload_notes.html b/apps/participation/templates/participation/upload_notes.html new file mode 100644 index 0000000..2e88851 --- /dev/null +++ b/apps/participation/templates/participation/upload_notes.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} +{% load i18n %} + +{% block content %} +
+
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+{% endblock %} diff --git a/apps/participation/urls.py b/apps/participation/urls.py index 369a677..d37abb3 100644 --- a/apps/participation/urls.py +++ b/apps/participation/urls.py @@ -6,9 +6,10 @@ from django.views.generic import TemplateView from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTeamDetailView, NoteUpdateView, \ ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \ - PoolUpdateTeamsView, PoolUpdateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, \ - TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \ - TournamentDetailView, TournamentExportCSVView, TournamentListView, TournamentUpdateView + PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, SolutionUploadView, SynthesisUploadView,\ + TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \ + TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \ + TournamentListView, TournamentUpdateView app_name = "participation" @@ -36,6 +37,7 @@ urlpatterns = [ path("pools//", PoolDetailView.as_view(), name="pool_detail"), path("pools//update/", PoolUpdateView.as_view(), name="pool_update"), path("pools//update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"), + path("pools//upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"), path("pools/passages/add//", PassageCreateView.as_view(), name="passage_create"), path("pools/passages//", PassageDetailView.as_view(), name="passage_detail"), path("pools/passages//update/", PassageUpdateView.as_view(), name="passage_update"), diff --git a/apps/participation/views.py b/apps/participation/views.py index b04e718..e10ab8a 100644 --- a/apps/participation/views.py +++ b/apps/participation/views.py @@ -6,6 +6,7 @@ import os from zipfile import ZipFile from django.conf import settings +from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.sites.models import Site from django.core.exceptions import PermissionDenied @@ -18,6 +19,7 @@ from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, FormView, RedirectView, TemplateView, UpdateView, View +from django.views.generic.detail import SingleObjectMixin from django.views.generic.edit import FormMixin, ProcessFormView from django_tables2 import SingleTableView from magic import Magic @@ -28,7 +30,7 @@ from tfjm.views import AdminMixin, VolunteerMixin from .forms import JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, PoolForm, \ PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \ - ValidateParticipationForm + UploadNotesForm, ValidateParticipationForm from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable @@ -703,6 +705,43 @@ class PoolUpdateTeamsView(VolunteerMixin, UpdateView): return self.handle_no_permission() +class PoolUploadNotesView(VolunteerMixin, FormView, DetailView): + model = Pool + form_class = UploadNotesForm + template_name = 'participation/upload_notes.html' + + def dispatch(self, request, *args, **kwargs): + self.object = self.get_object() + + if request.user.registration.is_admin or request.user.registration.is_volunteer \ + and (self.object.tournament in request.user.registration.organized_tournaments.all() + or request.user.registration in self.object.juries.all()): + return super().dispatch(request, *args, **kwargs) + + return self.handle_no_permission() + + @transaction.atomic + def form_valid(self, form): + pool = self.get_object() + parsed_notes = form.cleaned_data['parsed_notes'] + + for vr, notes in parsed_notes.items(): + if vr not in pool.juries.all(): + form.add_error('file', _("The following user is not registered as a jury:") + " " + str(vr)) + + for i, passage in enumerate(pool.passages.all()): + note = Note.objects.get(jury=vr, passage=passage) + passage_notes = notes[6 * i:6 * (i + 1)] + note.set_all(*passage_notes) + note.save() + + messages.success(self.request, _("Notes were successfully uploaded.")) + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy('participation:pool_detail', args=(self.kwargs['pk'],)) + + class PassageCreateView(VolunteerMixin, CreateView): model = Passage form_class = PassageForm diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index fe019db..d044ccb 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: 2022-04-26 15:09+0200\n" +"POT-Creation-Date: 2022-05-15 12:23+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,78 @@ msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" msgid "valid" msgstr "valide" -#: apps/participation/forms.py:25 +#: apps/participation/forms.py:30 msgid "This name is already used." msgstr "Ce nom est déjà utilisé." -#: apps/participation/forms.py:32 apps/participation/models.py:41 +#: apps/participation/forms.py:37 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:35 +#: apps/participation/forms.py:40 msgid "This trigram is already used." msgstr "Ce trigramme est déjà utilisé." -#: apps/participation/forms.py:50 +#: apps/participation/forms.py:55 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:79 apps/participation/forms.py:215 +#: apps/participation/forms.py:84 apps/participation/forms.py:283 #: 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:81 apps/registration/forms.py:119 +#: apps/participation/forms.py:86 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:99 +#: apps/participation/forms.py:104 msgid "I engage myself to participate to the whole TFJM²." msgstr "Je m'engage à participer à l'intégralité du TFJM²." -#: apps/participation/forms.py:114 +#: apps/participation/forms.py:119 msgid "Message to address to the team:" msgstr "Message à adresser à l'équipe :" -#: apps/participation/forms.py:152 +#: apps/participation/forms.py:157 msgid "The uploaded file size must be under 5 Mo." msgstr "Le fichier envoyé doit peser moins de 5 Mo." -#: apps/participation/forms.py:154 apps/participation/forms.py:217 +#: apps/participation/forms.py:159 apps/participation/forms.py:285 msgid "The uploaded file must be a PDF file." msgstr "Le fichier envoyé doit être au format PDF." -#: apps/participation/forms.py:158 +#: apps/participation/forms.py:163 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:198 +#: apps/participation/forms.py:200 +msgid "CSV file:" +msgstr "Tableur au format CSV :" + +#: apps/participation/forms.py:217 +msgid "" +"This file contains non-UTF-8 content. Please send your sheet as a CSV file." +msgstr "" +"Ce fichier contient des éléments non-UTF-8. Merci d'envoyer votre tableur au " +"format CSV." + +#: apps/participation/forms.py:242 +msgid "The following note is higher of the maximum expected value:" +msgstr "La note suivante est supérieure au maximum attendu :" + +#: apps/participation/forms.py:249 +msgid "The following user was not found:" +msgstr "L'utilisateur suivant n'a pas été trouvé :" + +#: apps/participation/forms.py:266 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:202 +#: apps/participation/forms.py:270 msgid "This defender did not work on this problem." msgstr "Ce défenseur ne travaille pas sur ce problème." @@ -203,7 +222,7 @@ msgstr "début" msgid "end" msgstr "fin" -#: apps/participation/models.py:147 apps/participation/models.py:408 +#: apps/participation/models.py:147 #: apps/participation/templates/participation/tournament_detail.html:18 msgid "place" msgstr "lieu" @@ -286,8 +305,8 @@ msgstr "L'équipe est sélectionnée pour la finale." msgid "Participation of the team {name} ({trigram})" msgstr "Participation de l'équipe {name} ({trigram})" -#: apps/participation/models.py:331 apps/participation/models.py:537 -#: apps/participation/models.py:577 +#: apps/participation/models.py:331 apps/participation/models.py:530 +#: apps/participation/models.py:568 msgid "participation" msgstr "participation" @@ -342,36 +361,32 @@ msgstr "poule" msgid "pools" msgstr "poules" -#: apps/participation/models.py:410 -msgid "Where the solution is presented?" -msgstr "Où est-ce que les solutions sont défendues ?" - -#: apps/participation/models.py:415 +#: apps/participation/models.py:408 msgid "defended solution" msgstr "solution défendue" -#: apps/participation/models.py:417 apps/participation/models.py:544 +#: apps/participation/models.py:410 apps/participation/models.py:537 #, python-brace-format msgid "Problem #{problem}" msgstr "Problème n°{problem}" -#: apps/participation/models.py:424 apps/participation/tables.py:106 +#: apps/participation/models.py:417 apps/participation/tables.py:106 msgid "defender" msgstr "défenseur" -#: apps/participation/models.py:431 apps/participation/models.py:589 +#: apps/participation/models.py:424 apps/participation/models.py:580 msgid "opponent" msgstr "opposant" -#: apps/participation/models.py:438 apps/participation/models.py:590 +#: apps/participation/models.py:431 apps/participation/models.py:581 msgid "reporter" msgstr "rapporteur" -#: apps/participation/models.py:443 +#: apps/participation/models.py:436 msgid "penalties" msgstr "pénalités" -#: apps/participation/models.py:445 +#: apps/participation/models.py:438 msgid "" "Number of penalties for the defender. The defender will loose a 0.5 " "coefficient per penalty." @@ -379,108 +394,108 @@ msgstr "" "Nombre de pénalités pour le défenseur. Le défenseur perd un coefficient 0.5 " "sur sa solution écrite par pénalité." -#: apps/participation/models.py:505 apps/participation/models.py:508 -#: apps/participation/models.py:511 +#: apps/participation/models.py:498 apps/participation/models.py:501 +#: apps/participation/models.py:504 #, python-brace-format msgid "Team {trigram} is not registered in the pool." msgstr "L'équipe {trigram} n'est pas inscrite dans la poule." -#: apps/participation/models.py:516 +#: apps/participation/models.py:509 #, python-brace-format msgid "Passage of {defender} for problem {problem}" msgstr "Passage de {defender} pour le problème {problem}" -#: apps/participation/models.py:520 apps/participation/models.py:584 -#: apps/participation/models.py:628 +#: apps/participation/models.py:513 apps/participation/models.py:575 +#: apps/participation/models.py:617 msgid "passage" msgstr "passage" -#: apps/participation/models.py:521 +#: apps/participation/models.py:514 msgid "passages" msgstr "passages" -#: apps/participation/models.py:542 +#: apps/participation/models.py:535 msgid "problem" msgstr "numéro de problème" -#: apps/participation/models.py:549 +#: apps/participation/models.py:542 msgid "solution for the final tournament" msgstr "solution pour la finale" -#: apps/participation/models.py:554 apps/participation/models.py:595 +#: apps/participation/models.py:547 apps/participation/models.py:586 msgid "file" msgstr "fichier" -#: apps/participation/models.py:562 +#: apps/participation/models.py:553 #, python-brace-format msgid "Solution of team {team} for problem {problem}" msgstr "Solution de l'équipe {team} pour le problème {problem}" -#: apps/participation/models.py:564 +#: apps/participation/models.py:555 msgid "for final" msgstr "pour la finale" -#: apps/participation/models.py:567 +#: apps/participation/models.py:558 msgid "solution" msgstr "solution" -#: apps/participation/models.py:568 +#: apps/participation/models.py:559 msgid "solutions" msgstr "solutions" -#: apps/participation/models.py:603 +#: apps/participation/models.py:592 #, 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}" -#: apps/participation/models.py:611 +#: apps/participation/models.py:600 msgid "synthesis" msgstr "note de synthèse" -#: apps/participation/models.py:612 +#: apps/participation/models.py:601 msgid "syntheses" msgstr "notes de synthèse" -#: apps/participation/models.py:621 +#: apps/participation/models.py:610 msgid "jury" msgstr "jury" -#: apps/participation/models.py:633 +#: apps/participation/models.py:622 msgid "defender writing note" msgstr "note d'écrit du défenseur" -#: apps/participation/models.py:639 +#: apps/participation/models.py:628 msgid "defender oral note" msgstr "note d'oral du défenseur" -#: apps/participation/models.py:645 +#: apps/participation/models.py:634 msgid "opponent writing note" msgstr "note d'écrit de l'opposant" -#: apps/participation/models.py:651 +#: apps/participation/models.py:640 msgid "opponent oral note" msgstr "note d'oral de l'opposant" -#: apps/participation/models.py:657 +#: apps/participation/models.py:646 msgid "reporter writing note" -msgstr "not d'écrit du rapporteur" +msgstr "note d'écrit du rapporteur" -#: apps/participation/models.py:663 +#: apps/participation/models.py:652 msgid "reporter oral note" msgstr "note d'oral du rapporteur" -#: apps/participation/models.py:672 +#: apps/participation/models.py:670 #, python-brace-format msgid "Notes of {jury} for {passage}" msgstr "Notes de {jury} pour le {passage}" -#: apps/participation/models.py:679 +#: apps/participation/models.py:677 msgid "note" msgstr "note" -#: apps/participation/models.py:680 +#: apps/participation/models.py:678 msgid "notes" msgstr "notes" @@ -555,12 +570,12 @@ msgid "Join" msgstr "Rejoindre" #: apps/participation/templates/participation/note_form.html:11 -#: apps/participation/templates/participation/passage_detail.html:49 -#: apps/participation/templates/participation/passage_detail.html:105 -#: apps/participation/templates/participation/passage_detail.html:111 +#: apps/participation/templates/participation/passage_detail.html:46 +#: apps/participation/templates/participation/passage_detail.html:102 +#: apps/participation/templates/participation/passage_detail.html:108 #: apps/participation/templates/participation/pool_detail.html:55 -#: apps/participation/templates/participation/pool_detail.html:73 -#: apps/participation/templates/participation/pool_detail.html:78 +#: apps/participation/templates/participation/pool_detail.html:74 +#: apps/participation/templates/participation/pool_detail.html:79 #: apps/participation/templates/participation/team_detail.html:113 #: apps/participation/templates/participation/team_detail.html:177 #: apps/participation/templates/participation/tournament_form.html:12 @@ -623,9 +638,11 @@ msgid "Upload solution" msgstr "Envoyer une solution" #: apps/participation/templates/participation/participation_detail.html:59 -#: apps/participation/templates/participation/passage_detail.html:117 +#: apps/participation/templates/participation/passage_detail.html:114 +#: apps/participation/templates/participation/pool_detail.html:84 #: apps/participation/templates/participation/team_detail.html:172 #: apps/participation/templates/participation/upload_motivation_letter.html:13 +#: apps/participation/templates/participation/upload_notes.html:12 #: apps/participation/templates/participation/upload_solution.html:11 #: apps/participation/templates/participation/upload_synthesis.html:16 #: apps/registration/templates/registration/upload_health_sheet.html:17 @@ -659,72 +676,68 @@ msgid "Defended solution:" msgstr "Solution défendue" #: apps/participation/templates/participation/passage_detail.html:28 -msgid "Place:" -msgstr "Lieu :" - -#: apps/participation/templates/participation/passage_detail.html:31 msgid "Defender penalties count:" msgstr "Nombre de pénalités :" -#: apps/participation/templates/participation/passage_detail.html:34 +#: apps/participation/templates/participation/passage_detail.html:31 msgid "Syntheses:" msgstr "Notes de synthèse :" -#: apps/participation/templates/participation/passage_detail.html:39 +#: apps/participation/templates/participation/passage_detail.html:36 msgid "No synthesis was uploaded yet." msgstr "Aucune note de synthèse n'a encore été envoyée." -#: apps/participation/templates/participation/passage_detail.html:47 -#: apps/participation/templates/participation/passage_detail.html:110 +#: apps/participation/templates/participation/passage_detail.html:44 +#: apps/participation/templates/participation/passage_detail.html:107 msgid "Update notes" msgstr "Modifier les notes" -#: apps/participation/templates/participation/passage_detail.html:53 -#: apps/participation/templates/participation/passage_detail.html:116 +#: apps/participation/templates/participation/passage_detail.html:50 +#: apps/participation/templates/participation/passage_detail.html:113 msgid "Upload synthesis" msgstr "Envoyer une note de synthèse" -#: apps/participation/templates/participation/passage_detail.html:61 +#: apps/participation/templates/participation/passage_detail.html:58 msgid "Notes detail" msgstr "Détails des notes" -#: apps/participation/templates/participation/passage_detail.html:68 +#: apps/participation/templates/participation/passage_detail.html:65 msgid "Average points for the defender writing:" msgstr "Moyenne de l'écrit du défenseur :" -#: apps/participation/templates/participation/passage_detail.html:71 +#: apps/participation/templates/participation/passage_detail.html:68 msgid "Average points for the defender oral:" msgstr "Moyenne de l'oral du défenseur :" -#: apps/participation/templates/participation/passage_detail.html:74 +#: apps/participation/templates/participation/passage_detail.html:71 msgid "Average points for the opponent writing:" msgstr "Moyenne de l'écrit de l'opposant :" -#: apps/participation/templates/participation/passage_detail.html:77 +#: apps/participation/templates/participation/passage_detail.html:74 msgid "Average points for the opponent oral:" msgstr "Moyenne de l'oral de l'opposant :" -#: apps/participation/templates/participation/passage_detail.html:80 +#: apps/participation/templates/participation/passage_detail.html:77 msgid "Average points for the reporter writing:" msgstr "Moyenne de l'écrit du rapporteur :" -#: apps/participation/templates/participation/passage_detail.html:83 +#: apps/participation/templates/participation/passage_detail.html:80 msgid "Average points for the reporter oral:" msgstr "Moyenne de l'oral du rapporteur :" -#: apps/participation/templates/participation/passage_detail.html:90 +#: apps/participation/templates/participation/passage_detail.html:87 msgid "Defender points:" msgstr "Points du défenseur :" -#: apps/participation/templates/participation/passage_detail.html:93 +#: apps/participation/templates/participation/passage_detail.html:90 msgid "Opponent points:" msgstr "Points de l'opposant :" -#: apps/participation/templates/participation/passage_detail.html:96 +#: apps/participation/templates/participation/passage_detail.html:93 msgid "Reporter points:" msgstr "Points du rapporteur :" -#: apps/participation/templates/participation/passage_detail.html:104 +#: apps/participation/templates/participation/passage_detail.html:101 #: apps/participation/templates/participation/passage_form.html:11 msgid "Update passage" msgstr "Modifier le passage" @@ -755,29 +768,37 @@ msgid "Ranking" msgstr "Classement" #: apps/participation/templates/participation/pool_detail.html:54 -#: apps/participation/templates/participation/pool_detail.html:67 +#: apps/participation/templates/participation/pool_detail.html:68 msgid "Add passage" msgstr "Ajouter un passage" #: apps/participation/templates/participation/pool_detail.html:56 -#: apps/participation/templates/participation/pool_detail.html:77 +#: apps/participation/templates/participation/pool_detail.html:78 msgid "Update teams" msgstr "Modifier les équipes" -#: apps/participation/templates/participation/pool_detail.html:63 +#: apps/participation/templates/participation/pool_detail.html:57 +msgid "Upload notes from a CSV file" +msgstr "Soumettre les notes à partir d'un fichier CSV" + +#: apps/participation/templates/participation/pool_detail.html:64 msgid "Passages" msgstr "Passages" -#: apps/participation/templates/participation/pool_detail.html:68 +#: apps/participation/templates/participation/pool_detail.html:69 #: apps/participation/templates/participation/tournament_detail.html:109 msgid "Add" msgstr "Ajouter" -#: apps/participation/templates/participation/pool_detail.html:72 +#: apps/participation/templates/participation/pool_detail.html:73 #: apps/participation/templates/participation/pool_form.html:11 msgid "Update pool" msgstr "Modifier la poule" +#: apps/participation/templates/participation/pool_detail.html:83 +msgid "Upload notes" +msgstr "Envoyer les notes" + #: apps/participation/templates/participation/team_detail.html:13 msgid "Name:" msgstr "Nom :" @@ -901,7 +922,7 @@ msgid "Invalidate" msgstr "Invalider" #: apps/participation/templates/participation/team_detail.html:171 -#: apps/participation/views.py:327 +#: apps/participation/views.py:329 msgid "Upload motivation letter" msgstr "Envoyer la lettre de motivation" @@ -910,7 +931,7 @@ msgid "Update team" msgstr "Modifier l'équipe" #: apps/participation/templates/participation/team_detail.html:181 -#: apps/participation/views.py:430 +#: apps/participation/views.py:432 msgid "Leave team" msgstr "Quitter l'équipe" @@ -1022,49 +1043,49 @@ msgstr "Retour aux détails de l'utilisateur" msgid "Templates:" msgstr "Modèles :" -#: apps/participation/views.py:43 tfjm/templates/base.html:71 +#: apps/participation/views.py:45 tfjm/templates/base.html:71 #: tfjm/templates/base.html:230 msgid "Create team" msgstr "Créer une équipe" -#: apps/participation/views.py:52 apps/participation/views.py:97 +#: apps/participation/views.py:54 apps/participation/views.py:99 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." -#: apps/participation/views.py:54 apps/participation/views.py:99 +#: apps/participation/views.py:56 apps/participation/views.py:101 msgid "You are already in a team." msgstr "Vous êtes déjà dans une équipe." -#: apps/participation/views.py:88 tfjm/templates/base.html:76 +#: apps/participation/views.py:90 tfjm/templates/base.html:76 #: tfjm/templates/base.html:225 msgid "Join team" msgstr "Rejoindre une équipe" -#: apps/participation/views.py:150 apps/participation/views.py:436 -#: apps/participation/views.py:470 +#: apps/participation/views.py:152 apps/participation/views.py:438 +#: apps/participation/views.py:472 msgid "You are not in a team." msgstr "Vous n'êtes pas dans une équipe." -#: apps/participation/views.py:151 apps/participation/views.py:471 +#: apps/participation/views.py:153 apps/participation/views.py:473 msgid "You don't participate, so you don't have any team." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." -#: apps/participation/views.py:177 +#: apps/participation/views.py:179 #, python-brace-format msgid "Detail of team {trigram}" msgstr "Détails de l'équipe {trigram}" -#: apps/participation/views.py:213 +#: apps/participation/views.py:215 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." -#: apps/participation/views.py:216 +#: apps/participation/views.py:218 msgid "The validation of the team is already done or pending." msgstr "La validation de l'équipe est déjà faite ou en cours." -#: apps/participation/views.py:219 +#: apps/participation/views.py:221 msgid "" "The team can't be validated: missing email address confirmations, " "authorizations, people, motivation letter or the tournament is not set." @@ -1073,66 +1094,74 @@ msgstr "" "d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre " "de motivation, soit le tournoi n'a pas été choisi." -#: apps/participation/views.py:241 +#: apps/participation/views.py:243 msgid "You are not an organizer of the tournament." msgstr "Vous n'êtes pas un organisateur du tournoi." -#: apps/participation/views.py:244 +#: apps/participation/views.py:246 msgid "This team has no pending validation." msgstr "L'équipe n'a pas de validation en attente." -#: apps/participation/views.py:274 +#: apps/participation/views.py:276 msgid "You must specify if you validate the registration or not." msgstr "Vous devez spécifier si vous validez l'inscription ou non." -#: apps/participation/views.py:307 +#: apps/participation/views.py:309 #, python-brace-format msgid "Update team {trigram}" msgstr "Mise à jour de l'équipe {trigram}" -#: apps/participation/views.py:366 apps/participation/views.py:416 +#: apps/participation/views.py:368 apps/participation/views.py:418 #, python-brace-format msgid "Motivation letter of {team}.{ext}" msgstr "Lettre de motivation de {team}.{ext}" -#: apps/participation/views.py:397 +#: apps/participation/views.py:399 #, python-brace-format msgid "Photo authorization of {participant}.{ext}" msgstr "Autorisation de droit à l'image de {participant}.{ext}" -#: apps/participation/views.py:403 +#: apps/participation/views.py:405 #, python-brace-format msgid "Parental authorization of {participant}.{ext}" msgstr "Autorisation parentale de {participant}.{ext}" -#: apps/participation/views.py:410 +#: apps/participation/views.py:412 #, python-brace-format msgid "Health sheet of {participant}.{ext}" msgstr "Fiche sanitaire de {participant}.{ext}" -#: apps/participation/views.py:420 +#: apps/participation/views.py:422 #, 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:438 +#: apps/participation/views.py:440 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:485 +#: apps/participation/views.py:487 msgid "The team is not validated yet." msgstr "L'équipe n'est pas encore validée." -#: apps/participation/views.py:499 +#: apps/participation/views.py:501 #, python-brace-format msgid "Participation of team {trigram}" msgstr "Participation de l'équipe {trigram}" -#: apps/participation/views.py:625 +#: apps/participation/views.py:627 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:811 +#: apps/participation/views.py:724 +msgid "The following user is not registered as a jury:" +msgstr "L'utilisateur suivant n'est pas inscrit en tant que juré⋅e :" + +#: apps/participation/views.py:732 +msgid "Notes were successfully uploaded." +msgstr "Les notes ont bien été envoyées." + +#: apps/participation/views.py:844 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." @@ -1535,16 +1564,16 @@ msgstr "" #: apps/registration/templates/registration/payment_form.html:17 msgid "" -"You can pay with a credit card through our Hello Asso page. To make the validation of the " "payment easier, please use the same e-mail " "address that you use on this platform. The payment verification will " "be checked automatically under 10 minutes, you don't necessary need to fill " "this form." msgstr "" -"Vous pouvez payer par carte bancaire sur notre page Hello Asso. Pour rendre la validation du " "paiement plus facile, merci d'utiliser la même " "adresse e-mail que vous utilisez sur cette plateforme. La " @@ -1563,14 +1592,14 @@ msgstr "" #: apps/registration/templates/registration/payment_form.html:39 msgid "" -"If any payment mean is available to you, please contact us at contact@tfjm.org to find " -"a solution to your difficulties." +"If any payment mean is available to you, please contact us at contact@tfjm.org " +"to find a solution to your difficulties." msgstr "" "Si aucun moyen de paiement ne vous convient, merci de nous contecter à " -"l'adresse contact@tfjm.org pour que nous puissions trouver une solution à vos " -"difficultés." +"l'adresse contact@tfjm.org pour que nous puissions trouver une solution à " +"vos difficultés." #: apps/registration/templates/registration/signup.html:5 #: apps/registration/templates/registration/signup.html:12 @@ -1902,12 +1931,12 @@ msgstr "Déconnexion" #, python-format msgid "" "Your email address is not validated. Please click on the link you received " -"by email. You can resend a mail by clicking on this link." +"by email. You can resend a mail by clicking on this link." msgstr "" "Votre adresse mail n'est pas validée. Merci de cliquer sur le lien que vous " -"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur ce lien." +"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur ce lien." #: tfjm/templates/base.html:180 msgid "Contact us" @@ -1953,3 +1982,9 @@ msgstr "Résultats" #: tfjm/templates/search/search.html:25 msgid "No results found." msgstr "Aucun résultat." + +#~ msgid "Where the solution is presented?" +#~ msgstr "Où est-ce que les solutions sont défendues ?" + +#~ msgid "Place:" +#~ msgstr "Lieu :"