Send syntheses
This commit is contained in:
parent
26eacad2fd
commit
b55aa6f4f3
|
@ -110,7 +110,7 @@ class SynthesisViewSet(ModelViewSet):
|
|||
queryset = Synthesis.objects.all()
|
||||
serializer_class = SynthesisSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['team', 'team__trigram', 'dest', 'round', ]
|
||||
filterset_fields = ['team', 'team__trigram', 'source', 'round', ]
|
||||
|
||||
|
||||
# Routers provide an easy way of automatically determining the URL conf.
|
||||
|
|
|
@ -263,7 +263,7 @@ class Command(BaseCommand):
|
|||
obj_dict = {
|
||||
"file": args[0],
|
||||
"team": Team.objects.get(pk=args[1]),
|
||||
"dest": "defender" if args[3] == "DEFENDER" else "opponent"
|
||||
"source": "defender" if args[3] == "DEFENDER" else "opponent"
|
||||
if args[4] == "OPPOSANT" else "rapporteur",
|
||||
"uploaded_at": args[4],
|
||||
}
|
||||
|
|
|
@ -243,6 +243,10 @@ class Solution(Document):
|
|||
verbose_name=_("final solution"),
|
||||
)
|
||||
|
||||
@property
|
||||
def tournament(self):
|
||||
return Tournament.get_final() if self.final else self.team.tournament
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("solution")
|
||||
verbose_name_plural = _("solutions")
|
||||
|
@ -261,14 +265,14 @@ class Synthesis(Document):
|
|||
verbose_name=_("team"),
|
||||
)
|
||||
|
||||
dest = models.CharField(
|
||||
source = models.CharField(
|
||||
max_length=16,
|
||||
choices=[
|
||||
("defender", _("Defender")),
|
||||
("opponent", _("Opponent")),
|
||||
("rapporteur", _("Rapporteur")),
|
||||
],
|
||||
verbose_name=_("dest"),
|
||||
verbose_name=_("source"),
|
||||
)
|
||||
|
||||
round = models.PositiveSmallIntegerField(
|
||||
|
@ -284,14 +288,18 @@ class Synthesis(Document):
|
|||
verbose_name=_("final synthesis"),
|
||||
)
|
||||
|
||||
@property
|
||||
def tournament(self):
|
||||
return Tournament.get_final() if self.final else self.team.tournament
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("synthesis")
|
||||
verbose_name_plural = _("syntheses")
|
||||
unique_together = ('team', 'dest', 'round', 'final',)
|
||||
unique_together = ('team', 'source', 'round', 'final',)
|
||||
|
||||
def __str__(self):
|
||||
return _("Synthesis of team {trigram} that is {dest} for problem {problem}")\
|
||||
.format(trigram=self.team.trigram, dest=self.dest, problem=self.problem)
|
||||
return _("Synthesis of team {trigram} that is {source} for the tournament {tournament}")\
|
||||
.format(trigram=self.team.trigram, source=self.source, tournament=self.tournament)
|
||||
|
||||
|
||||
class Config(models.Model):
|
||||
|
|
|
@ -58,4 +58,4 @@ class SolutionForm(forms.ModelForm):
|
|||
class SynthesisForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Synthesis
|
||||
fields = ('file', 'dest',)
|
||||
fields = ('file', 'source', 'round',)
|
||||
|
|
|
@ -64,10 +64,22 @@ class Tournament(models.Model):
|
|||
verbose_name=_("year"),
|
||||
)
|
||||
|
||||
@property
|
||||
def linked_organizers(self):
|
||||
return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
|
||||
for user in self.organizers.all()]
|
||||
|
||||
@property
|
||||
def solutions(self):
|
||||
from member.models import Solution
|
||||
return Solution.objects.filter(team__tournament=self)
|
||||
return Solution.objects.filter(final=self.final) if self.final \
|
||||
else Solution.objects.filter(team__tournament=self)
|
||||
|
||||
@property
|
||||
def syntheses(self):
|
||||
from member.models import Synthesis
|
||||
return Synthesis.objects.filter(final=self.final) if self.final \
|
||||
else Synthesis.objects.filter(team__tournament=self)
|
||||
|
||||
@classmethod
|
||||
def get_final(cls):
|
||||
|
|
|
@ -2,7 +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 member.models import Solution, Synthesis
|
||||
from .models import Tournament, Team
|
||||
|
||||
|
||||
|
@ -49,8 +49,8 @@ class SolutionTable(tables.Table):
|
|||
|
||||
tournament = tables.LinkColumn(
|
||||
"tournament:detail",
|
||||
args=[A("team.tournament.pk")],
|
||||
accessor=A("team.tournament"),
|
||||
args=[A("tournament.pk")],
|
||||
accessor=A("tournament"),
|
||||
)
|
||||
|
||||
file = tables.LinkColumn(
|
||||
|
@ -75,6 +75,17 @@ class SolutionTable(tables.Table):
|
|||
|
||||
|
||||
class SynthesisTable(tables.Table):
|
||||
team = tables.LinkColumn(
|
||||
"tournament:team_detail",
|
||||
args=[A("team.pk")],
|
||||
)
|
||||
|
||||
tournament = tables.LinkColumn(
|
||||
"tournament:detail",
|
||||
args=[A("tournament.pk")],
|
||||
accessor=A("tournament"),
|
||||
)
|
||||
|
||||
file = tables.LinkColumn(
|
||||
"document",
|
||||
args=[A("file")],
|
||||
|
@ -89,8 +100,8 @@ class SynthesisTable(tables.Table):
|
|||
return _("Download")
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = ("team", "team.tournament", "round", "dest", "uploaded_at", "file", )
|
||||
model = Synthesis
|
||||
fields = ("team", "tournament", "round", "source", "uploaded_at", "file", )
|
||||
attrs = {
|
||||
'class': 'table table-condensed table-striped table-hover'
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django.urls import path
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView,\
|
||||
TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView
|
||||
from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
|
||||
TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView,\
|
||||
SynthesesOrgaListView
|
||||
|
||||
app_name = "tournament"
|
||||
|
||||
|
@ -16,6 +16,6 @@ urlpatterns = [
|
|||
path("add-organizer/", AddOrganizerView.as_view(), name="add_organizer"),
|
||||
path("solutions/", SolutionsView.as_view(), name="solutions"),
|
||||
path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
|
||||
path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"),
|
||||
path("all_syntheses/", RedirectView.as_view(pattern_name="index"), name="all_syntheses"),
|
||||
path("syntheses/", SynthesesView.as_view(), name="syntheses"),
|
||||
path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
|
||||
]
|
||||
|
|
|
@ -10,13 +10,13 @@ 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.views.generic.edit import BaseFormView
|
||||
from django_tables2.views import SingleTableView
|
||||
|
||||
from member.models import TFJMUser, Solution
|
||||
from .forms import TournamentForm, OrganizerForm, TeamForm, SolutionForm
|
||||
from member.models import TFJMUser, Solution, Synthesis
|
||||
from .forms import TournamentForm, OrganizerForm, TeamForm, SolutionForm, SynthesisForm
|
||||
from .models import Tournament, Team
|
||||
from .tables import TournamentTable, TeamTable, SolutionTable
|
||||
from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable
|
||||
|
||||
|
||||
class AdminMixin(LoginRequiredMixin):
|
||||
|
@ -26,6 +26,13 @@ class AdminMixin(LoginRequiredMixin):
|
|||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgaMixin(LoginRequiredMixin):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.organizes:
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class TeamMixin(LoginRequiredMixin):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.team:
|
||||
|
@ -181,14 +188,6 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
|
|||
|
||||
return super().post(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:
|
||||
|
@ -211,16 +210,11 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
|
|||
|
||||
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):
|
||||
class SolutionsOrgaListView(OrgaMixin, SingleTableView):
|
||||
model = Solution
|
||||
table_class = SolutionTable
|
||||
template_name = "tournament/solutions_orga_list.html"
|
||||
|
@ -262,3 +256,104 @@ class SolutionsOrgaListView(AdminMixin, SingleTableView):
|
|||
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 SynthesesView(TeamMixin, BaseFormView, SingleTableView):
|
||||
model = Synthesis
|
||||
table_class = SynthesisTable
|
||||
form_class = SynthesisForm
|
||||
template_name = "tournament/syntheses_list.html"
|
||||
extra_context = dict(title=_("Syntheses"))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "zip" in request.POST:
|
||||
syntheses = request.user.team.syntheses
|
||||
|
||||
out = BytesIO()
|
||||
zf = zipfile.ZipFile(out, "w")
|
||||
|
||||
for synthesis in syntheses:
|
||||
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 for team {team}.zip")
|
||||
.format(team=str(request.user.team)).replace(" ", "%20"))
|
||||
return resp
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
if not self.request.user.admin:
|
||||
qs = qs.filter(team=self.request.user.team)
|
||||
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round',
|
||||
'source',)
|
||||
|
||||
def form_valid(self, form):
|
||||
synthesis = form.instance
|
||||
synthesis.team = self.request.user.team
|
||||
synthesis.final = synthesis.team.selected_for_final
|
||||
prev_syn = Synthesis.objects.filter(team=synthesis.team, round=synthesis.round, source=synthesis.source,
|
||||
final=synthesis.final)
|
||||
for syn in prev_syn.all():
|
||||
syn.delete()
|
||||
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
id = ""
|
||||
for _ in range(64):
|
||||
id += random.choice(alphabet)
|
||||
synthesis.file.name = id
|
||||
synthesis.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("tournament:syntheses")
|
||||
|
||||
|
||||
class SynthesesOrgaListView(OrgaMixin, SingleTableView):
|
||||
model = Synthesis
|
||||
table_class = SynthesisTable
|
||||
template_name = "tournament/syntheses_orga_list.html"
|
||||
extra_context = dict(title=_("All syntheses"))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "tournament_zip" in request.POST:
|
||||
tournament = Tournament.objects.get(pk=request.POST["tournament_zip"][0])
|
||||
syntheses = tournament.syntheses
|
||||
if not request.user.admin and request.user not in tournament.organizers.all():
|
||||
raise PermissionDenied
|
||||
|
||||
out = BytesIO()
|
||||
zf = zipfile.ZipFile(out, "w")
|
||||
|
||||
for synthesis in syntheses:
|
||||
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 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', 'round',
|
||||
'source',)
|
||||
|
||||
|
|
|
@ -12,23 +12,33 @@
|
|||
<dt class="col-xl-6 text-right">{% trans 'role'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.get_role_display }}</dd>
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'team'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="{% url "tournament:team_detail" pk=tfjmuser.team.pk %}">{{ tfjmuser.team }}</a></dd>
|
||||
{% if tfjmuser.team %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'team'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="{% url "tournament:team_detail" pk=tfjmuser.team.pk %}">{{ tfjmuser.team }}</a></dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'birth date'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.birth_date }}</dd>
|
||||
{% if tfjmuser.birth_date %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'birth date'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.birth_date }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'gender'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.get_gender_display }}</dd>
|
||||
{% if tfjmuser.participates %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'gender'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.get_gender_display }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'address'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.address }}, {{ tfjmuser.postal_code }}, {{ tfjmuser.city }}{% if tfjmuser.country != "France" %}, {{ tfjmuser.country }}{% endif %}</dd>
|
||||
{% if tfjmuser.address %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'address'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.address }}, {{ tfjmuser.postal_code }}, {{ tfjmuser.city }}{% if tfjmuser.country != "France" %}, {{ tfjmuser.country }}{% endif %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'email'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="mailto:{{ tfjmuser.email }}">{{ tfjmuser.email }}</a></dd>
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'phone number'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.phone_number }}</dd>
|
||||
{% if tfjmuser.phone_number %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'phone number'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.phone_number }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if tfjmuser.role == '3participant' %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'school'|capfirst %}</dt>
|
||||
|
@ -53,9 +63,9 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if tfjmuser.role == '2coach' %}
|
||||
{% if tfjmuser.role != '3participant' %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'description'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tfjmuser.description }}</dd>
|
||||
<dd class="col-xl-6">{{ tfjmuser.description|default_if_none:"" }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n crispy_forms_filters django_tables2 %}
|
||||
|
||||
{% block content %}
|
||||
{% if form %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-block btn-success">{% trans "Submit" %}</button>
|
||||
</form>
|
||||
<hr>
|
||||
{% endif %}
|
||||
{% 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 %}
|
|
@ -10,7 +10,7 @@
|
|||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-xl-6 text-right">{% trans 'organizers'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tournament.organizers.all|join:", " }}</dd>
|
||||
<dd class="col-xl-6">{% autoescape off %}{{ tournament.linked_organizers|join:", " }}{% endautoescape %}</dd>
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'size'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ tournament.size }}</dd>
|
||||
|
|
Loading…
Reference in New Issue