Solutions
This commit is contained in:
parent
eee7fc845c
commit
c636f483bb
|
@ -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)
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load i18n django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% render_table table %}
|
||||||
|
{% endblock %}
|
|
@ -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 %}
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue