mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2024-12-25 17:02:28 +00:00
Pool support
This commit is contained in:
parent
b6422c1a79
commit
104ca590a5
@ -9,7 +9,7 @@ from rest_framework.filters import SearchFilter
|
|||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from member.models import TFJMUser, Authorization, Solution, Synthesis, MotivationLetter
|
from member.models import TFJMUser, Authorization, Solution, Synthesis, MotivationLetter
|
||||||
from tournament.models import Team, Tournament
|
from tournament.models import Team, Tournament, Pool
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
@ -59,6 +59,12 @@ class SynthesisSerializer(serializers.ModelSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class PoolSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Pool
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(ModelViewSet):
|
class UserViewSet(ModelViewSet):
|
||||||
queryset = TFJMUser.objects.all()
|
queryset = TFJMUser.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
@ -113,6 +119,13 @@ class SynthesisViewSet(ModelViewSet):
|
|||||||
filterset_fields = ['team', 'team__trigram', 'source', 'round', ]
|
filterset_fields = ['team', 'team__trigram', 'source', 'round', ]
|
||||||
|
|
||||||
|
|
||||||
|
class PoolViewSet(ModelViewSet):
|
||||||
|
queryset = Pool.objects.all()
|
||||||
|
serializer_class = PoolSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['teams', 'teams__trigram', 'round', ]
|
||||||
|
|
||||||
|
|
||||||
# Routers provide an easy way of automatically determining the URL conf.
|
# Routers provide an easy way of automatically determining the URL conf.
|
||||||
# Register each app API router and user viewset
|
# Register each app API router and user viewset
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import random
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
@ -151,6 +152,16 @@ class DocumentView(LoginRequiredMixin, View):
|
|||||||
if isinstance(doc, Solution) or isinstance(doc, Synthesis) or isinstance(doc, MotivationLetter):
|
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()
|
grant = grant or doc.team == request.user.team or request.user in doc.team.tournament.organizers.all()
|
||||||
|
|
||||||
|
if isinstance(doc, Synthesis) and request.user.organizes:
|
||||||
|
grant = True
|
||||||
|
|
||||||
|
if isinstance(doc, Solution):
|
||||||
|
for pool in doc.pools.all():
|
||||||
|
if pool.round == 2 and datetime.now() < doc.tournament.date_solutions_2:
|
||||||
|
continue
|
||||||
|
if self.request.user.team in pool.teams.all():
|
||||||
|
grant = True
|
||||||
|
|
||||||
if not grant:
|
if not grant:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.contrib.auth.admin import admin
|
from django.contrib.auth.admin import admin
|
||||||
|
|
||||||
from tournament.models import Team, Tournament, Payment
|
from tournament.models import Team, Tournament, Pool, Payment
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Team)
|
@admin.register(Team)
|
||||||
@ -13,6 +13,11 @@ class TournamentAdmin(admin.ModelAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Pool)
|
||||||
|
class PoolAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Payment)
|
@admin.register(Payment)
|
||||||
class PaymentAdmin(admin.ModelAdmin):
|
class PaymentAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
@ -3,14 +3,20 @@ import re
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from member.models import TFJMUser, Solution, Synthesis
|
from member.models import TFJMUser, Solution, Synthesis
|
||||||
from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput
|
from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput
|
||||||
from tournament.models import Tournament, Team
|
from tournament.models import Tournament, Team, Pool
|
||||||
|
|
||||||
|
|
||||||
class TournamentForm(forms.ModelForm):
|
class TournamentForm(forms.ModelForm):
|
||||||
|
organizers = forms.ModelMultipleChoiceField(
|
||||||
|
TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer")).order_by('role'),
|
||||||
|
label=_("Organizers"),
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
@ -122,3 +128,74 @@ class SynthesisForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Synthesis
|
model = Synthesis
|
||||||
fields = ('file', 'source', 'round',)
|
fields = ('file', 'source', 'round',)
|
||||||
|
|
||||||
|
|
||||||
|
class PoolForm(forms.ModelForm):
|
||||||
|
team1 = forms.ModelChoiceField(
|
||||||
|
Team.objects.filter(validation_status="2valid").all(),
|
||||||
|
empty_label=_("Choose a team..."),
|
||||||
|
label=_("Team 1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
problem1 = forms.IntegerField(
|
||||||
|
min_value=1,
|
||||||
|
max_value=8,
|
||||||
|
initial=1,
|
||||||
|
label=_("Problem defended by team 1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
team2 = forms.ModelChoiceField(
|
||||||
|
Team.objects.filter(validation_status="2valid").all(),
|
||||||
|
empty_label=_("Choose a team..."),
|
||||||
|
label=_("Team 2"),
|
||||||
|
)
|
||||||
|
|
||||||
|
problem2 = forms.IntegerField(
|
||||||
|
min_value=1,
|
||||||
|
max_value=8,
|
||||||
|
initial=2,
|
||||||
|
label=_("Problem defended by team 2"),
|
||||||
|
)
|
||||||
|
|
||||||
|
team3 = forms.ModelChoiceField(
|
||||||
|
Team.objects.filter(validation_status="2valid").all(),
|
||||||
|
empty_label=_("Choose a team..."),
|
||||||
|
label=_("Team 3"),
|
||||||
|
)
|
||||||
|
|
||||||
|
problem3 = forms.IntegerField(
|
||||||
|
min_value=1,
|
||||||
|
max_value=8,
|
||||||
|
initial=3,
|
||||||
|
label=_("Problem defended by team 3"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
|
team1, pb1 = cleaned_data["team1"], cleaned_data["problem1"]
|
||||||
|
team2, pb2 = cleaned_data["team2"], cleaned_data["problem2"]
|
||||||
|
team3, pb3 = cleaned_data["team3"], cleaned_data["problem3"]
|
||||||
|
|
||||||
|
sol1 = Solution.objects.get(team=team1, problem=pb1, final=team1.selected_for_final)
|
||||||
|
sol2 = Solution.objects.get(team=team2, problem=pb2, final=team2.selected_for_final)
|
||||||
|
sol3 = Solution.objects.get(team=team3, problem=pb3, final=team3.selected_for_final)
|
||||||
|
|
||||||
|
cleaned_data["teams"] = [team1, team2, team3]
|
||||||
|
cleaned_data["solutions"] = [sol1, sol2, sol3]
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
pool = super().save(commit)
|
||||||
|
|
||||||
|
pool.refresh_from_db()
|
||||||
|
pool.teams.set(self.cleaned_data["teams"])
|
||||||
|
pool.solutions.set(self.cleaned_data["solutions"])
|
||||||
|
pool.save()
|
||||||
|
|
||||||
|
return pool
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Pool
|
||||||
|
fields = ('round', 'juries',)
|
||||||
|
@ -198,7 +198,52 @@ class Team(models.Model):
|
|||||||
unique_together = (('name', 'year',), ('trigram', 'year',),)
|
unique_together = (('name', 'year',), ('trigram', 'year',),)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.trigram + " -- " + self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Pool(models.Model):
|
||||||
|
teams = models.ManyToManyField(
|
||||||
|
Team,
|
||||||
|
related_name="pools",
|
||||||
|
verbose_name=_("teams"),
|
||||||
|
)
|
||||||
|
|
||||||
|
solutions = models.ManyToManyField(
|
||||||
|
"member.Solution",
|
||||||
|
related_name="pools",
|
||||||
|
verbose_name=_("solutions"),
|
||||||
|
)
|
||||||
|
|
||||||
|
round = models.PositiveIntegerField(
|
||||||
|
choices=[
|
||||||
|
(1, _("Round 1")),
|
||||||
|
(2, _("Round 2")),
|
||||||
|
],
|
||||||
|
verbose_name=_("round"),
|
||||||
|
)
|
||||||
|
|
||||||
|
juries = models.ManyToManyField(
|
||||||
|
"member.TFJMUser",
|
||||||
|
related_name="pools",
|
||||||
|
verbose_name=_("juries"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def problems(self):
|
||||||
|
return list(d["problem"] for d in self.solutions.values("problem").all())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tournament(self):
|
||||||
|
return self.solutions.first().tournament
|
||||||
|
|
||||||
|
@property
|
||||||
|
def syntheses(self):
|
||||||
|
from member.models import Synthesis
|
||||||
|
return Synthesis.objects.filter(team__in=self.teams.all(), round=self.round, final=self.tournament.final)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("pool")
|
||||||
|
verbose_name_plural = _("pools")
|
||||||
|
|
||||||
|
|
||||||
class Payment(models.Model):
|
class Payment(models.Model):
|
||||||
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
|
|||||||
from django_tables2 import A
|
from django_tables2 import A
|
||||||
|
|
||||||
from member.models import Solution, Synthesis
|
from member.models import Solution, Synthesis
|
||||||
from .models import Tournament, Team
|
from .models import Tournament, Team, Pool
|
||||||
|
|
||||||
|
|
||||||
class TournamentTable(tables.Table):
|
class TournamentTable(tables.Table):
|
||||||
@ -105,3 +105,21 @@ class SynthesisTable(tables.Table):
|
|||||||
attrs = {
|
attrs = {
|
||||||
'class': 'table table-condensed table-striped table-hover'
|
'class': 'table table-condensed table-striped table-hover'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PoolTable(tables.Table):
|
||||||
|
def render_teams(self, value):
|
||||||
|
return ", ".join(team.trigram for team in value.all())
|
||||||
|
|
||||||
|
def render_problems(self, value):
|
||||||
|
return ", ".join([str(pb) for pb in value])
|
||||||
|
|
||||||
|
def render_juries(self, value):
|
||||||
|
return ", ".join(str(jury) for jury in value.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Pool
|
||||||
|
fields = ("teams", "problems", "round", "juries", )
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-condensed table-striped table-hover'
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ from django.urls import path
|
|||||||
|
|
||||||
from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
|
from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
|
||||||
TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView, \
|
TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView, \
|
||||||
SynthesesOrgaListView
|
SynthesesOrgaListView, PoolListView, PoolCreateView, PoolDetailView
|
||||||
|
|
||||||
app_name = "tournament"
|
app_name = "tournament"
|
||||||
|
|
||||||
@ -18,4 +18,7 @@ urlpatterns = [
|
|||||||
path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
|
path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
|
||||||
path("syntheses/", SynthesesView.as_view(), name="syntheses"),
|
path("syntheses/", SynthesesView.as_view(), name="syntheses"),
|
||||||
path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
|
path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
|
||||||
|
path("pools/", PoolListView.as_view(), name="pools"),
|
||||||
|
path("pools/add/", PoolCreateView.as_view(), name="create_pool"),
|
||||||
|
path("pool/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
||||||
]
|
]
|
||||||
|
@ -17,9 +17,9 @@ from django.views.generic.edit import BaseFormView
|
|||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
|
|
||||||
from member.models import TFJMUser, Solution, Synthesis
|
from member.models import TFJMUser, Solution, Synthesis
|
||||||
from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm
|
from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm, PoolForm
|
||||||
from .models import Tournament, Team
|
from .models import Tournament, Team, Pool
|
||||||
from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable
|
from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, PoolTable
|
||||||
|
|
||||||
|
|
||||||
class AdminMixin(LoginRequiredMixin):
|
class AdminMixin(LoginRequiredMixin):
|
||||||
@ -411,3 +411,73 @@ class SynthesesOrgaListView(OrgaMixin, SingleTableView):
|
|||||||
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round',
|
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round',
|
||||||
'source',)
|
'source',)
|
||||||
|
|
||||||
|
|
||||||
|
class PoolListView(LoginRequiredMixin, SingleTableView):
|
||||||
|
model = Pool
|
||||||
|
table_class = PoolTable
|
||||||
|
extra_context = dict(title=_("Pools"))
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset()
|
||||||
|
user = self.request.user
|
||||||
|
if not user.admin and user.organizes:
|
||||||
|
qs = qs.filter(Q(jurys=user) | Q(solutions__tournament__organizers=user))
|
||||||
|
elif user.participates:
|
||||||
|
qs = qs.filter(teams=user.team)
|
||||||
|
return qs.distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class PoolCreateView(AdminMixin, CreateView):
|
||||||
|
model = Pool
|
||||||
|
form_class = PoolForm
|
||||||
|
extra_context = dict(title=_("Create pool"))
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy("tournament:pools")
|
||||||
|
|
||||||
|
|
||||||
|
class PoolDetailView(LoginRequiredMixin, DetailView):
|
||||||
|
model = Pool
|
||||||
|
extra_context = dict(title=_("Pool detail"))
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset()
|
||||||
|
user = self.request.user
|
||||||
|
if not user.admin and user.organizes:
|
||||||
|
qs = qs.filter(Q(jurys=user) | Q(solutions__tournament__organizers=user))
|
||||||
|
elif user.participates:
|
||||||
|
qs = qs.filter(teams=user.team)
|
||||||
|
return qs.distinct()
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
pool = self.get_object()
|
||||||
|
|
||||||
|
if "solutions_zip" in request.POST:
|
||||||
|
out = BytesIO()
|
||||||
|
zf = zipfile.ZipFile(out, "w")
|
||||||
|
|
||||||
|
for solution in pool.solutions.all():
|
||||||
|
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 of a pool.zip").replace(" ", "%20"))
|
||||||
|
return resp
|
||||||
|
elif "syntheses_zip" in request.POST:
|
||||||
|
out = BytesIO()
|
||||||
|
zf = zipfile.ZipFile(out, "w")
|
||||||
|
|
||||||
|
for synthesis in pool.syntheses.all():
|
||||||
|
zf.write(synthesis.file.path, str(synthesis) + ".pdf")
|
||||||
|
|
||||||
|
zf.close()
|
||||||
|
|
||||||
|
resp = HttpResponse(out.getvalue(), content_type="application/x-zip-compressed")
|
||||||
|
resp['Content-Disposition'] = 'attachment; filename={}' \
|
||||||
|
.format(_("Syntheses of a pool.zip").replace(" ", "%20"))
|
||||||
|
return resp
|
||||||
|
|
||||||
|
return self.get(request, *args, **kwargs)
|
@ -129,6 +129,9 @@
|
|||||||
<a class="nav-link" href="{% url "tournament:all_syntheses" %}"><i class="fas fa-feather"></i> {% trans "Syntheses" %}</a>
|
<a class="nav-link" href="{% url "tournament:all_syntheses" %}"><i class="fas fa-feather"></i> {% trans "Syntheses" %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="{% url "tournament:pools" %}"><i class="fas fa-swimming-pool"></i> {% trans "Pools" %}</a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="https://www.helloasso.com/associations/animath/formulaires/5/widget"><i
|
<a class="nav-link" href="https://www.helloasso.com/associations/animath/formulaires/5/widget"><i
|
||||||
|
70
templates/tournament/pool_detail.html
Normal file
70
templates/tournament/pool_detail.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load getconfig i18n django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4>{{ title }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-xl-6 text-right">{% trans 'juries'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ pool.juries.all|join:", " }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6 text-right">{% trans 'teams'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ pool.teams.all|join:", " }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6 text-right">{% trans 'round'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ pool.round }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6 text-right">{% trans 'tournament'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ pool.tournament }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4>{% trans "Solutions" %}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul>
|
||||||
|
{% for solution in pool.solutions.all %}
|
||||||
|
<li><a data-turbolinks="false" href="{{ solution.file.url }}">{{ solution }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="btn btn-success" name="solutions_zip">{% trans "Download ZIP archive" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if user.organizes %}
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4>{% trans "Syntheses" %}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul>
|
||||||
|
{% for synthesis in pool.syntheses.all %}
|
||||||
|
<li><a data-turbolinks="false" href="{{ synthesis.file.url }}">{{ synthesis }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="btn btn-success" name="syntheses_zip">{% trans "Download ZIP archive" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
11
templates/tournament/pool_form.html
Normal file
11
templates/tournament/pool_form.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load i18n crispy_forms_filters %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<input type="submit" class="btn btn-primary btn-block" value="{% trans "Submit" %}">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
12
templates/tournament/pool_list.html
Normal file
12
templates/tournament/pool_list.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load i18n django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% render_table table %}
|
||||||
|
|
||||||
|
{% if user.admin %}
|
||||||
|
<hr>
|
||||||
|
<a href="{% url "tournament:create_pool" %}"><button class="btn btn-secondary btn-block">{% trans "Add pool" %}</button></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user