From 26eacad2fd39bf52cbec50b2cd20175d057911d6 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 4 May 2020 23:37:21 +0200 Subject: [PATCH] Send solutions --- apps/member/forms.py | 1 - apps/member/models.py | 21 ++++++++----- apps/member/views.py | 9 ++++-- apps/tournament/forms.py | 19 ++++++++++- apps/tournament/tables.py | 36 ++++++++++++++++++++- apps/tournament/views.py | 40 ++++++++++++++++++++---- templates/tournament/solutions_list.html | 10 +++++- templates/tournament/team_detail.html | 2 +- tfjm/middlewares.py | 1 - 9 files changed, 118 insertions(+), 21 deletions(-) diff --git a/apps/member/forms.py b/apps/member/forms.py index e304e4e..584903b 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -10,7 +10,6 @@ class SignUpForm(UserCreationForm): super().__init__(*args, **kwargs) self.fields["first_name"].required = True self.fields["last_name"].required = True - print(self.fields["role"].choices) self.fields["role"].choices = [ ('', _("Choose a role...")), ('participant', _("Participant")), diff --git a/apps/member/models.py b/apps/member/models.py index 2559be9..698d6d7 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -178,6 +178,10 @@ class Document(PolymorphicModel): verbose_name = _("document") verbose_name_plural = _("documents") + def delete(self, *args, **kwargs): + self.file.delete(True) + return super().delete(*args, **kwargs) + class Authorization(Document): user = models.ForeignKey( @@ -234,14 +238,15 @@ class Solution(Document): verbose_name=_("problem"), ) - def save(self, **kwargs): - self.type = "solution" - super().save(**kwargs) + final = models.BooleanField( + default=False, + verbose_name=_("final solution"), + ) class Meta: verbose_name = _("solution") verbose_name_plural = _("solutions") - unique_together = ('team', 'problem',) + unique_together = ('team', 'problem', 'final',) def __str__(self): return _("Solution of team {trigram} for problem {problem}")\ @@ -274,13 +279,15 @@ class Synthesis(Document): verbose_name=_("round"), ) - def save(self, **kwargs): - self.type = "synthesis" - super().save(**kwargs) + final = models.BooleanField( + default=False, + verbose_name=_("final synthesis"), + ) class Meta: verbose_name = _("synthesis") verbose_name_plural = _("syntheses") + unique_together = ('team', 'dest', 'round', 'final',) def __str__(self): return _("Synthesis of team {trigram} that is {dest} for problem {problem}")\ diff --git a/apps/member/views.py b/apps/member/views.py index 222d6fc..f08563a 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -12,7 +12,7 @@ from django_tables2 import SingleTableView from tournament.models import Team from tournament.views import AdminMixin, TeamMixin from .forms import SignUpForm, TFJMUserForm -from .models import TFJMUser, Document +from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis from .tables import UserTable @@ -93,7 +93,12 @@ class DocumentView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): doc = Document.objects.get(file=self.kwargs["file"]) - if not request.user.admin: + grant = request.user.admin + + if isinstance(doc, Solution) or isinstance(doc, Synthesis) or isinstance(doc, MotivationLetter): + grant = grant or doc.team == request.user.team or request.user in doc.team.tournament.organizers.all() + + if not grant: raise PermissionDenied return FileResponse(doc.file, content_type="application/pdf") diff --git a/apps/tournament/forms.py b/apps/tournament/forms.py index 37e2ec9..d710cb0 100644 --- a/apps/tournament/forms.py +++ b/apps/tournament/forms.py @@ -1,7 +1,7 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from member.models import TFJMUser +from member.models import TFJMUser, Solution, Synthesis from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput from tournament.models import Tournament, Team @@ -42,3 +42,20 @@ class JoinTeam(forms.Form): label=_("Access code"), max_length=6, ) + + +class SolutionForm(forms.ModelForm): + problem = forms.ChoiceField( + label=_("Problem"), + choices=[(str(i), _("Problem #{problem:d}").format(problem=i)) for i in range(1, 9)], + ) + + class Meta: + model = Solution + fields = ('file', 'problem',) + + +class SynthesisForm(forms.ModelForm): + class Meta: + model = Synthesis + fields = ('file', 'dest',) diff --git a/apps/tournament/tables.py b/apps/tournament/tables.py index 7cebb16..2ee3478 100644 --- a/apps/tournament/tables.py +++ b/apps/tournament/tables.py @@ -2,6 +2,7 @@ import django_tables2 as tables from django.utils.translation import gettext as _ from django_tables2 import A +from member.models import Solution from .models import Tournament, Team @@ -41,6 +42,39 @@ class TeamTable(tables.Table): class SolutionTable(tables.Table): + team = tables.LinkColumn( + "tournament:team_detail", + args=[A("team.pk")], + ) + + tournament = tables.LinkColumn( + "tournament:detail", + args=[A("team.tournament.pk")], + accessor=A("team.tournament"), + ) + + file = tables.LinkColumn( + "document", + args=[A("file")], + attrs={ + "a": { + "data-turbolinks": "false", + } + } + ) + + def render_file(self): + return _("Download") + + class Meta: + model = Solution + fields = ("team", "tournament", "problem", "uploaded_at", "file", ) + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + + +class SynthesisTable(tables.Table): file = tables.LinkColumn( "document", args=[A("file")], @@ -56,7 +90,7 @@ class SolutionTable(tables.Table): class Meta: model = Team - fields = ("team", "team.tournament", "problem", "uploaded_at", "file", ) + fields = ("team", "team.tournament", "round", "dest", "uploaded_at", "file", ) attrs = { 'class': 'table table-condensed table-striped table-hover' } diff --git a/apps/tournament/views.py b/apps/tournament/views.py index c0ee679..57cbdac 100644 --- a/apps/tournament/views.py +++ b/apps/tournament/views.py @@ -1,3 +1,4 @@ +import random import zipfile from io import BytesIO @@ -9,10 +10,11 @@ from django.shortcuts import redirect from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import DetailView, CreateView, UpdateView +from django.views.generic.edit import FormMixin, BaseFormView from django_tables2.views import SingleTableView from member.models import TFJMUser, Solution -from .forms import TournamentForm, OrganizerForm, TeamForm +from .forms import TournamentForm, OrganizerForm, TeamForm, SolutionForm from .models import Tournament, Team from .tables import TournamentTable, TeamTable, SolutionTable @@ -96,7 +98,8 @@ class TeamDetailView(LoginRequiredMixin, DetailView): model = Team def dispatch(self, request, *args, **kwargs): - if not request.user.admin and self.request.user not in self.get_object().tournament.organizers: + if not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()\ + and self.get_object() != request.user.team: raise PermissionDenied return super().dispatch(request, *args, **kwargs) @@ -151,9 +154,10 @@ class AddOrganizerView(AdminMixin, CreateView): template_name = "tournament/add_organizer.html" -class SolutionsView(TeamMixin, SingleTableView): +class SolutionsView(TeamMixin, BaseFormView, SingleTableView): model = Solution table_class = SolutionTable + form_class = SolutionForm template_name = "tournament/solutions_list.html" extra_context = dict(title=_("Solutions")) @@ -175,7 +179,7 @@ class SolutionsView(TeamMixin, SingleTableView): .format(team=str(request.user.team)).replace(" ", "%20")) return resp - return self.get(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -188,9 +192,33 @@ class SolutionsView(TeamMixin, SingleTableView): def get_queryset(self): qs = super().get_queryset() if not self.request.user.admin: - qs = qs.filter(team__tournament__organizers=self.request.user) + qs = qs.filter(team=self.request.user.team) return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',) + def form_valid(self, form): + solution = form.instance + solution.team = self.request.user.team + solution.final = solution.team.selected_for_final + prev_sol = Solution.objects.filter(problem=solution.problem, team=solution.team, final=solution.final) + for sol in prev_sol.all(): + sol.delete() + alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" + id = "" + for _ in range(64): + id += random.choice(alphabet) + solution.file.name = id + solution.save() + + return super().form_valid(form) + + def form_invalid(self, form): + print(form.errors) + + return super().form_invalid(form) + + def get_success_url(self): + return reverse_lazy("tournament:solutions") + class SolutionsOrgaListView(AdminMixin, SingleTableView): model = Solution @@ -202,7 +230,7 @@ class SolutionsOrgaListView(AdminMixin, SingleTableView): if "tournament_zip" in request.POST: tournament = Tournament.objects.get(pk=request.POST["tournament_zip"][0]) solutions = tournament.solutions - if not request.user.admin and request.user not in tournament.organizers: + if not request.user.admin and request.user not in tournament.organizers.all(): raise PermissionDenied out = BytesIO() diff --git a/templates/tournament/solutions_list.html b/templates/tournament/solutions_list.html index 3be9113..2503997 100644 --- a/templates/tournament/solutions_list.html +++ b/templates/tournament/solutions_list.html @@ -1,7 +1,15 @@ {% extends "base.html" %} -{% load i18n django_tables2 %} +{% load i18n crispy_forms_filters django_tables2 %} {% block content %} + {% if form %} +
+ {% csrf_token %} + {{ form|crispy }} + +
+
+ {% endif %} {% render_table table %} {% endblock %} diff --git a/templates/tournament/team_detail.html b/templates/tournament/team_detail.html index 5b0e01a..dcb2dab 100644 --- a/templates/tournament/team_detail.html +++ b/templates/tournament/team_detail.html @@ -16,7 +16,7 @@
{{ team.trigram }}
{% trans 'tournament'|capfirst %}
-
{{ team.tournament }}
+
{{ team.tournament }}
{% trans 'coachs'|capfirst %}
{% autoescape off %}{{ team.linked_encadrants|join:", " }}{% endautoescape %}
diff --git a/tfjm/middlewares.py b/tfjm/middlewares.py index 7a51220..cf10323 100644 --- a/tfjm/middlewares.py +++ b/tfjm/middlewares.py @@ -55,7 +55,6 @@ class SessionMiddleware(object): request.user = TFJMUser.objects.get(pk=request.session["_fake_user_id"]) user = request.user - print(user) if 'HTTP_X_FORWARDED_FOR' in request.META: ip = request.META.get('HTTP_X_FORWARDED_FOR') else: