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.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',) | ||||
|   | ||||
| @@ -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é", | ||||
|     ) | ||||
|  | ||||
|     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") | ||||
|   | ||||
| @@ -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 class="card-body"> | ||||
|             <dl class="row"> | ||||
|                 <dt class="col-sm-2">{% trans "Tournament:" %}</dt> | ||||
|                 <dd class="col-sm-10">{{ pool.tournament }}</dd> | ||||
|                 <dt class="col-sm-3">{% trans "Tournament:" %}</dt> | ||||
|                 <dd class="col-sm-9">{{ pool.tournament }}</dd> | ||||
|  | ||||
|                 <dt class="col-sm-2">{% trans "Round:" %}</dt> | ||||
|                 <dd class="col-sm-10">{{ pool.get_round_display }}</dd> | ||||
|                 <dt class="col-sm-3">{% trans "Round:" %}</dt> | ||||
|                 <dd class="col-sm-9">{{ pool.get_round_display }}</dd> | ||||
|  | ||||
|                 <dt class="col-sm-2">{% trans "Teams:" %}</dt> | ||||
|                 <dd class="col-sm-10"> | ||||
|                 <dt class="col-sm-3">{% trans "Teams:" %}</dt> | ||||
|                 <dd class="col-sm-9"> | ||||
|                     {% for participation in pool.participations.all %} | ||||
|                         <a href="{{ participation.get_absolute_url }}" data-turbolinks="false">{{ participation.team }}{% if not forloop.last %}, {% endif %}</a> | ||||
|                     {% endfor %} | ||||
|                 </dd> | ||||
|  | ||||
|                 <dt class="col-sm-2">{% trans "Juries:" %}</dt> | ||||
|                 <dd class="col-sm-10">{{ pool.juries.all|join:", " }}</dd> | ||||
|                 <dt class="col-sm-3">{% trans "Juries:" %}</dt> | ||||
|                 <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> | ||||
|         </div> | ||||
|         {% if user.registration.is_admin %} | ||||
|             <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="#updateTeamsModal">{% trans "Update teams" %}</button> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </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" 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") | ||||
|             }); | ||||
|         }); | ||||
|     </script> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -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/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"), | ||||
|     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/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") | ||||
| ] | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user