1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2024-12-24 17:42:23 +00:00

Display detail about a passage

This commit is contained in:
Yohann D'ANELLO 2021-01-14 15:59:11 +01:00
parent f3f862c1ab
commit d912c8aab4
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
7 changed files with 178 additions and 13 deletions

View File

@ -9,7 +9,7 @@ from django.core.exceptions import ValidationError
from django.utils import formats from django.utils import formats
from django.utils.translation import gettext_lazy as _ 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): class TeamForm(forms.ModelForm):
@ -145,3 +145,20 @@ class PoolTeamsForm(forms.ModelForm):
widgets = { widgets = {
"participations": forms.CheckboxSelectMultiple, "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',)

View File

@ -340,6 +340,13 @@ class Passage(models.Model):
default="Non indiqué", 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( defender = models.ForeignKey(
Participation, Participation,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -361,6 +368,16 @@ class Passage(models.Model):
related_name="+", 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): def clean(self):
if self.defender not in self.pool.participations.all(): if self.defender not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.") raise ValidationError(_("Team {trigram} is not registered in the pool.")
@ -373,6 +390,10 @@ class Passage(models.Model):
.format(trigram=self.reporter.team.trigram)) .format(trigram=self.reporter.team.trigram))
return super().clean() return super().clean()
def __str__(self):
return _("Passage of {defender} for problem {problem}")\
.format(defender=self.defender.team, problem=self.solution_number)
class Meta: class Meta:
verbose_name = _("passage") verbose_name = _("passage")
verbose_name_plural = _("passages") verbose_name_plural = _("passages")

View File

@ -0,0 +1,55 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{% trans "any" as any %}
<div class="card bg-light shadow">
<div class="card-header text-center">
<h4>{{ passage }}</h4>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">{% trans "Pool:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.pool.get_absolute_url }}">{{ passage.pool }}</a></dd>
<dt class="col-sm-3">{% trans "Defender:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.defender.get_absolute_url }}">{{ passage.defender.team }}</a></dd>
<dt class="col-sm-3">{% trans "Opponent:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.opponent.get_absolute_url }}">{{ passage.opponent.team }}</a></dd>
<dt class="col-sm-3">{% trans "Reporter:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd>
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}" data-turbolinks="false">{{ passage.defended_solution }}</a></dd>
<dt class="col-sm-3">{% trans "Place:" %}</dt>
<dd class="col-sm-9">{{ passage.place }}</dd>
</dl>
</div>
{% if user.registration.is_admin %}
<div class="card-footer text-center">
<button class="btn btn-primary" data-toggle="modal" data-target="#updatePassageModal">{% trans "Update" %}</button>
</div>
{% endif %}
</div>
{% 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 %}
<script>
$(document).ready(function () {
$('button[data-target="#updatePassageModal"]').click(function() {
let modalBody = $("#updatePassageModal div.modal-body");
if (!modalBody.html().trim())
modalBody.load("{% url "participation:passage_update" pk=passage.pk %} #form-content")
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load crispy_forms_filters i18n %}
{% block content %}
<form method="post">
<div id="form-content">
{% csrf_token %}
{{ form|crispy }}
</div>
<button class="btn btn-primary" type="submit">{% trans "Update passage" %}</button>
</form>
{% endblock content %}

View File

@ -9,31 +9,50 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
<dt class="col-sm-2">{% trans "Tournament:" %}</dt> <dt class="col-sm-3">{% trans "Tournament:" %}</dt>
<dd class="col-sm-10">{{ pool.tournament }}</dd> <dd class="col-sm-9">{{ pool.tournament }}</dd>
<dt class="col-sm-2">{% trans "Round:" %}</dt> <dt class="col-sm-3">{% trans "Round:" %}</dt>
<dd class="col-sm-10">{{ pool.get_round_display }}</dd> <dd class="col-sm-9">{{ pool.get_round_display }}</dd>
<dt class="col-sm-2">{% trans "Teams:" %}</dt> <dt class="col-sm-3">{% trans "Teams:" %}</dt>
<dd class="col-sm-10"> <dd class="col-sm-9">
{% for participation in pool.participations.all %} {% for participation in pool.participations.all %}
<a href="{{ participation.get_absolute_url }}" data-turbolinks="false">{{ participation.team }}{% if not forloop.last %}, {% endif %}</a> <a href="{{ participation.get_absolute_url }}" data-turbolinks="false">{{ participation.team }}{% if not forloop.last %}, {% endif %}</a>
{% endfor %} {% endfor %}
</dd> </dd>
<dt class="col-sm-2">{% trans "Juries:" %}</dt> <dt class="col-sm-3">{% trans "Juries:" %}</dt>
<dd class="col-sm-10">{{ pool.juries.all|join:", " }}</dd> <dd class="col-sm-9">{{ pool.juries.all|join:", " }}</dd>
<dt class="col-sm-3">{% trans "Passages:" %}</dt>
<dd class="col-sm-9">
{% for passage in pool.passages.all %}
<a href="{{ passage.get_absolute_url }}" data-turbolinks="false">{{ passage }}{% if not forloop.last %}, {% endif %}</a>
{% endfor %}
</dd>
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
<dd class="col-sm-9">
{% for passage in pool.passages.all %}
<a href="{{ passage.defended_solution.get_absolute_url }}" data-turbolinks="false">{{ passage.defended_solution }}{% if not forloop.last %}, {% endif %}</a>
{% endfor %}
</dd>
</dl> </dl>
</div> </div>
{% if user.registration.is_admin %} {% if user.registration.is_admin %}
<div class="card-footer text-center"> <div class="card-footer text-center">
<button class="btn btn-success" data-toggle="modal" data-target="#addPassageModal">{% trans "Add passage" %}</button>
<button class="btn btn-primary" data-toggle="modal" data-target="#updatePoolModal">{% trans "Update" %}</button> <button class="btn btn-primary" data-toggle="modal" data-target="#updatePoolModal">{% trans "Update" %}</button>
<button class="btn btn-primary" data-toggle="modal" data-target="#updateTeamsModal">{% trans "Update teams" %}</button> <button class="btn btn-primary" data-toggle="modal" data-target="#updateTeamsModal">{% trans "Update teams" %}</button>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% 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 pool" as modal_title %}
{% trans "Update" as modal_button %} {% trans "Update" as modal_button %}
{% url "participation:pool_update" pk=pool.pk as modal_action %} {% url "participation:pool_update" pk=pool.pk as modal_action %}
@ -59,6 +78,12 @@
if (!modalBody.html().trim()) if (!modalBody.html().trim())
modalBody.load("{% url "participation:pool_update_teams" pk=pool.pk %} #form-content") 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")
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -5,7 +5,8 @@ from django.urls import path
from django.views.generic import TemplateView from django.views.generic import TemplateView
from .views import CreateTeamView, JoinTeamView, \ from .views import CreateTeamView, JoinTeamView, \
MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, PoolCreateView, PoolDetailView, \ MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, \
PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \
PoolUpdateView, PoolUpdateTeamsView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \ PoolUpdateView, PoolUpdateTeamsView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \
TeamUpdateView, TournamentCreateView, TournamentDetailView, TournamentListView, TournamentUpdateView, \ TeamUpdateView, TournamentCreateView, TournamentDetailView, TournamentListView, TournamentUpdateView, \
SolutionUploadView SolutionUploadView
@ -33,5 +34,8 @@ urlpatterns = [
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"), path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"), path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"), path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat") path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat")
] ]

View File

@ -23,9 +23,9 @@ from tfjm.lists import get_sympa_client
from tfjm.matrix import Matrix from tfjm.matrix import Matrix
from tfjm.views import AdminMixin from tfjm.views import AdminMixin
from .forms import JoinTeamForm, ParticipationForm, PoolForm, PoolTeamsForm, RequestValidationForm, SolutionForm, \ from .forms import JoinTeamForm, ParticipationForm, PassageForm, PoolForm, PoolTeamsForm, RequestValidationForm, \
TeamForm, TournamentForm, ValidateParticipationForm SolutionForm, TeamForm, TournamentForm, ValidateParticipationForm
from .models import Participation, Team, Tournament, Solution, Pool from .models import Participation, Passage, Pool, Team, Tournament, Solution
from .tables import TeamTable, TournamentTable, ParticipationTable, PoolTable from .tables import TeamTable, TournamentTable, ParticipationTable, PoolTable
@ -487,3 +487,33 @@ class PoolUpdateView(AdminMixin, UpdateView):
class PoolUpdateTeamsView(AdminMixin, UpdateView): class PoolUpdateTeamsView(AdminMixin, UpdateView):
model = Pool model = Pool
form_class = PoolTeamsForm 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