Send syntheses

This commit is contained in:
Yohann D'ANELLO 2020-05-05 00:11:38 +02:00
parent 26eacad2fd
commit b55aa6f4f3
12 changed files with 219 additions and 50 deletions

View File

@ -110,7 +110,7 @@ class SynthesisViewSet(ModelViewSet):
queryset = Synthesis.objects.all() queryset = Synthesis.objects.all()
serializer_class = SynthesisSerializer serializer_class = SynthesisSerializer
filter_backends = [DjangoFilterBackend] 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. # Routers provide an easy way of automatically determining the URL conf.

View File

@ -263,7 +263,7 @@ class Command(BaseCommand):
obj_dict = { obj_dict = {
"file": args[0], "file": args[0],
"team": Team.objects.get(pk=args[1]), "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", if args[4] == "OPPOSANT" else "rapporteur",
"uploaded_at": args[4], "uploaded_at": args[4],
} }

View File

@ -243,6 +243,10 @@ class Solution(Document):
verbose_name=_("final solution"), verbose_name=_("final solution"),
) )
@property
def tournament(self):
return Tournament.get_final() if self.final else self.team.tournament
class Meta: class Meta:
verbose_name = _("solution") verbose_name = _("solution")
verbose_name_plural = _("solutions") verbose_name_plural = _("solutions")
@ -261,14 +265,14 @@ class Synthesis(Document):
verbose_name=_("team"), verbose_name=_("team"),
) )
dest = models.CharField( source = models.CharField(
max_length=16, max_length=16,
choices=[ choices=[
("defender", _("Defender")), ("defender", _("Defender")),
("opponent", _("Opponent")), ("opponent", _("Opponent")),
("rapporteur", _("Rapporteur")), ("rapporteur", _("Rapporteur")),
], ],
verbose_name=_("dest"), verbose_name=_("source"),
) )
round = models.PositiveSmallIntegerField( round = models.PositiveSmallIntegerField(
@ -284,14 +288,18 @@ class Synthesis(Document):
verbose_name=_("final synthesis"), verbose_name=_("final synthesis"),
) )
@property
def tournament(self):
return Tournament.get_final() if self.final else self.team.tournament
class Meta: class Meta:
verbose_name = _("synthesis") verbose_name = _("synthesis")
verbose_name_plural = _("syntheses") verbose_name_plural = _("syntheses")
unique_together = ('team', 'dest', 'round', 'final',) unique_together = ('team', 'source', 'round', 'final',)
def __str__(self): def __str__(self):
return _("Synthesis of team {trigram} that is {dest} for problem {problem}")\ return _("Synthesis of team {trigram} that is {source} for the tournament {tournament}")\
.format(trigram=self.team.trigram, dest=self.dest, problem=self.problem) .format(trigram=self.team.trigram, source=self.source, tournament=self.tournament)
class Config(models.Model): class Config(models.Model):

View File

@ -58,4 +58,4 @@ class SolutionForm(forms.ModelForm):
class SynthesisForm(forms.ModelForm): class SynthesisForm(forms.ModelForm):
class Meta: class Meta:
model = Synthesis model = Synthesis
fields = ('file', 'dest',) fields = ('file', 'source', 'round',)

View File

@ -64,10 +64,22 @@ class Tournament(models.Model):
verbose_name=_("year"), 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 @property
def solutions(self): def solutions(self):
from member.models import Solution 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 @classmethod
def get_final(cls): def get_final(cls):

View File

