plateforme-tfjm2/apps/member/views.py

375 lines
13 KiB
Python

import random
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Q
from django.http import FileResponse, Http404
from django.shortcuts import redirect, resolve_url
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import CreateView, UpdateView, DetailView, FormView, TemplateView
from django_tables2 import SingleTableView
from tournament.forms import TeamForm, JoinTeam
from tournament.models import Team, Tournament, Pool
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
from .tokens import email_validation_token
class CreateUserView(CreateView):
"""
Signup form view.
"""
model = TFJMUser
form_class = SignUpForm
template_name = "registration/signup.html"
# When errors are reported from the signup view, don't send passwords to admins
@method_decorator(sensitive_post_parameters('password1', 'password2',))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.send_email_validation_link()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('member:email_validation_sent')
class UserValidateView(TemplateView):
"""
A view to validate the email address.
"""
title = _("Email validation")
template_name = 'registration/email_validation_complete.html'
extra_context = {"title": _("Validate email")}
def get(self, *args, **kwargs):
"""
With a given token and user id (in params), validate the email address.
"""
assert 'uidb64' in kwargs and 'token' in kwargs
self.validlink = False
user = self.get_user(kwargs['uidb64'])
token = kwargs['token']
# Validate the token
if user is not None and email_validation_token.check_token(user, token):
self.validlink = True
user.email_confirmed = True
user.save()
return self.render_to_response(self.get_context_data(), status=200 if self.validlink else 400)
def get_user(self, uidb64):
"""
Get user from the base64-encoded string.
"""
try:
# urlsafe_base64_decode() decodes to bytestring
uid = urlsafe_base64_decode(uidb64).decode()
user = TFJMUser.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, TFJMUser.DoesNotExist, ValidationError):
user = None
return user
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_object'] = self.get_user(self.kwargs["uidb64"])
context['login_url'] = resolve_url(settings.LOGIN_URL)
if self.validlink:
context['validlink'] = True
else:
context.update({
'title': _('Email validation unsuccessful'),
'validlink': False,
})
return context
class UserValidationEmailSentView(TemplateView):
"""
Display the information that the validation link has been sent.
"""
template_name = 'registration/email_validation_email_sent.html'
extra_context = {"title": _('Email validation email sent')}
class UserResendValidationEmailView(LoginRequiredMixin, DetailView):
"""
Rensend the email validation link.
"""
model = TFJMUser
extra_context = {"title": _("Resend email validation link")}
def get(self, request, *args, **kwargs):
user = self.get_object()
user.profile.send_email_validation_link()
url = 'member:user_detail' if user.profile.registration_valid else 'member:future_user_detail'
return redirect(url, user.id)
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(AccessMixin, 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})
if request.user.is_authenticated:
grant = request.user.admin
if isinstance(doc, Solution) or isinstance(doc, Synthesis):
grant = grant or doc.team == request.user.team or request.user in doc.tournament.organizers.all()
elif 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, 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
elif isinstance(doc, Synthesis):
for pool in request.user.pools.all(): # If the user is a jury in the pool
if doc.team in pool.teams.all() and doc.final == pool.tournament.final:
grant = True
break
else:
pool = Pool.objects.filter(extra_access_token=self.request.session["extra_access_token"])
if pool.exists():
pool = pool.get()
if isinstance(doc, Solution):
grant = doc in pool.solutions.all()
elif isinstance(doc, Synthesis):
grant = doc.team in pool.teams.all() and doc.final == pool.tournament.final
else:
grant = False
else:
grant = False
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"])