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:
parent
f3f862c1ab
commit
d912c8aab4
@ -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',)
|
||||||
|
@ -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")
|
||||||
|
@ -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 %}
|
13
apps/participation/templates/participation/passage_form.html
Normal file
13
apps/participation/templates/participation/passage_form.html
Normal 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 %}
|
@ -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 %}
|
||||||
|
@ -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")
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user