diff --git a/apps/participation/forms.py b/apps/participation/forms.py
index 86c58a2..ad98eed 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, Team, Tournament, Solution, Pool
+from .models import Participation, Passage, Pool, Team, Tournament, Solution
class TeamForm(forms.ModelForm):
@@ -145,3 +145,20 @@ class PoolTeamsForm(forms.ModelForm):
widgets = {
"participations": forms.CheckboxSelectMultiple,
}
+
+
+class PassageForm(forms.ModelForm):
+ def clean(self):
+ cleaned_data = super().clean()
+ if "defender" in cleaned_data and "opponent" in cleaned_data and "reporter" in cleaned_data \
+ and len({cleaned_data["defender"], cleaned_data["opponent"], cleaned_data["reporter"]}) < 3:
+ self.add_error(None, _("The defender, the opponent and the reporter must be different."))
+ if "defender" in self.cleaned_data and "solution_number" in self.cleaned_data \
+ and not Solution.objects.filter(participation=cleaned_data["defender"],
+ problem=cleaned_data["solution_number"]).exists():
+ self.add_error("solution_number", _("This defender did not work on this problem."))
+ return cleaned_data
+
+ class Meta:
+ model = Passage
+ fields = ('solution_number', 'place', 'defender', 'opponent', 'reporter',)
diff --git a/apps/participation/models.py b/apps/participation/models.py
index 6de2376..d85adf0 100644
--- a/apps/participation/models.py
+++ b/apps/participation/models.py
@@ -340,6 +340,13 @@ class Passage(models.Model):
default="Non indiqué",
)
+ solution_number = models.PositiveSmallIntegerField(
+ verbose_name=_("defended solution"),
+ choices=[
+ (i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, settings.PROBLEM_COUNT + 1)
+ ],
+ )
+
defender = models.ForeignKey(
Participation,
on_delete=models.PROTECT,
@@ -361,6 +368,16 @@ class Passage(models.Model):
related_name="+",
)
+ @property
+ def defended_solution(self) -> "Solution":
+ return Solution.objects.get(
+ participation=self.defender,
+ problem=self.solution_number,
+ final_solution=self.pool.tournament.final)
+
+ def get_absolute_url(self):
+ return reverse_lazy("participation:passage_detail", args=(self.pk,))
+
def clean(self):
if self.defender not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.")
@@ -373,6 +390,10 @@ class Passage(models.Model):
.format(trigram=self.reporter.team.trigram))
return super().clean()
+ def __str__(self):
+ return _("Passage of {defender} for problem {problem}")\
+ .format(defender=self.defender.team, problem=self.solution_number)
+
class Meta:
verbose_name = _("passage")
verbose_name_plural = _("passages")
diff --git a/apps/participation/templates/participation/passage_detail.html b/apps/participation/templates/participation/passage_detail.html
new file mode 100644
index 0000000..38a5203
--- /dev/null
+++ b/apps/participation/templates/participation/passage_detail.html
@@ -0,0 +1,55 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+
+{% block content %}
+{% trans "any" as any %}
+
+
+ {% trans "Update passage" as modal_title %}
+ {% trans "Update" as modal_button %}
+ {% url "participation:passage_update" pk=passage.pk as modal_action %}
+ {% include "base_modal.html" with modal_id="updatePassage" %}
+{% endblock %}
+
+{% block extrajavascript %}
+
+{% endblock %}
diff --git a/apps/participation/templates/participation/passage_form.html b/apps/participation/templates/participation/passage_form.html
new file mode 100644
index 0000000..daabd59
--- /dev/null
+++ b/apps/participation/templates/participation/passage_form.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+
+{% load crispy_forms_filters i18n %}
+
+{% block content %}
+
+{% endblock content %}
diff --git a/apps/participation/templates/participation/pool_detail.html b/apps/participation/templates/participation/pool_detail.html
index c13a2d1..0dbf035 100644
--- a/apps/participation/templates/participation/pool_detail.html
+++ b/apps/participation/templates/participation/pool_detail.html
@@ -9,31 +9,50 @@
- - {% trans "Tournament:" %}
- - {{ pool.tournament }}
+ - {% trans "Tournament:" %}
+ - {{ pool.tournament }}
- - {% trans "Round:" %}
- - {{ pool.get_round_display }}
+ - {% trans "Round:" %}
+ - {{ pool.get_round_display }}
- - {% trans "Teams:" %}
- -
+
- {% trans "Teams:" %}
+ -
{% for participation in pool.participations.all %}
{{ participation.team }}{% if not forloop.last %}, {% endif %}
{% endfor %}
- - {% trans "Juries:" %}
- - {{ pool.juries.all|join:", " }}
+ - {% trans "Juries:" %}
+ - {{ pool.juries.all|join:", " }}
+
+ - {% trans "Passages:" %}
+ -
+ {% for passage in pool.passages.all %}
+ {{ passage }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+
+ - {% trans "Defended solutions:" %}
+ -
+ {% for passage in pool.passages.all %}
+ {{ passage.defended_solution }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+
{% if user.registration.is_admin %}
{% endif %}
+ {% trans "Add passage" as modal_title %}
+ {% trans "Add" as modal_button %}
+ {% url "participation:passage_create" pk=pool.pk as modal_action %}
+ {% include "base_modal.html" with modal_id="addPassage" modal_button_type="success" %}
+
{% trans "Update pool" as modal_title %}
{% trans "Update" as modal_button %}
{% url "participation:pool_update" pk=pool.pk as modal_action %}
@@ -59,6 +78,12 @@
if (!modalBody.html().trim())
modalBody.load("{% url "participation:pool_update_teams" pk=pool.pk %} #form-content")
});
+
+ $('button[data-target="#addPassageModal"]').click(function() {
+ let modalBody = $("#addPassageModal div.modal-body");
+ if (!modalBody.html().trim())
+ modalBody.load("{% url "participation:passage_create" pk=pool.pk %} #form-content")
+ });
});
{% endblock %}
diff --git a/apps/participation/urls.py b/apps/participation/urls.py
index c3df0e7..026c2e8 100644
--- a/apps/participation/urls.py
+++ b/apps/participation/urls.py
@@ -5,7 +5,8 @@ from django.urls import path
from django.views.generic import TemplateView
from .views import CreateTeamView, JoinTeamView, \
- MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, PoolCreateView, PoolDetailView, \
+ MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, \
+ PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \
PoolUpdateView, PoolUpdateTeamsView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \
TeamUpdateView, TournamentCreateView, TournamentDetailView, TournamentListView, TournamentUpdateView, \
SolutionUploadView
@@ -33,5 +34,8 @@ 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/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"),
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 253ef34..6151f86 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, PoolForm, PoolTeamsForm, RequestValidationForm, SolutionForm, \
- TeamForm, TournamentForm, ValidateParticipationForm
-from .models import Participation, Team, Tournament, Solution, Pool
+from .forms import JoinTeamForm, ParticipationForm, PassageForm, PoolForm, PoolTeamsForm, RequestValidationForm, \
+ SolutionForm, TeamForm, TournamentForm, ValidateParticipationForm
+from .models import Participation, Passage, Pool, Team, Tournament, Solution
from .tables import TeamTable, TournamentTable, ParticipationTable, PoolTable
@@ -487,3 +487,33 @@ class PoolUpdateView(AdminMixin, UpdateView):
class PoolUpdateTeamsView(AdminMixin, UpdateView):
model = Pool
form_class = PoolTeamsForm
+
+
+class PassageCreateView(AdminMixin, CreateView):
+ model = Passage
+ form_class = PassageForm
+
+ def dispatch(self, request, *args, **kwargs):
+ qs = Pool.objects.filter(pk=self.kwargs["pk"])
+ if not qs.exists():
+ raise Http404
+ self.pool = qs.get()
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_form(self, form_class=None):
+ form = super().get_form(form_class)
+ form.instance.pool = self.pool
+ form.fields["defender"].queryset = self.pool.participations.all()
+ form.fields["opponent"].queryset = self.pool.participations.all()
+ form.fields["reporter"].queryset = self.pool.participations.all()
+ return form
+
+
+class PassageDetailView(AdminMixin, DetailView):
+ model = Passage
+
+
+class PassageUpdateView(AdminMixin, UpdateView):
+ model = Passage
+ form_class = PassageForm
+