2020-05-04 21:37:21 +00:00
|
|
|
import random
|
2020-04-30 18:11:03 +00:00
|
|
|
import zipfile
|
2020-05-05 21:06:32 +00:00
|
|
|
from datetime import timedelta
|
2020-04-30 18:11:03 +00:00
|
|
|
from io import BytesIO
|
|
|
|
|
2020-05-25 16:27:07 +00:00
|
|
|
from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
|
2020-04-30 18:11:03 +00:00
|
|
|
from django.core.exceptions import PermissionDenied
|
2020-05-05 00:47:17 +00:00
|
|
|
from django.core.mail import send_mail
|
2020-04-29 14:26:52 +00:00
|
|
|
from django.db.models import Q
|
2020-04-30 18:11:03 +00:00
|
|
|
from django.http import HttpResponse
|
2020-05-04 18:21:53 +00:00
|
|
|
from django.shortcuts import redirect
|
2020-05-05 00:47:17 +00:00
|
|
|
from django.template.loader import render_to_string
|
2020-05-04 18:21:53 +00:00
|
|
|
from django.urls import reverse_lazy
|
2020-05-05 11:54:26 +00:00
|
|
|
from django.utils import timezone
|
2020-04-29 14:26:52 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2020-05-04 18:21:53 +00:00
|
|
|
from django.views.generic import DetailView, CreateView, UpdateView
|
2020-05-04 22:11:38 +00:00
|
|
|
from django.views.generic.edit import BaseFormView
|
2020-04-29 14:26:52 +00:00
|
|
|
from django_tables2.views import SingleTableView
|
2020-05-04 22:11:38 +00:00
|
|
|
from member.models import TFJMUser, Solution, Synthesis
|
2020-05-05 15:12:24 +00:00
|
|
|
|
2020-05-05 02:45:38 +00:00
|
|
|
from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm, PoolForm
|
|
|
|
from .models import Tournament, Team, Pool
|
|
|
|
from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, PoolTable
|
2020-04-30 18:11:03 +00:00
|
|
|
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
class AdminMixin(LoginRequiredMixin):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
If a view extends this mixin, then the view will be only accessible to administrators.
|
|
|
|
"""
|
|
|
|
|
2020-04-30 18:11:03 +00:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
2020-05-05 00:20:45 +00:00
|
|
|
if not request.user.is_authenticated or not request.user.admin:
|
2020-04-30 18:11:03 +00:00
|
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2020-05-25 16:27:07 +00:00
|
|
|
class OrgaMixin(AccessMixin):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
If a view extends this mixin, then the view will be only accessible to administrators or organizers.
|
|
|
|
"""
|
|
|
|
|
2020-05-04 22:11:38 +00:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
2020-05-25 16:27:07 +00:00
|
|
|
if not request.user.is_authenticated and not request.session["extra_access_token"]:
|
|
|
|
return self.handle_no_permission()
|
|
|
|
elif request.user.is_authenticated and not request.user.organizes:
|
2020-05-04 22:11:38 +00:00
|
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
class TeamMixin(LoginRequiredMixin):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
If a view extends this mixin, then the view will be only accessible to users that are registered in a team.
|
|
|
|
"""
|
|
|
|
|
2020-04-30 18:11:03 +00:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
2020-05-05 00:20:45 +00:00
|
|
|
if not request.user.is_authenticated or not request.user.team:
|
2020-04-30 18:11:03 +00:00
|
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
2020-04-29 14:26:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TournamentListView(SingleTableView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Display the list of all tournaments, ordered by start date then name.
|
|
|
|
"""
|
|
|
|
|
2020-04-29 14:26:52 +00:00
|
|
|
model = Tournament
|
|
|
|
table_class = TournamentTable
|
|
|
|
extra_context = dict(title=_("Tournaments list"),)
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
|
2020-04-29 14:59:59 +00:00
|
|
|
team_users = TFJMUser.objects.filter(Q(team__isnull=False) | Q(role="admin") | Q(role="organizer"))\
|
|
|
|
.order_by('-role')
|
|
|
|
valid_team_users = team_users.filter(
|
|
|
|
Q(team__validation_status="2valid") | Q(role="admin") | Q(role="organizer"))
|
2020-04-29 14:26:52 +00:00
|
|
|
|
|
|
|
context["team_users_emails"] = [user.email for user in team_users]
|
|
|
|
context["valid_team_users_emails"] = [user.email for user in valid_team_users]
|
|
|
|
|
|
|
|
return context
|
2020-04-29 14:59:59 +00:00
|
|
|
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
class TournamentCreateView(AdminMixin, CreateView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Create a tournament. Only accessible to admins.
|
|
|
|
"""
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
model = Tournament
|
|
|
|
form_class = TournamentForm
|
|
|
|
extra_context = dict(title=_("Add tournament"),)
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
return reverse_lazy('tournament:detail', args=(self.object.pk,))
|
|
|
|
|
|
|
|
|
2020-04-29 14:59:59 +00:00
|
|
|
class TournamentDetailView(DetailView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Display the detail of a tournament.
|
|
|
|
Accessible to all, including not authenticated users.
|
|
|
|
"""
|
|
|
|
|
2020-04-29 14:59:59 +00:00
|
|
|
model = Tournament
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
|
|
|
|
context["title"] = _("Tournament of {name}").format(name=self.object.name)
|
|
|
|
|
2020-05-20 13:47:45 +00:00
|
|
|
if self.object.final:
|
|
|
|
team_users = TFJMUser.objects.filter(team__selected_for_final=True)
|
|
|
|
valid_team_users = team_users
|
|
|
|
else:
|
|
|
|
team_users = TFJMUser.objects.filter(
|
|
|
|
Q(team__tournament=self.object)
|
|
|
|
| Q(organized_tournaments=self.object)).order_by('role')
|
|
|
|
valid_team_users = team_users.filter(
|
|
|
|
Q(team__validation_status="2valid")
|
|
|
|
| Q(role="admin")
|
|
|
|
| Q(organized_tournaments=self.object))
|
2020-04-29 14:59:59 +00:00
|
|
|
|
|
|
|
context["team_users_emails"] = [user.email for user in team_users]
|
|
|
|
context["valid_team_users_emails"] = [user.email for user in valid_team_users]
|
|
|
|
|
|
|
|
context["teams"] = TeamTable(self.object.teams.all())
|
|
|
|
|
|
|
|
return context
|
2020-04-29 15:58:11 +00:00
|
|
|
|
|
|
|
|
2020-05-11 12:08:19 +00:00
|
|
|
class TournamentUpdateView(OrgaMixin, UpdateView):
|
|
|
|
"""
|
|
|
|
Update the data of a tournament.
|
|
|
|
Reserved to admins and organizers of the tournament.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Restrict the view to organizers of tournaments, then process the request.
|
|
|
|
"""
|
|
|
|
if self.request.user.role == "1volunteer" and self.request.user not in self.get_object().organizers.all():
|
|
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
model = Tournament
|
|
|
|
form_class = TournamentForm
|
|
|
|
extra_context = dict(title=_("Update tournament"),)
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
return reverse_lazy('tournament:detail', args=(self.object.pk,))
|
|
|
|
|
|
|
|
|
2020-04-29 15:58:11 +00:00
|
|
|
class TeamDetailView(LoginRequiredMixin, DetailView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
View the detail of a team.
|
|
|
|
Restricted to this team, admins and organizers of its tournament.
|
|
|
|
"""
|
2020-04-29 15:58:11 +00:00
|
|
|
model = Team
|
|
|
|
|
2020-04-30 19:07:12 +00:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Protect the page and process the request.
|
|
|
|
"""
|
2020-05-05 00:20:45 +00:00
|
|
|
if not request.user.is_authenticated or \
|
|
|
|
(not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all()
|
2020-05-18 21:51:13 +00:00
|
|
|
and not (self.get_object().selected_for_final
|
|
|
|
and request.user in Tournament.get_final().organizers.all())
|
2020-05-05 00:20:45 +00:00
|
|
|
and self.get_object() != request.user.team):
|
2020-04-30 19:07:12 +00:00
|
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Process POST requests. Supported requests:
|
|
|
|
- get the solutions of the team as a ZIP archive
|
|
|
|
- a user leaves its team (if the composition is not validated yet)
|
|
|
|
- the team requests the validation
|
|
|
|
- Organizers can validate or invalidate the request
|
|
|
|
- Admins can delete teams
|
|
|
|
- Admins can select teams for the final tournament
|
|
|
|
"""
|
2020-04-30 19:07:12 +00:00
|
|
|
team = self.get_object()
|
|
|
|
if "zip" in request.POST:
|
|
|
|
solutions = team.solutions.all()
|
|
|
|
|
|
|
|
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(team)).replace(" ", "%20"))
|
|
|
|
return resp
|
2020-05-05 00:20:45 +00:00
|
|
|
elif "leave" in request.POST and request.user.participates:
|
2020-05-04 22:56:34 +00:00
|
|
|
request.user.team = None
|
|
|
|
request.user.save()
|
|
|
|
if not team.users.exists():
|
|
|
|
team.delete()
|
|
|
|
return redirect('tournament:detail', pk=team.tournament.pk)
|
2020-05-11 12:08:19 +00:00
|
|
|
elif "request_validation" in request.POST and request.user.participates and team.can_validate:
|
2020-05-05 00:20:45 +00:00
|
|
|
team.validation_status = "1waiting"
|
|
|
|
team.save()
|
2020-05-05 15:23:33 +00:00
|
|
|
team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team)
|
2020-05-05 00:20:45 +00:00
|
|
|
return redirect('tournament:team_detail', pk=team.pk)
|
|
|
|
elif "validate" in request.POST and request.user.organizes:
|
|
|
|
team.validation_status = "2valid"
|
|
|
|
team.save()
|
2020-05-05 15:12:24 +00:00
|
|
|
team.send_mail("validate_team", "Équipe validée TFJM²")
|
2020-05-05 00:20:45 +00:00
|
|
|
return redirect('tournament:team_detail', pk=team.pk)
|
|
|
|
elif "invalidate" in request.POST and request.user.organizes:
|
|
|
|
team.validation_status = "0invalid"
|
|
|
|
team.save()
|
2020-05-05 15:12:24 +00:00
|
|
|
team.send_mail("unvalidate_team", "Équipe non validée TFJM²")
|
2020-05-05 00:20:45 +00:00
|
|
|
return redirect('tournament:team_detail', pk=team.pk)
|
|
|
|
elif "delete" in request.POST and request.user.organizes:
|
2020-05-04 18:21:53 +00:00
|
|
|
team.delete()
|
|
|
|
return redirect('tournament:detail', pk=team.tournament.pk)
|
2020-05-06 16:10:33 +00:00
|
|
|
elif "select_final" in request.POST and request.user.admin and not team.selected_for_final and team.pools:
|
2020-05-11 12:08:19 +00:00
|
|
|
# We copy all solutions for solutions for the final
|
2020-05-06 16:10:33 +00:00
|
|
|
for solution in team.solutions.all():
|
|
|
|
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
|
|
|
id = ""
|
|
|
|
for i in range(64):
|
|
|
|
id += random.choice(alphabet)
|
|
|
|
with solution.file.open("rb") as source:
|
|
|
|
with open("/code/media/" + id, "wb") as dest:
|
|
|
|
for chunk in source.chunks():
|
|
|
|
dest.write(chunk)
|
|
|
|
new_sol = Solution(
|
|
|
|
file=id,
|
|
|
|
team=team,
|
|
|
|
problem=solution.problem,
|
|
|
|
final=True,
|
|
|
|
)
|
|
|
|
new_sol.save()
|
|
|
|
team.selected_for_final = True
|
|
|
|
team.save()
|
2020-05-07 17:19:58 +00:00
|
|
|
team.send_mail("select_for_final", "Sélection pour la finale, félicitations ! - TFJM²",
|
|
|
|
final=Tournament.get_final())
|
2020-05-06 16:10:33 +00:00
|
|
|
return redirect('tournament:team_detail', pk=team.pk)
|
2020-04-30 19:07:12 +00:00
|
|
|
|
|
|
|
return self.get(request, *args, **kwargs)
|
|
|
|
|
2020-04-29 15:58:11 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
|
|
|
|
context["title"] = _("Information about team")
|
2020-05-25 16:27:07 +00:00
|
|
|
context["ordered_solutions"] = self.object.solutions.order_by('final', 'problem',).all()
|
2020-05-05 22:20:39 +00:00
|
|
|
context["team_users_emails"] = [user.email for user in self.object.users.all()]
|
2020-04-29 15:58:11 +00:00
|
|
|
|
|
|
|
return context
|
2020-04-30 18:11:03 +00:00
|
|
|
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
class TeamUpdateView(LoginRequiredMixin, UpdateView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Update the information about a team.
|
|
|
|
Team members, admins and organizers are allowed to do this.
|
|
|
|
"""
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
model = Team
|
|
|
|
form_class = TeamForm
|
2020-05-04 20:27:45 +00:00
|
|
|
extra_context = dict(title=_("Update team"),)
|
2020-05-04 18:21:53 +00:00
|
|
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
2020-05-04 20:27:45 +00:00
|
|
|
if not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all() \
|
|
|
|
and self.get_object() != self.request.user.team:
|
2020-05-04 18:21:53 +00:00
|
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class AddOrganizerView(AdminMixin, CreateView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Add a new organizer account. No password is created, the user should reset its password using the link
|
|
|
|
sent by mail. Only name and email are requested.
|
|
|
|
Only admins are granted to do this.
|
|
|
|
"""
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
model = TFJMUser
|
|
|
|
form_class = OrganizerForm
|
|
|
|
extra_context = dict(title=_("Add organizer"),)
|
|
|
|
template_name = "tournament/add_organizer.html"
|
|
|
|
|
2020-05-05 00:47:17 +00:00
|
|
|
def form_valid(self, form):
|
|
|
|
user = form.instance
|
2020-05-05 15:29:35 +00:00
|
|
|
msg = render_to_string("mail_templates/add_organizer.txt", context=dict(user=user))
|
|
|
|
msg_html = render_to_string("mail_templates/add_organizer.html", context=dict(user=user))
|
|
|
|
send_mail('Organisateur du TFJM² 2020', msg, 'contact@tfjm.org', [user.email], html_message=msg_html)
|
2020-05-05 00:47:17 +00:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
return reverse_lazy('index')
|
|
|
|
|
2020-05-04 18:21:53 +00:00
|
|
|
|
2020-05-04 21:37:21 +00:00
|
|
|
class SolutionsView(TeamMixin, BaseFormView, SingleTableView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Upload and view solutions for a team.
|
|
|
|
"""
|
|
|
|
|
2020-04-30 18:11:03 +00:00
|
|
|
model = Solution
|
|
|
|
table_class = SolutionTable
|
2020-05-04 21:37:21 +00:00
|
|
|
form_class = SolutionForm
|
2020-04-30 18:11:03 +00:00
|
|
|
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
|
|
|
|
|
2020-05-04 21:37:21 +00:00
|
|
|
return super().post(request, *args, **kwargs)
|
2020-04-30 18:11:03 +00:00
|
|
|
|
2020-05-05 20:29:10 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
self.object_list = self.get_queryset()
|
2020-05-05 21:19:18 +00:00
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context["now"] = timezone.now()
|
2020-05-23 10:19:14 +00:00
|
|
|
context["real_deadline"] = self.request.user.team.future_tournament.date_solutions + timedelta(minutes=30)
|
2020-05-05 21:19:18 +00:00
|
|
|
return context
|
2020-05-05 20:29:10 +00:00
|
|
|
|
2020-04-30 18:11:03 +00:00
|
|
|
def get_queryset(self):
|
2020-05-06 19:10:48 +00:00
|
|
|
qs = super().get_queryset().filter(team=self.request.user.team)
|
2020-05-06 16:10:33 +00:00
|
|
|
return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
|
|
|
|
'problem',)
|
2020-04-30 18:11:03 +00:00
|
|
|
|
2020-05-04 21:37:21 +00:00
|
|
|
def form_valid(self, form):
|
|
|
|
solution = form.instance
|
|
|
|
solution.team = self.request.user.team
|
|
|
|
solution.final = solution.team.selected_for_final
|
2020-05-05 00:20:45 +00:00
|
|
|
|
2020-05-05 21:06:32 +00:00
|
|
|
if timezone.now() > solution.tournament.date_solutions + timedelta(minutes=30):
|
2020-05-05 20:29:10 +00:00
|
|
|
form.add_error('file', _("You can't publish your solution anymore. Deadline: {date:%m-%d-%Y %H:%M}.")
|
2020-05-05 21:06:32 +00:00
|
|
|
.format(date=timezone.localtime(solution.tournament.date_solutions)))
|
2020-05-05 00:20:45 +00:00
|
|
|
return super().form_invalid(form)
|
|
|
|
|
2020-05-04 21:37:21 +00:00
|
|
|
prev_sol = Solution.objects.filter(problem=solution.problem, team=solution.team, final=solution.final)
|
|
|
|
for sol in prev_sol.all():
|
|
|
|
sol.delete()
|
|
|
|
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
|
|
|
|
id = ""
|
2020-05-05 00:20:45 +00:00
|
|
|
for i in range(64):
|
2020-05-04 21:37:21 +00:00
|
|
|
id += random.choice(alphabet)
|
|
|
|
solution.file.name = id
|
|
|
|
solution.save()
|
|
|
|
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
return reverse_lazy("tournament:solutions")
|
|
|
|
|
2020-04-30 18:11:03 +00:00
|
|
|
|
2020-05-04 22:11:38 +00:00
|
|
|
class SolutionsOrgaListView(OrgaMixin, SingleTableView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
View all solutions sent by teams for the organized tournaments. Juries can view solutions of their pools.
|
|
|
|
Organizers can download a ZIP archive for each organized tournament.
|
|
|
|
"""
|
|
|
|
|
2020-04-30 18:11:03 +00:00
|
|
|
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:
|
2020-05-06 16:10:33 +00:00
|
|
|
tournament = Tournament.objects.get(pk=int(request.POST["tournament_zip"]))
|
2020-04-30 18:11:03 +00:00
|
|
|
solutions = tournament.solutions
|
2020-05-04 21:37:21 +00:00
|
|
|
if not request.user.admin and request.user not in tournament.organizers.all():
|
2020-04-30 18:11:03 +00:00
|
|
|
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)
|
|
|
|
|
2020-05-25 16:27:07 +00:00
|
|
|
if self.request.user.is_authenticated:
|
|
|
|
context["tournaments"] = \
|
|
|
|
Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments
|
2020-04-30 18:11:03 +00:00
|
|
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
qs = super().get_queryset()
|
2020-05-25 16:27:07 +00:00
|
|
|
if self.request.user.is_authenticated and not self.request.user.admin:
|
2020-05-20 13:47:45 +00:00
|
|
|
if self.request.user in Tournament.get_final().organizers.all():
|
|
|
|
qs = qs.filter(Q(team__tournament__organizers=self.request.user) | Q(pools__juries=self.request.user)
|
|
|
|
| Q(final=True))
|
|
|
|
else:
|
|
|
|
qs = qs.filter(Q(team__tournament__organizers=self.request.user) | Q(pools__juries=self.request.user))
|
2020-05-25 16:27:07 +00:00
|
|
|
elif not self.request.user.is_authenticated:
|
|
|
|
qs = qs.filter(pools__extra_access_token=self.request.session["extra_access_token"])
|
2020-05-06 16:10:33 +00:00
|
|
|
return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
|
2020-05-07 09:58:43 +00:00
|
|
|
'problem',).distinct()
|
2020-05-04 22:11:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SynthesesView(TeamMixin, BaseFormView, SingleTableView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Upload and view syntheses for a team.
|
|
|
|
"""
|
2020-05-04 22:11:38 +00:00
|
|
|
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):
|
2020-05-06 19:10:48 +00:00
|
|
|
qs = super().get_queryset().filter(team=self.request.user.team)
|
2020-05-06 16:10:33 +00:00
|
|
|
return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
|
|
|
|
'round', 'source',)
|
2020-05-04 22:11:38 +00:00
|
|
|
|
2020-05-05 20:29:10 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
self.object_list = self.get_queryset()
|
2020-05-05 21:19:18 +00:00
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context["now"] = timezone.now()
|
2020-05-08 14:25:55 +00:00
|
|
|
context["real_deadline_1"] = self.request.user.team.future_tournament.date_syntheses + timedelta(minutes=30)
|
|
|
|
context["real_deadline_2"] = self.request.user.team.future_tournament.date_syntheses_2 + timedelta(minutes=30)
|
2020-05-05 21:19:18 +00:00
|
|
|
return context
|
2020-05-05 20:29:10 +00:00
|
|
|
|
2020-05-04 22:11:38 +00:00
|
|
|
def form_valid(self, form):
|
|
|
|
synthesis = form.instance
|
|
|
|
synthesis.team = self.request.user.team
|
|
|
|
synthesis.final = synthesis.team.selected_for_final
|
2020-05-05 00:20:45 +00:00
|
|
|
|
2020-05-05 21:06:32 +00:00
|
|
|
if synthesis.round == '1' and timezone.now() > (synthesis.tournament.date_syntheses + timedelta(minutes=30)):
|
2020-05-05 00:20:45 +00:00
|
|
|
form.add_error('file', _("You can't publish your synthesis anymore for the first round."
|
2020-05-05 20:29:10 +00:00
|
|
|
" Deadline: {date:%m-%d-%Y %H:%M}.")
|
2020-05-05 21:06:32 +00:00
|
|
|
.format(date=timezone.localtime(synthesis.tournament.date_syntheses)))
|
2020-05-05 00:20:45 +00:00
|
|
|
return super().form_invalid(form)
|
|
|
|
|
2020-05-05 21:06:32 +00:00
|
|
|
if synthesis.round == '2' and timezone.now() > synthesis.tournament.date_syntheses_2 + timedelta(minutes=30):
|
2020-05-05 00:20:45 +00:00
|
|
|
form.add_error('file', _("You can't publish your synthesis anymore for the second round."
|
2020-05-05 20:29:10 +00:00
|
|
|
" Deadline: {date:%m-%d-%Y %H:%M}.")
|
2020-05-05 21:06:32 +00:00
|
|
|
.format(date=timezone.localtime(synthesis.tournament.date_syntheses_2)))
|
2020-05-05 00:20:45 +00:00
|
|
|
return super().form_invalid(form)
|
|
|
|
|
2020-05-04 22:11:38 +00:00
|
|
|
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 = ""
|
2020-05-05 00:20:45 +00:00
|
|
|
for i in range(64):
|
2020-05-04 22:11:38 +00:00
|
|
|
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):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
View all syntheses sent by teams for the organized tournaments. Juries can view syntheses of their pools.
|
|
|
|
Organizers can download a ZIP archive for each organized tournament.
|
|
|
|
"""
|
2020-05-04 22:11:38 +00:00
|
|
|
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:
|
2020-05-06 16:10:33 +00:00
|
|
|
tournament = Tournament.objects.get(pk=request.POST["tournament_zip"])
|
2020-05-04 22:11:38 +00:00
|
|
|
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)
|
|
|
|
|
2020-05-25 16:27:07 +00:00
|
|
|
if self.request.user.is_authenticated:
|
|
|
|
context["tournaments"] = \
|
|
|
|
Tournament.objects if self.request.user.admin else self.request.user.organized_tournaments
|
2020-05-04 22:11:38 +00:00
|
|
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
qs = super().get_queryset()
|
2020-05-25 16:27:07 +00:00
|
|
|
if self.request.user.is_authenticated and not self.request.user.admin:
|
2020-05-20 13:47:45 +00:00
|
|
|
if self.request.user in Tournament.get_final().organizers.all():
|
2020-05-20 13:50:44 +00:00
|
|
|
qs = qs.filter(Q(team__tournament__organizers=self.request.user)
|
|
|
|
| Q(team__pools__juries=self.request.user)
|
2020-05-20 13:47:45 +00:00
|
|
|
| Q(final=True))
|
|
|
|
else:
|
2020-05-20 13:50:44 +00:00
|
|
|
qs = qs.filter(Q(team__tournament__organizers=self.request.user)
|
|
|
|
| Q(team__pools__juries=self.request.user))
|
2020-05-25 16:27:07 +00:00
|
|
|
elif not self.request.user.is_authenticated:
|
|
|
|
pool = Pool.objects.filter(extra_access_token=self.request.session["extra_access_token"])
|
|
|
|
if pool.exists():
|
|
|
|
pool = pool.get()
|
|
|
|
qs = qs.filter(team__pools=pool, final=pool.tournament.final)
|
|
|
|
else:
|
|
|
|
qs = qs.none()
|
2020-05-06 16:10:33 +00:00
|
|
|
return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram',
|
2020-05-15 18:45:36 +00:00
|
|
|
'round', 'source',).distinct()
|
2020-05-04 22:11:38 +00:00
|
|
|
|
2020-05-05 02:45:38 +00:00
|
|
|
|
2020-05-25 16:27:07 +00:00
|
|
|
class PoolListView(SingleTableView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
View the list of visible pools.
|
|
|
|
Admins see all, juries see their own pools, organizers see the pools of their tournaments.
|
|
|
|
"""
|
2020-05-05 02:45:38 +00:00
|
|
|
model = Pool
|
|
|
|
table_class = PoolTable
|
|
|
|
extra_context = dict(title=_("Pools"))
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
qs = super().get_queryset()
|
|
|
|
user = self.request.user
|
2020-05-25 16:27:07 +00:00
|
|
|
if user.is_authenticated:
|
|
|
|
if not user.admin and user.organizes:
|
|
|
|
qs = qs.filter(Q(juries=user) | Q(teams__tournament__organizers=user))
|
|
|
|
elif user.participates:
|
|
|
|
qs = qs.filter(teams=user.team)
|
|
|
|
else:
|
|
|
|
qs = qs.filter(extra_access_token=self.request.session["extra_access_token"])
|
2020-05-23 17:23:57 +00:00
|
|
|
qs = qs.distinct().order_by('id')
|
2020-05-05 14:04:53 +00:00
|
|
|
return qs
|
2020-05-05 02:45:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
class PoolCreateView(AdminMixin, CreateView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
Create a pool manually.
|
|
|
|
This page should not be used: prefer send automatically data from the drawing bot.
|
|
|
|
"""
|
2020-05-05 02:45:38 +00:00
|
|
|
model = Pool
|
|
|
|
form_class = PoolForm
|
|
|
|
extra_context = dict(title=_("Create pool"))
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
return reverse_lazy("tournament:pools")
|
|
|
|
|
|
|
|
|
2020-05-25 16:27:07 +00:00
|
|
|
class PoolDetailView(DetailView):
|
2020-05-11 12:08:19 +00:00
|
|
|
"""
|
|
|
|
See the detail of a pool.
|
|
|
|
Teams and juries can download here defended solutions of the pool.
|
|
|
|
If this is the second round, teams can't download solutions of the other teams before the date when they
|
|
|
|
should be available.
|
|
|
|
Juries see also syntheses. They see of course solutions immediately.
|
|
|
|
This is also true for organizers and admins.
|
|
|
|
All can be downloaded as a ZIP archive.
|
|
|
|
"""
|
2020-05-05 02:45:38 +00:00
|
|
|
model = Pool
|
|
|
|
extra_context = dict(title=_("Pool detail"))
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
qs = super().get_queryset()
|
|
|
|
user = self.request.user
|
2020-05-25 16:27:07 +00:00
|
|
|
if user.is_authenticated:
|
|
|
|
if not user.admin and user.organizes:
|
|
|
|
qs = qs.filter(Q(juries=user) | Q(teams__tournament__organizers=user))
|
|
|
|
elif user.participates:
|
|
|
|
qs = qs.filter(teams=user.team)
|
|
|
|
else:
|
|
|
|
qs = qs.filter(extra_access_token=self.request.session["extra_access_token"])
|
2020-05-05 02:45:38 +00:00
|
|
|
return qs.distinct()
|
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
user = request.user
|
|
|
|
pool = self.get_object()
|
|
|
|
|
|
|
|
if "solutions_zip" in request.POST:
|
2020-05-25 16:27:07 +00:00
|
|
|
if user.is_authenticated and user.participates and pool.round == 2\
|
|
|
|
and pool.tournament.date_solutions_2 > timezone.now():
|
2020-05-05 16:29:02 +00:00
|
|
|
raise PermissionDenied
|
|
|
|
|
2020-05-05 02:45:38 +00:00
|
|
|
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={}' \
|
2020-05-08 17:44:46 +00:00
|
|
|
.format(_("Solutions of a pool for the round {round} of the tournament {tournament}.zip")
|
|
|
|
.format(round=pool.round, tournament=str(pool.tournament)).replace(" ", "%20"))
|
2020-05-05 02:45:38 +00:00
|
|
|
return resp
|
2020-05-25 16:27:07 +00:00
|
|
|
elif "syntheses_zip" in request.POST and (not user.is_authenticated or user.organizes):
|
2020-05-05 02:45:38 +00:00
|
|
|
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={}' \
|
2020-05-08 17:06:04 +00:00
|
|
|
.format(_("Syntheses of a pool for the round {round} of the tournament {tournament}.zip")
|
|
|
|
.format(round=pool.round, tournament=str(pool.tournament)).replace(" ", "%20"))
|
2020-05-05 02:45:38 +00:00
|
|
|
return resp
|
|
|
|
|
2020-05-05 03:57:57 +00:00
|
|
|
return self.get(request, *args, **kwargs)
|