From c8780a6d9d414515e4f9e5e5e959bc9da0e44c70 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 14 Jan 2021 17:26:08 +0100 Subject: [PATCH] Upload syntheses --- apps/participation/forms.py | 13 +++++- .../migrations/0007_auto_20210112_1801.py | 2 +- apps/participation/models.py | 9 ++-- .../participation/passage_detail.html | 46 +++++++++++++++---- .../participation/upload_synthesis.html | 13 ++++++ apps/participation/urls.py | 3 +- apps/participation/views.py | 42 +++++++++++++++-- apps/registration/views.py | 2 +- 8 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 apps/participation/templates/participation/upload_synthesis.html diff --git a/apps/participation/forms.py b/apps/participation/forms.py index ad98eed..7b0ddfd 100644 --- a/apps/participation/forms.py +++ b/apps/participation/forms.py @@ -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, Passage, Pool, Team, Tournament, Solution +from .models import Participation, Passage, Pool, Team, Tournament, Solution, Synthesis class TeamForm(forms.ModelForm): @@ -162,3 +162,14 @@ class PassageForm(forms.ModelForm): class Meta: model = Passage fields = ('solution_number', 'place', 'defender', 'opponent', 'reporter',) + + +class SynthesisForm(forms.ModelForm): + def save(self, commit=True): + """ + Don't save a synthesis with this way. Use a view instead + """ + + class Meta: + model = Synthesis + fields = ('type', 'file',) diff --git a/apps/participation/migrations/0007_auto_20210112_1801.py b/apps/participation/migrations/0007_auto_20210112_1801.py index f7615dc..e2a46ed 100644 --- a/apps/participation/migrations/0007_auto_20210112_1801.py +++ b/apps/participation/migrations/0007_auto_20210112_1801.py @@ -24,6 +24,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='synthesis', name='file', - field=models.FileField(blank=True, default='', unique=True, upload_to=participation.models.get_random_synthesis_filename, verbose_name='file'), + field=models.FileField(blank=True, default='', unique=True, upload_to=participation.models.get_synthesis_filename, verbose_name='file'), ), ] diff --git a/apps/participation/models.py b/apps/participation/models.py index d85adf0..8635651 100644 --- a/apps/participation/models.py +++ b/apps/participation/models.py @@ -404,8 +404,8 @@ def get_solution_filename(instance, filename): + ("final" if instance.final_solution else "") -def get_random_synthesis_filename(instance, filename): - return "syntheses/" + get_random_string(64) +def get_synthesis_filename(instance, filename): + return f"syntheses/{instance.participation.team.trigram}_{instance.type}_{instance.passage.pk}" class Solution(models.Model): @@ -469,12 +469,15 @@ class Synthesis(models.Model): file = models.FileField( verbose_name=_("file"), - upload_to=get_random_synthesis_filename, + upload_to=get_synthesis_filename, unique=True, blank=True, default="", ) + def __str__(self): + return _("Synthesis for the {type} of the {passage}").format(type=self.get_type_display(), passage=self.passage) + class Meta: verbose_name = _("synthesis") verbose_name_plural = _("syntheses") diff --git a/apps/participation/templates/participation/passage_detail.html b/apps/participation/templates/participation/passage_detail.html index 38a5203..3f376cb 100644 --- a/apps/participation/templates/participation/passage_detail.html +++ b/apps/participation/templates/participation/passage_detail.html @@ -27,29 +27,57 @@
{% trans "Place:" %}
{{ passage.place }}
+ +
{% trans "Syntheses:" %}
+
+ {% for synthesis in passage.syntheses.all %} + {{ synthesis }}{% if not forloop.last %}, {% endif %} + {% empty %} + {% trans "No synthesis was uploaded yet." %} + {% endfor %} +
{% if user.registration.is_admin %} + {% elif user.registration.participates %} + {% endif %} - {% 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" %} + {% if user.registration.is_admin %} + {% 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" %} + {% elif user.registration.participates %} + {% trans "Upload synthesis" as modal_title %} + {% trans "Upload" as modal_button %} + {% url "participation:upload_synthesis" pk=passage.pk as modal_action %} + {% include "base_modal.html" with modal_id="uploadSynthesis" modal_enctype="multipart/form-data" %} + {% endif %} {% endblock %} {% block extrajavascript %} {% endblock %} diff --git a/apps/participation/templates/participation/upload_synthesis.html b/apps/participation/templates/participation/upload_synthesis.html new file mode 100644 index 0000000..378585f --- /dev/null +++ b/apps/participation/templates/participation/upload_synthesis.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% load crispy_forms_filters i18n %} + +{% block content %} +
+
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+{% endblock content %} diff --git a/apps/participation/urls.py b/apps/participation/urls.py index 026c2e8..b622178 100644 --- a/apps/participation/urls.py +++ b/apps/participation/urls.py @@ -9,7 +9,7 @@ from .views import CreateTeamView, JoinTeamView, \ PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \ PoolUpdateView, PoolUpdateTeamsView, TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, \ TeamUpdateView, TournamentCreateView, TournamentDetailView, TournamentListView, TournamentUpdateView, \ - SolutionUploadView + SolutionUploadView, SynthesisUploadView app_name = "participation" @@ -37,5 +37,6 @@ urlpatterns = [ path("pools/passages/add//", PassageCreateView.as_view(), name="passage_create"), path("pools/passages//", PassageDetailView.as_view(), name="passage_detail"), path("pools/passages//update/", PassageUpdateView.as_view(), name="passage_update"), + path("pools/passages//solution/", SynthesisUploadView.as_view(), name="upload_synthesis"), path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat") ] diff --git a/apps/participation/views.py b/apps/participation/views.py index 6151f86..45aa654 100644 --- a/apps/participation/views.py +++ b/apps/participation/views.py @@ -24,8 +24,8 @@ from tfjm.matrix import Matrix from tfjm.views import AdminMixin from .forms import JoinTeamForm, ParticipationForm, PassageForm, PoolForm, PoolTeamsForm, RequestValidationForm, \ - SolutionForm, TeamForm, TournamentForm, ValidateParticipationForm -from .models import Participation, Passage, Pool, Team, Tournament, Solution + TeamForm, TournamentForm, ValidateParticipationForm, SolutionForm, SynthesisForm +from .models import Participation, Passage, Pool, Team, Tournament, Solution, Synthesis from .tables import TeamTable, TournamentTable, ParticipationTable, PoolTable @@ -460,6 +460,7 @@ class SolutionUploadView(LoginRequiredMixin, FormView): for sol in Solution.objects.filter(participation=self.participation, problem=form_sol.problem, final_solution=self.participation.final).all(): + sol.file.delete() sol.delete() form_sol.participation = self.participation form_sol.final = self.participation.final @@ -475,7 +476,7 @@ class PoolCreateView(AdminMixin, CreateView): form_class = PoolForm -class PoolDetailView(AdminMixin, DetailView): +class PoolDetailView(LoginRequiredMixin, DetailView): model = Pool @@ -509,7 +510,7 @@ class PassageCreateView(AdminMixin, CreateView): return form -class PassageDetailView(AdminMixin, DetailView): +class PassageDetailView(LoginRequiredMixin, DetailView): model = Passage @@ -517,3 +518,36 @@ class PassageUpdateView(AdminMixin, UpdateView): model = Passage form_class = PassageForm + +class SynthesisUploadView(LoginRequiredMixin, FormView): + template_name = "participation/upload_synthesis.html" + form_class = SynthesisForm + + def dispatch(self, request, *args, **kwargs): + qs = Passage.objects.filter(pk=self.kwargs["pk"]) + if not qs.exists(): + raise Http404 + self.participation = self.request.user.registration.team.participation + self.passage = qs.get() + return super().dispatch(request, *args, **kwargs) + + def form_valid(self, form): + """ + When a solution is submitted, it replaces a previous solution if existing, + otherwise it creates a new solution. + It is discriminating whenever the team is selected for the final tournament or not. + """ + form_syn = form.instance + # Drop previous solution if existing + for syn in Synthesis.objects.filter(participation=self.participation, + passage=self.passage, + type=form_syn.type).all(): + syn.file.delete() + syn.delete() + form_syn.participation = self.participation + form_syn.passage = self.passage + form_syn.save() + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("participation:passage_detail", args=(self.passage.pk,)) diff --git a/apps/registration/views.py b/apps/registration/views.py index b30437d..d6c1b43 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -372,7 +372,7 @@ class SynthesisView(LoginRequiredMixin, View): """ def get(self, request, *args, **kwargs): filename = kwargs["filename"] - path = f"media/syhntheses/{filename}" + path = f"media/syntheses/{filename}" if not os.path.exists(path): raise Http404 solution = Synthesis.objects.get(file__endswith=filename)