diff --git a/apps/participation/apps.py b/apps/participation/apps.py index 4a8755a..23bd437 100644 --- a/apps/participation/apps.py +++ b/apps/participation/apps.py @@ -12,6 +12,8 @@ class ParticipationConfig(AppConfig): name = 'participation' def ready(self): - from participation.signals import create_team_participation, update_mailing_list + from participation.signals import create_notes, create_team_participation, update_mailing_list pre_save.connect(update_mailing_list, "participation.Team") post_save.connect(create_team_participation, "participation.Team") + post_save.connect(create_notes, "participation.Passage") + post_save.connect(create_notes, "participation.Pool") diff --git a/apps/participation/forms.py b/apps/participation/forms.py index 7b0ddfd..c6ef3bf 100644 --- a/apps/participation/forms.py +++ b/apps/participation/forms.py @@ -9,7 +9,7 @@ from django.core.exceptions import ValidationError from django.utils import formats from django.utils.translation import gettext_lazy as _ -from .models import Participation, Passage, Pool, Team, Tournament, Solution, Synthesis +from .models import Note, Participation, Passage, Pool, Team, Tournament, Solution, Synthesis class TeamForm(forms.ModelForm): @@ -173,3 +173,10 @@ class SynthesisForm(forms.ModelForm): class Meta: model = Synthesis fields = ('type', 'file',) + + +class NoteForm(forms.ModelForm): + class Meta: + model = Note + fields = ('defender_writing', 'defender_oral', 'opponent_writing', + 'opponent_oral', 'reporter_writing', 'reporter_oral', ) diff --git a/apps/participation/models.py b/apps/participation/models.py index 6b18e1a..461b33b 100644 --- a/apps/participation/models.py +++ b/apps/participation/models.py @@ -379,7 +379,8 @@ class Passage(models.Model): final_solution=self.pool.tournament.final) def avg(self, iterator) -> int: - return sum(iterator) / len(list(iterator)) + items = [i for i in iterator if i] + return sum(items) / len(items) if items else 0 @property def average_defender_writing(self): @@ -581,6 +582,9 @@ class Note(models.Model): default=0, ) + def get_absolute_url(self): + return reverse_lazy("participation:passage_detail", args=(self.passage.pk,)) + def __str__(self): return _("Notes of {jury} for {passage}").format(jury=self.jury, passage=self.passage) diff --git a/apps/participation/signals.py b/apps/participation/signals.py index ac391a4..680f1f6 100644 --- a/apps/participation/signals.py +++ b/apps/participation/signals.py @@ -1,7 +1,8 @@ # Copyright (C) 2020 by Animath # SPDX-License-Identifier: GPL-3.0-or-later +from typing import Union -from participation.models import Participation, Team +from participation.models import Note, Participation, Passage, Pool, Team from tfjm.lists import get_sympa_client @@ -33,3 +34,13 @@ def update_mailing_list(instance: Team, **_): for coach in instance.coachs.all(): get_sympa_client().subscribe(coach.user.email, f"equipe-{instance.trigram.lower()}", False, f"{coach.user.first_name} {coach.user.last_name}") + + +def create_notes(instance: Union[Passage, Pool], **_): + if isinstance(instance, Pool): + for passage in instance.passages.all(): + create_notes(passage) + return + + for jury in instance.pool.juries.all(): + Note.objects.get_or_create(jury=jury, passage=instance) diff --git a/apps/participation/templates/participation/note_form.html b/apps/participation/templates/participation/note_form.html new file mode 100644 index 0000000..78231ff --- /dev/null +++ b/apps/participation/templates/participation/note_form.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% load crispy_forms_filters i18n %} + +{% block content %} +
+
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+{% endblock content %} diff --git a/apps/participation/templates/participation/passage_detail.html b/apps/participation/templates/participation/passage_detail.html index 3f376cb..531b778 100644 --- a/apps/participation/templates/participation/passage_detail.html +++ b/apps/participation/templates/participation/passage_detail.html @@ -40,6 +40,7 @@ {% if user.registration.is_admin %} {% elif user.registration.participates %} @@ -54,6 +55,11 @@ {% trans "Update" as modal_button %} {% url "participation:passage_update" pk=passage.pk as modal_action %} {% include "base_modal.html" with modal_id="updatePassage" %} + + {% trans "Update notes" as modal_title %} + {% trans "Update" as modal_button %} + {% url "participation:update_notes" pk=my_note.pk as modal_action %} + {% include "base_modal.html" with modal_id="updateNotes" %} {% elif user.registration.participates %} {% trans "Upload synthesis" as modal_title %} {% trans "Upload" as modal_button %} @@ -71,6 +77,12 @@ if (!modalBody.html().trim()) modalBody.load("{% url "participation:passage_update" pk=passage.pk %} #form-content") }); + + $('button[data-target="#updateNotesModal"]').click(function() { + let modalBody = $("#updateNotesModal div.modal-body"); + if (!modalBody.html().trim()) + modalBody.load("{% url "participation:update_notes" pk=my_note.pk %} #form-content") + }); {% elif user.registration.participates %} $('button[data-target="#uploadSynthesisModal"]').click(function() { let modalBody = $("#uploadSynthesisModal div.modal-body"); diff --git a/apps/participation/urls.py b/apps/participation/urls.py index b622178..09151ef 100644 --- a/apps/participation/urls.py +++ b/apps/participation/urls.py @@ -5,7 +5,7 @@ from django.urls import path from django.views.generic import TemplateView from .views import CreateTeamView, JoinTeamView, \ - MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, \ + MyParticipationDetailView, MyTeamDetailView, NoteUpdateView, ParticipationDetailView, \ PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \ PoolUpdateView, PoolUpdateTeamsView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \ TeamUpdateView, TournamentCreateView, TournamentDetailView, TournamentListView, TournamentUpdateView, \ @@ -38,5 +38,6 @@ urlpatterns = [ path("pools/passages//", PassageDetailView.as_view(), name="passage_detail"), path("pools/passages//update/", PassageUpdateView.as_view(), name="passage_update"), path("pools/passages//solution/", SynthesisUploadView.as_view(), name="upload_synthesis"), + path("pools/passages/notes//", NoteUpdateView.as_view(), name="update_notes"), path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat") ] diff --git a/apps/participation/views.py b/apps/participation/views.py index 45aa654..70db0ea 100644 --- a/apps/participation/views.py +++ b/apps/participation/views.py @@ -23,9 +23,9 @@ from tfjm.lists import get_sympa_client from tfjm.matrix import Matrix from tfjm.views import AdminMixin -from .forms import JoinTeamForm, ParticipationForm, PassageForm, PoolForm, PoolTeamsForm, RequestValidationForm, \ - TeamForm, TournamentForm, ValidateParticipationForm, SolutionForm, SynthesisForm -from .models import Participation, Passage, Pool, Team, Tournament, Solution, Synthesis +from .forms import JoinTeamForm, NoteForm, ParticipationForm, PassageForm, PoolForm, PoolTeamsForm, \ + RequestValidationForm, TeamForm, TournamentForm, ValidateParticipationForm, SolutionForm, SynthesisForm +from .models import Note, Participation, Passage, Pool, Team, Tournament, Solution, Synthesis from .tables import TeamTable, TournamentTable, ParticipationTable, PoolTable @@ -513,6 +513,12 @@ class PassageCreateView(AdminMixin, CreateView): class PassageDetailView(LoginRequiredMixin, DetailView): model = Passage + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if self.request.user.registration in self.object.pool.juries.all(): + context["my_note"] = Note.objects.get(passage=self.object, jury=self.request.user.registration) + return context + class PassageUpdateView(AdminMixin, UpdateView): model = Passage @@ -551,3 +557,8 @@ class SynthesisUploadView(LoginRequiredMixin, FormView): def get_success_url(self): return reverse_lazy("participation:passage_detail", args=(self.passage.pk,)) + + +class NoteUpdateView(LoginRequiredMixin, UpdateView): + model = Note + form_class = NoteForm