mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-10-31 10:09:53 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			f3f862c1ab
			...
			6f26b24359
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 6f26b24359 | ||
|  | 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',) | ||||||
|   | |||||||
| @@ -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, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user