1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-02-19 00:21:19 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Yohann D'ANELLO
6f26b24359
Store the defended solution in the passage 2021-01-14 16:27:44 +01:00
Yohann D'ANELLO
d912c8aab4
Display detail about a passage 2021-01-14 15:59:11 +01:00
8 changed files with 197 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

@ -0,0 +1,19 @@
# Generated by Django 3.0.11 on 2021-01-14 13:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('participation', '0009_auto_20210114_1313'),
]
operations = [
migrations.AddField(
model_name='passage',
name='solution_number',
field=models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], default=None, verbose_name='defended solution'),
preserve_default=False,
),
]

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