Solutions

This commit is contained in:
Yohann D'ANELLO 2020-04-30 20:11:03 +02:00
parent eee7fc845c
commit c636f483bb
7 changed files with 162 additions and 6 deletions

View File

@ -63,6 +63,11 @@ class Tournament(models.Model):
verbose_name=_("year"), verbose_name=_("year"),
) )
@property
def solutions(self):
from member.models import Solution
return Solution.objects.filter(team__tournament=self)
@classmethod @classmethod
def get_final(cls): def get_final(cls):
return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True) return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True)

View File

@ -38,3 +38,25 @@ class TeamTable(tables.Table):
attrs = { attrs = {
'class': 'table table-condensed table-striped table-hover' 'class': 'table table-condensed table-striped table-hover'
} }
class SolutionTable(tables.Table):
file = tables.LinkColumn(
"document",
args=[A("file")],
attrs={
"a": {
"data-turbolinks": "false",
}
}
)
def render_file(self):
return _("Download")
class Meta:
model = Team
fields = ("team", "team.tournament", "problem", "uploaded_at", "file", )
attrs = {
'class': 'table table-condensed table-striped table-hover'
}

View File

@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from .views import TournamentListView, TournamentDetailView, TeamDetailView from .views import TournamentListView, TournamentDetailView, TeamDetailView, SolutionsView, SolutionsOrgaListView
app_name = "tournament" app_name = "tournament"
@ -11,8 +11,8 @@ urlpatterns = [
path('<int:pk>/', TournamentDetailView.as_view(), name="detail"), path('<int:pk>/', TournamentDetailView.as_view(), name="detail"),
path('team/<int:pk>/', TeamDetailView.as_view(), name="team_detail"), path('team/<int:pk>/', TeamDetailView.as_view(), name="team_detail"),
path("add-organizer/", RedirectView.as_view(pattern_name="index"), name="add_organizer"), path("add-organizer/", RedirectView.as_view(pattern_name="index"), name="add_organizer"),
path("solutions/", RedirectView.as_view(pattern_name="index"), name="solutions"), path("solutions/", SolutionsView.as_view(), name="solutions"),
path("all-solutions/", RedirectView.as_view(pattern_name="index"), name="all_solutions"), path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"), path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"),
path("all_syntheses/", RedirectView.as_view(pattern_name="index"), name="all_syntheses"), path("all_syntheses/", RedirectView.as_view(pattern_name="index"), name="all_syntheses"),
] ]

View File

@ -1,12 +1,31 @@
import zipfile
from io import BytesIO
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView from django.views.generic import DetailView
from django_tables2.views import SingleTableView from django_tables2.views import SingleTableView
from member.models import TFJMUser from member.models import TFJMUser, Solution
from .models import Tournament, Team from .models import Tournament, Team
from .tables import TournamentTable, TeamTable from .tables import TournamentTable, TeamTable, SolutionTable
class AdminMixin(object):
def dispatch(self, request, *args, **kwargs):
if not request.user.admin:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class TeamMixin(object):
def dispatch(self, request, *args, **kwargs):
if not request.user.team:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class TournamentListView(SingleTableView): class TournamentListView(SingleTableView):
@ -61,3 +80,88 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
context["title"] = _("Information about team") context["title"] = _("Information about team")
return context return context
class SolutionsView(LoginRequiredMixin, TeamMixin, SingleTableView):
model = Solution
table_class = SolutionTable
template_name = "tournament/solutions_list.html"
extra_context = dict(title=_("Solutions"))
def post(self, request, *args, **kwargs):
if "zip" in request.POST:
solutions = request.user.team.solutions
out = BytesIO()
zf = zipfile.ZipFile(out, "w")
for solution in solutions:
zf.write(solution.file.path, str(solution) + ".pdf")
zf.close()
resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
resp['Content-Disposition'] = 'attachment; filename={}'\
.format(_("Solutions for team {team}.zip")
.format(team=str(request.user.team)).replace(" ", "%20"))
return resp
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["tournaments"] = \
Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments
return context
def get_queryset(self):
qs = super().get_queryset()
if not self.request.user.admin:
qs = qs.filter(team__tournament__organizers=self.request.user)
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',)
class SolutionsOrgaListView(LoginRequiredMixin, AdminMixin, SingleTableView):
model = Solution
table_class = SolutionTable
template_name = "tournament/solutions_orga_list.html"
extra_context = dict(title=_("All solutions"))
def post(self, request, *args, **kwargs):
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:
raise PermissionDenied
out = BytesIO()
zf = zipfile.ZipFile(out, "w")
for solution in solutions:
zf.write(solution.file.path, str(solution) + ".pdf")
zf.close()
resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
resp['Content-Disposition'] = 'attachment; filename={}'\
.format(_("Solutions for tournament {tournament}.zip")
.format(tournament=str(tournament)).replace(" ", "%20"))
return resp
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["tournaments"] = \
Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments
return context
def get_queryset(self):
qs = super().get_queryset()
if not self.request.user.admin:
qs = qs.filter(team__tournament__organizers=self.request.user)
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',)

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% load i18n django_tables2 %}
{% block content %}
{% render_table table %}
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% load i18n django_tables2 %}
{% block content %}
{% render_table table %}
<hr>
<form method="post">
{% csrf_token %}
<div class="btn-group btn-block">
{% for tournament in tournaments.all %}
<button name="tournament_zip" value="{{ tournament.id }}" class="btn btn-success">{% blocktrans %}{{ tournament }} — ZIP{% endblocktrans %}</button>
{% endfor %}
</div>
</form>
{% endblock %}

View File

@ -43,7 +43,7 @@
{% if team.motivation_letters.count %} {% if team.motivation_letters.count %}
<div class="alert alert-info"> <div class="alert alert-info">
<strong>{% blocktrans with version=team.motivation_letters.count %}Motivation letter (version {{ version }}):{% endblocktrans %}</strong> <strong>{% blocktrans with version=team.motivation_letters.count %}Motivation letter (version {{ version }}):{% endblocktrans %}</strong>
<a data-turbolinks="false" href="{% url "member:document" file=team.motivation_letters.last.file %}">{% trans "Download" %}</a> <a data-turbolinks="false" href="{% url "document" file=team.motivation_letters.last.file %}">{% trans "Download" %}</a>
</div> </div>
{% endif %} {% endif %}