plateforme-tfjm2/apps/member/views.py

375 lines
13 KiB
Python
Raw Normal View History

2020-05-04 22:56:34 +00:00
import random
2020-09-20 19:24:52 +00:00
from django.conf import settings
2020-05-25 16:27:07 +00:00
from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
2020-05-04 19:02:57 +00:00
from django.contrib.auth.models import AnonymousUser
2020-09-20 19:24:52 +00:00
from django.core.exceptions import PermissionDenied, ValidationError
2020-04-30 19:07:12 +00:00
from django.db.models import Q
from django.http import FileResponse, Http404
2020-09-20 19:24:52 +00:00
from django.shortcuts import redirect, resolve_url
2020-05-04 22:56:34 +00:00
from django.urls import reverse_lazy
2020-05-05 11:54:26 +00:00
from django.utils import timezone
from django.utils.decorators import method_decorator
2020-09-20 19:24:52 +00:00
from django.utils.http import urlsafe_base64_decode
2020-04-30 19:07:12 +00:00
from django.utils.translation import gettext_lazy as _
2020-04-29 23:20:50 +00:00
from django.views import View
from django.views.decorators.debug import sensitive_post_parameters
2020-09-20 19:24:52 +00:00
from django.views.generic import CreateView, UpdateView, DetailView, FormView, TemplateView
2020-04-30 19:07:12 +00:00
from django_tables2 import SingleTableView
2020-05-04 22:56:34 +00:00
from tournament.forms import TeamForm, JoinTeam
2020-05-25 16:27:07 +00:00
from tournament.models import Team, Tournament, Pool
from tournament.views import AdminMixin, TeamMixin, OrgaMixin
2020-05-11 12:08:19 +00:00
2020-05-04 23:04:07 +00:00
from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm
2020-05-04 21:37:21 +00:00
from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis
2020-04-30 19:07:12 +00:00
from .tables import UserTable
2020-09-20 19:24:52 +00:00
from .tokens import email_validation_token
2020-04-29 13:29:01 +00:00
class CreateUserView(CreateView):
2020-05-11 12:08:19 +00:00
"""
Signup form view.
"""
2020-04-29 13:29:01 +00:00
model = TFJMUser
form_class = SignUpForm
template_name = "registration/signup.html"
2020-04-29 23:20:50 +00:00
# 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)
2020-09-20 19:24:52 +00:00
def form_valid(self, form):
form.instance.send_email_validation_link()
return super().form_valid(form)
def get_success_url(self):
2020-09-20 19:24:52 +00:00
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)
2020-04-29 23:20:50 +00:00
2020-05-04 18:21:53 +00:00
class MyAccountView(LoginRequiredMixin, UpdateView):
2020-05-11 12:08:19 +00:00
"""
Update our personal data.
"""
2020-05-04 18:21:53 +00:00
model = TFJMUser
template_name = "member/my_account.html"
2020-05-04 23:04:07 +00:00
def get_form_class(self):
2020-05-11 12:08:19 +00:00
# The used form can change according to the role of the user.
2020-05-04 23:04:07 +00:00
return AdminUserForm if self.request.user.organizes else TFJMUserForm \
if self.request.user.role == "3participant" else CoachUserForm
2020-05-04 18:21:53 +00:00
def get_object(self, queryset=None):
return self.request.user
2020-05-15 00:47:22 +00:00
def get_success_url(self):
return reverse_lazy('member:my_account')
2020-05-04 18:21:53 +00:00
class UserDetailView(LoginRequiredMixin, DetailView):
2020-05-11 12:08:19 +00:00
"""
View the personal information of a given user.
Only organizers can see this page, since there are personal data.
"""
2020-05-04 18:21:53 +00:00
model = TFJMUser
form_class = TFJMUserForm
2020-05-04 19:02:57 +00:00
context_object_name = "tfjmuser"
2020-05-04 18:21:53 +00:00
def dispatch(self, request, *args, **kwargs):
2020-05-04 19:02:57 +00:00
if isinstance(request.user, AnonymousUser):
raise PermissionDenied
self.object = self.get_object()
2020-05-04 18:21:53 +00:00
if not request.user.admin \
2020-05-04 19:02:57 +00:00
and (self.object.team is not None and request.user not in self.object.team.tournament.organizers.all())\
2020-05-18 22:09:07 +00:00
and (self.object.team is not None and self.object.team.selected_for_final
and request.user not in Tournament.get_final().organizers.all())\
2020-05-04 18:21:53 +00:00
and self.request.user != self.object:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
2020-05-04 19:02:57 +00:00
def post(self, request, *args, **kwargs):
2020-05-11 12:08:19 +00:00
"""
An administrator can log in through this page as someone else, and act as this other person.
"""
2020-05-11 12:21:55 +00:00
if "view_as" in request.POST and self.request.user.admin:
2020-05-04 19:02:57 +00:00
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)
2020-05-04 18:21:53 +00:00
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = str(self.object)
return context
2020-05-04 22:56:34 +00:00
class AddTeamView(LoginRequiredMixin, CreateView):
2020-05-11 12:08:19 +00:00
"""
Register a new team.
Users can choose the name, the trigram and a preferred tournament.
"""
2020-05-04 20:27:45 +00:00
model = Team
2020-05-04 22:56:34 +00:00
form_class = TeamForm
2020-05-04 20:27:45 +00:00
2020-05-04 22:56:34 +00:00
def form_valid(self, form):
2020-05-05 00:20:45 +00:00
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)
2020-05-11 12:08:19 +00:00
# Generate a random access code
2020-05-04 22:56:34 +00:00
team = form.instance
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
code = ""
2020-05-05 00:20:45 +00:00
for i in range(6):
2020-05-04 22:56:34 +00:00
code += random.choice(alphabet)
team.access_code = code
team.validation_status = "0invalid"
2020-05-04 20:27:45 +00:00
2020-05-04 22:56:34 +00:00
team.save()
team.refresh_from_db()
2020-05-04 20:27:45 +00:00
2020-05-04 22:56:34 +00:00
self.request.user.team = team
self.request.user.save()
2020-05-04 20:27:45 +00:00
2020-05-04 22:56:34 +00:00
return super().form_valid(form)
2020-05-04 20:27:45 +00:00
2020-05-04 22:56:34 +00:00
def get_success_url(self):
return reverse_lazy("member:my_team")
2020-05-04 20:27:45 +00:00
2020-05-04 22:56:34 +00:00
class JoinTeamView(LoginRequiredMixin, FormView):
2020-05-11 12:08:19 +00:00
"""
Join a team with a given access code.
"""
2020-05-04 22:56:34 +00:00
model = Team
form_class = JoinTeam
template_name = "tournament/team_form.html"
def form_valid(self, form):
team = form.cleaned_data["team"]
2020-05-05 00:20:45 +00:00
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)
2020-05-11 12:08:19 +00:00
if self.request.user.role == '2coach' and len(team.coaches) == 3:
2020-05-05 00:20:45 +00:00
form.add_error('access_code', _("This team is full of coachs."))
return self.form_invalid(form)
2020-05-05 14:17:46 +00:00
if self.request.user.role == '3participant' and len(team.participants) == 6:
2020-05-05 00:20:45 +00:00
form.add_error('access_code', _("This team is full of participants."))
return self.form_invalid(form)
2020-05-11 12:08:19 +00:00
if not team.invalid:
form.add_error('access_code', _("This team is already validated or waiting for validation."))
2020-05-04 22:56:34 +00:00
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):
2020-05-11 12:08:19 +00:00
"""
Redirect to the page of the information of our personal team.
"""
2020-05-04 22:56:34 +00:00
def get(self, request, *args, **kwargs):
return redirect("tournament:team_detail", pk=request.user.team.pk)
2020-05-04 20:27:45 +00:00
2020-05-25 16:27:07 +00:00
class DocumentView(AccessMixin, View):
2020-05-11 12:08:19 +00:00
"""
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.
"""
2020-04-29 23:20:50 +00:00
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})
2020-04-29 23:20:50 +00:00
2020-05-25 16:27:07 +00:00
if request.user.is_authenticated:
grant = request.user.admin
2020-05-25 17:24:19 +00:00
if isinstance(doc, Solution) or isinstance(doc, Synthesis):
2020-05-25 16:27:07 +00:00
grant = grant or doc.team == request.user.team or request.user in doc.tournament.organizers.all()
2020-05-25 17:24:19 +00:00
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()
2020-05-25 16:27:07 +00:00
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
2020-05-05 02:45:38 +00:00
2020-05-04 21:37:21 +00:00
if not grant:
2020-04-29 23:20:50 +00:00
raise PermissionDenied
2020-05-06 21:43:14 +00:00
return FileResponse(doc.file, content_type="application/pdf", filename=str(doc) + ".pdf")
2020-04-30 19:07:12 +00:00
2020-05-04 18:21:53 +00:00
class ProfileListView(AdminMixin, SingleTableView):
2020-05-11 12:08:19 +00:00
"""
List all registered profiles.
"""
2020-04-30 19:07:12 +00:00
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")
2020-04-30 19:07:12 +00:00
2020-05-04 18:21:53 +00:00
class OrphanedProfileListView(AdminMixin, SingleTableView):
2020-05-11 12:08:19 +00:00
"""
List all orphaned profiles, ie. participants that have no team.
"""
2020-04-30 19:07:12 +00:00
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")
2020-04-30 19:07:12 +00:00
class OrganizersListView(OrgaMixin, SingleTableView):
2020-05-11 12:08:19 +00:00
"""
List all organizers.
"""
2020-04-30 19:07:12 +00:00
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")
2020-05-04 19:02:57 +00:00
class ResetAdminView(AdminMixin, View):
2020-05-11 12:08:19 +00:00
"""
Return to admin view, clear the session field that let an administrator to log in as someone else.
"""
2020-05-04 19:02:57 +00:00
def dispatch(self, request, *args, **kwargs):
if "_fake_user_id" in request.session:
del request.session["_fake_user_id"]
return redirect(request.GET["path"])