@ -2,7 +2,7 @@ import django_tables2 as tables
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_tables2 import A from django_tables2 import A
from member.models import Solution from member.models import Solution, Synthesis
from .models import Tournament, Team from .models import Tournament, Team
@ -49,8 +49,8 @@ class SolutionTable(tables.Table):
tournament = tables.LinkColumn( tournament = tables.LinkColumn(
"tournament:detail", "tournament:detail",
args=[A("team.tournament.pk")], args=[A("tournament.pk")],
accessor=A("team.tournament"), accessor=A("tournament"),
) )
file = tables.LinkColumn( file = tables.LinkColumn(
@ -75,6 +75,17 @@ class SolutionTable(tables.Table):
class SynthesisTable(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( file = tables.LinkColumn(
"document", "document",
args=[A("file")], args=[A("file")],
@ -89,8 +100,8 @@ class SynthesisTable(tables.Table):
return _("Download") return _("Download")
class Meta: class Meta:
model = Team model = Synthesis
fields = ("team", "team.tournament", "round", "dest", "uploaded_at", "file", ) fields = ("team", "tournament", "round", "source", "uploaded_at", "file", )
attrs = { attrs = {
'class': 'table table-condensed table-striped table-hover' 'class': 'table table-condensed table-striped table-hover'
} }

View File

@ -1,8 +1,8 @@
from django.urls import path from django.urls import path
from django.views.generic import RedirectView
from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView,\ from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView, \
TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView, SynthesesView,\
SynthesesOrgaListView
app_name = "tournament" app_name = "tournament"
@ -16,6 +16,6 @@ urlpatterns = [
path("add-organizer/", AddOrganizerView.as_view(), name="add_organizer"), path("add-organizer/", AddOrganizerView.as_view(), name="add_organizer"),
path("solutions/", SolutionsView.as_view(), name="solutions"), path("solutions/", SolutionsView.as_view(), name="solutions"),
path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"), path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"), path("syntheses/", SynthesesView.as_view(), name="syntheses"),
path("all_syntheses/", RedirectView.as_view(pattern_name="index"), name="all_syntheses"), path("all_syntheses/", SynthesesOrgaListView.as_view(), name="all_syntheses"),
] ]

View File

@ -10,13 +10,13 @@ from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, CreateView, UpdateView 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 django_tables2.views import SingleTableView
from member.models import TFJMUser, Solution from member.models import TFJMUser, Solution, Synthesis
from .forms import TournamentForm, OrganizerForm, TeamForm, SolutionForm from .forms import TournamentForm, OrganizerForm, TeamForm, SolutionForm, SynthesisForm
from .models import Tournament, Team from .models import Tournament, Team
from .tables import TournamentTable, TeamTable, SolutionTable from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable
class AdminMixin(LoginRequiredMixin): class AdminMixin(LoginRequiredMixin):
@ -26,6 +26,13 @@ class AdminMixin(LoginRequiredMixin):
return super().dispatch(request, *args, **kwargs) 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): class TeamMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.team: if not request.user.team:
@ -181,14 +188,6 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
return super().post(request, *args, **kwargs) 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): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
if not self.request.user.admin: if not self.request.user.admin:
@ -211,16 +210,11 @@ class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
return super().form_valid(form) return super().form_valid(form)
def form_invalid(self, form):
print(form.errors)
return super().form_invalid(form)
def get_success_url(self): def get_success_url(self):
return reverse_lazy("tournament:solutions") return reverse_lazy("tournament:solutions")
class SolutionsOrgaListView(AdminMixin, SingleTableView): class SolutionsOrgaListView(OrgaMixin, SingleTableView):
model = Solution model = Solution
table_class = SolutionTable table_class = SolutionTable
template_name = "tournament/solutions_orga_list.html" template_name = "tournament/solutions_orga_list.html"
@ -262,3 +256,104 @@ class SolutionsOrgaListView(AdminMixin, SingleTableView):
if not self.request.user.admin: if not self.request.user.admin:
qs = qs.filter(team__tournament__organizers=self.request.user) qs = qs.filter(team__tournament__organizers=self.request.user)
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',) 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',)

View File

@ -12,23 +12,33 @@
<dt class="col-xl-6 text-right">{% trans 'role'|capfirst %}</dt> <dt class="col-xl-6 text-right">{% trans 'role'|capfirst %}</dt>
<dd class="col-xl-6">{{ tfjmuser.get_role_display }}</dd> <dd class="col-xl-6">{{ tfjmuser.get_role_display }}</dd>
<dt class="col-xl-6 text-right">{% trans 'team'|capfirst %}</dt> {% if tfjmuser.team %}
<dd class="col-xl-6"><a href="{% url "tournament:team_detail" pk=tfjmuser.team.pk %}">{{ tfjmuser.team }}</a></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>
{% endif %}
<dt class="col-xl-6 text-right">{% trans 'birth date'|capfirst %}</dt> {% if tfjmuser.birth_date %}
<dd class="col-xl-6">{{ tfjmuser.birth_date }}</dd> <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> {% if tfjmuser.participates %}
<dd class="col-xl-6">{{ tfjmuser.get_gender_display }}</dd> <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> {% if tfjmuser.address %}
<dd class="col-xl-6">{{ tfjmuser.address }}, {{ tfjmuser.postal_code }}, {{ tfjmuser.city }}{% if tfjmuser.country != "France" %}, {{ tfjmuser.country }}{% endif %}</dd> <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> <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> <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> {% if tfjmuser.phone_number %}
<dd class="col-xl-6">{{ tfjmuser.phone_number }}</dd> <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' %} {% if tfjmuser.role == '3participant' %}
<dt class="col-xl-6 text-right">{% trans 'school'|capfirst %}</dt> <dt class="col-xl-6 text-right">{% trans 'school'|capfirst %}</dt>
@ -53,9 +63,9 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if tfjmuser.role == '2coach' %} {% if tfjmuser.role != '3participant' %}
<dt class="col-xl-6 text-right">{% trans 'description'|capfirst %}</dt> <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 %} {% endif %}
</dl> </dl>
</div> </div>

View File

@ -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 %}

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

@ -10,7 +10,7 @@
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
<dt class="col-xl-6 text-right">{% trans 'organizers'|capfirst %}</dt> <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> <dt class="col-xl-6 text-right">{% trans 'size'|capfirst %}</dt>
<dd class="col-xl-6">{{ tournament.size }}</dd> <dd class="col-xl-6">{{ tournament.size }}</dd>