import random from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied from django.db.models import Q from django.http import FileResponse, Http404 from django.shortcuts import redirect from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views import View from django.views.generic import CreateView, UpdateView, DetailView, FormView from django_tables2 import SingleTableView from tournament.forms import TeamForm, JoinTeam from tournament.models import Team, Tournament from tournament.views import AdminMixin, TeamMixin, OrgaMixin from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis from .tables import UserTable class CreateUserView(CreateView): """ Signup form view. """ model = TFJMUser form_class = SignUpForm template_name = "registration/signup.html" class MyAccountView(LoginRequiredMixin, UpdateView): """ Update our personal data. """ model = TFJMUser template_name = "member/my_account.html" def get_form_class(self): # The used form can change according to the role of the user. return AdminUserForm if self.request.user.organizes else TFJMUserForm \ if self.request.user.role == "3participant" else CoachUserForm def get_object(self, queryset=None): return self.request.user def get_success_url(self): return reverse_lazy('member:my_account') class UserDetailView(LoginRequiredMixin, DetailView): """ View the personal information of a given user. Only organizers can see this page, since there are personal data. """ model = TFJMUser form_class = TFJMUserForm context_object_name = "tfjmuser" def dispatch(self, request, *args, **kwargs): if isinstance(request.user, AnonymousUser): raise PermissionDenied self.object = self.get_object() if not request.user.admin \ and (self.object.team is not None and request.user not in self.object.team.tournament.organizers.all())\ and (self.object.team is not None and self.object.team.selected_for_final and request.user not in Tournament.get_final().organizers.all())\ and self.request.user != self.object: raise PermissionDenied return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): """ An administrator can log in through this page as someone else, and act as this other person. """ if "view_as" in request.POST and self.request.user.admin: session = request.session session["admin"] = request.user.pk obj = self.get_object() session["_fake_user_id"] = obj.pk return redirect(request.path) return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = str(self.object) return context class AddTeamView(LoginRequiredMixin, CreateView): """ Register a new team. Users can choose the name, the trigram and a preferred tournament. """ model = Team form_class = TeamForm def form_valid(self, form): if self.request.user.organizes: form.add_error('name', _("You can't organize and participate at the same time.")) return self.form_invalid(form) if self.request.user.team: form.add_error('name', _("You are already in a team.")) return self.form_invalid(form) # Generate a random access code team = form.instance alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" code = "" for i in range(6): code += random.choice(alphabet) team.access_code = code team.validation_status = "0invalid" team.save() team.refresh_from_db() self.request.user.team = team self.request.user.save() return super().form_valid(form) def get_success_url(self): return reverse_lazy("member:my_team") class JoinTeamView(LoginRequiredMixin, FormView): """ Join a team with a given access code. """ model = Team form_class = JoinTeam template_name = "tournament/team_form.html" def form_valid(self, form): team = form.cleaned_data["team"] if self.request.user.organizes: form.add_error('access_code', _("You can't organize and participate at the same time.")) return self.form_invalid(form) if self.request.user.team: form.add_error('access_code', _("You are already in a team.")) return self.form_invalid(form) if self.request.user.role == '2coach' and len(team.coaches) == 3: form.add_error('access_code', _("This team is full of coachs.")) return self.form_invalid(form) if self.request.user.role == '3participant' and len(team.participants) == 6: form.add_error('access_code', _("This team is full of participants.")) return self.form_invalid(form) if not team.invalid: form.add_error('access_code', _("This team is already validated or waiting for validation.")) self.request.user.team = team self.request.user.save() return super().form_valid(form) def get_success_url(self): return reverse_lazy("member:my_team") class MyTeamView(TeamMixin, View): """ Redirect to the page of the information of our personal team. """ def get(self, request, *args, **kwargs): return redirect("tournament:team_detail", pk=request.user.team.pk) class DocumentView(LoginRequiredMixin, View): """ View a PDF document, if we have the right. - Everyone can see the documents that concern itself. - An administrator can see anything. - An organizer can see documents that are related to its tournament. - A jury can see solutions and syntheses that are evaluated in their pools. """ def get(self, request, *args, **kwargs): try: doc = Document.objects.get(file=self.kwargs["file"]) except Document.DoesNotExist: raise Http404(_("No %(verbose_name)s found matching the query") % {'verbose_name': Document._meta.verbose_name}) grant = request.user.admin 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.selected_for_final and request.user in Tournament.get_final().organizers.all()) if isinstance(doc, Synthesis) and request.user.organizes: grant = True if isinstance(doc, Solution): for pool in doc.pools.all(): if request.user in pool.juries.all(): grant = True break if pool.round == 2 and timezone.now() < doc.tournament.date_solutions_2: continue if self.request.user.team in pool.teams.all(): grant = True if not grant: raise PermissionDenied return FileResponse(doc.file, content_type="application/pdf", filename=str(doc) + ".pdf") class ProfileListView(AdminMixin, SingleTableView): """ List all registered profiles. """ model = TFJMUser queryset = TFJMUser.objects.order_by("role", "last_name", "first_name") table_class = UserTable template_name = "member/profile_list.html" extra_context = dict(title=_("All profiles"), type="all") class OrphanedProfileListView(AdminMixin, SingleTableView): """ List all orphaned profiles, ie. participants that have no team. """ model = TFJMUser queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\ .order_by("role", "last_name", "first_name") table_class = UserTable template_name = "member/profile_list.html" extra_context = dict(title=_("Orphaned profiles"), type="orphaned") class OrganizersListView(OrgaMixin, SingleTableView): """ List all organizers. """ model = TFJMUser queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\ .order_by("role", "last_name", "first_name") table_class = UserTable template_name = "member/profile_list.html" extra_context = dict(title=_("Organizers"), type="organizers") class ResetAdminView(AdminMixin, View): """ Return to admin view, clear the session field that let an administrator to log in as someone else. """ def dispatch(self, request, *args, **kwargs): if "_fake_user_id" in request.session: del request.session["_fake_user_id"] return redirect(request.GET["path"])