import random import zipfile from datetime import timedelta from io import BytesIO from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied from django.core.mail import send_mail from django.db.models import Q from django.http import HttpResponse from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views.generic import DetailView, CreateView, UpdateView from django.views.generic.edit import BaseFormView from django_tables2.views import SingleTableView from member.models import TFJMUser, Solution, Synthesis from .forms import TournamentForm, OrganizerForm, SolutionForm, SynthesisForm, TeamForm, PoolForm from .models import Tournament, Team, Pool from .tables import TournamentTable, TeamTable, SolutionTable, SynthesisTable, PoolTable class AdminMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not request.user.admin: raise PermissionDenied return super().dispatch(request, *args, **kwargs) class OrgaMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or 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.is_authenticated or not request.user.team: raise PermissionDenied return super().dispatch(request, *args, **kwargs) class TournamentListView(SingleTableView): model = Tournament table_class = TournamentTable extra_context = dict(title=_("Tournaments list"),) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) 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")) 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 class TournamentCreateView(AdminMixin, CreateView): 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,)) class TournamentDetailView(DetailView): model = Tournament def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = _("Tournament of {name}").format(name=self.object.name) 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)) 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 class TournamentUpdateView(AdminMixin, UpdateView): 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,)) class TeamDetailView(LoginRequiredMixin, DetailView): model = Team def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or \ (not request.user.admin and self.request.user not in self.get_object().tournament.organizers.all() and self.get_object() != request.user.team): raise PermissionDenied return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): print(request.POST) 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 elif "leave" in request.POST and request.user.participates: request.user.team = None request.user.save() if not team.users.exists(): team.delete() return redirect('tournament:detail', pk=team.tournament.pk) elif "request_validation" in request.POST and request.user.participates: team.validation_status = "1waiting" team.save() team.tournament.send_mail_to_organizers("request_validation", "Demande de validation TFJM²", team=team) return redirect('tournament:team_detail', pk=team.pk) elif "validate" in request.POST and request.user.organizes: team.validation_status = "2valid" team.save() team.send_mail("validate_team", "Équipe validée TFJM²") return redirect('tournament:team_detail', pk=team.pk) elif "invalidate" in request.POST and request.user.organizes: team.validation_status = "0invalid" team.save() team.send_mail("unvalidate_team", "Équipe non validée TFJM²") return redirect('tournament:team_detail', pk=team.pk) elif "delete" in request.POST and request.user.organizes: team.delete() return redirect('tournament:detail', pk=team.tournament.pk) elif "select_final" in request.POST and request.user.admin and not team.selected_for_final and team.pools: 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() return redirect('tournament:team_detail', pk=team.pk) return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = _("Information about team") context["ordered_solutions"] = self.object.solutions.order_by('problem').all() context["team_users_emails"] = [user.email for user in self.object.users.all()] return context class TeamUpdateView(LoginRequiredMixin, UpdateView): model = Team form_class = TeamForm extra_context = dict(title=_("Update team"),) def dispatch(self, request, *args, **kwargs): 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: raise PermissionDenied return super().dispatch(request, *args, **kwargs) class AddOrganizerView(AdminMixin, CreateView): model = TFJMUser form_class = OrganizerForm extra_context = dict(title=_("Add organizer"),) template_name = "tournament/add_organizer.html" def form_valid(self, form): user = form.instance 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) return super().form_valid(form) def get_success_url(self): return reverse_lazy('index') class SolutionsView(TeamMixin, BaseFormView, SingleTableView): model = Solution table_class = SolutionTable form_class = SolutionForm 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 super().post(request, *args, **kwargs) def get_context_data(self, **kwargs): self.object_list = self.get_queryset() context = super().get_context_data(**kwargs) context["now"] = timezone.now() context["real_deadline"] = self.request.user.team.tournament.date_solutions + timedelta(minutes=30) return context def get_queryset(self): qs = super().get_queryset().filter(team=self.request.user.team) return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',) def form_valid(self, form): solution = form.instance solution.team = self.request.user.team solution.final = solution.team.selected_for_final if timezone.now() > solution.tournament.date_solutions + timedelta(minutes=30): form.add_error('file', _("You can't publish your solution anymore. Deadline: {date:%m-%d-%Y %H:%M}.") .format(date=timezone.localtime(solution.tournament.date_solutions))) return super().form_invalid(form) 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 = "" for i in range(64): 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") class SolutionsOrgaListView(OrgaMixin, 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=int(request.POST["tournament_zip"])) solutions = tournament.solutions if not request.user.admin and request.user not in tournament.organizers.all(): 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(Q(team__tournament__organizers=self.request.user) | Q(pools__juries=self.request.user)) return qs.order_by('final', '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().filter(team=self.request.user.team) return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round', 'source',) def get_context_data(self, **kwargs): self.object_list = self.get_queryset() context = super().get_context_data(**kwargs) context["now"] = timezone.now() return context def form_valid(self, form): synthesis = form.instance synthesis.team = self.request.user.team synthesis.final = synthesis.team.selected_for_final if synthesis.round == '1' and timezone.now() > (synthesis.tournament.date_syntheses + timedelta(minutes=30)): form.add_error('file', _("You can't publish your synthesis anymore for the first round." " Deadline: {date:%m-%d-%Y %H:%M}.") .format(date=timezone.localtime(synthesis.tournament.date_syntheses))) return super().form_invalid(form) if synthesis.round == '2' and timezone.now() > synthesis.tournament.date_syntheses_2 + timedelta(minutes=30): form.add_error('file', _("You can't publish your synthesis anymore for the second round." " Deadline: {date:%m-%d-%Y %H:%M}.") .format(date=timezone.localtime(synthesis.tournament.date_syntheses_2))) return super().form_invalid(form) 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 i 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"]) 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(Q(team__tournament__organizers=self.request.user) | Q(team__pools__juries=self.request.user)) return qs.order_by('final', 'team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'round', '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(juries=user) | Q(teams__tournament__organizers=user)) elif user.participates: qs = qs.filter(teams=user.team) qs = qs.distinct().order_by('solutions__final', 'teams__tournament__date_start', 'teams__tournament__name', 'round',) return qs 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(juries=user) | Q(teams__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: if user.participates and pool.round == 2 and pool.tournament.date_solutions_2 > timezone.now(): raise PermissionDenied 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 and user.organizes: if user.participates and pool.round == 2 and pool.tournament.date_solutions_2 > timezone.now(): raise PermissionDenied 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)