nk20/apps/registration/views.py

371 lines
13 KiB
Python

# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.shortcuts import resolve_url, redirect
from django.urls import reverse_lazy
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import CreateView, TemplateView, DetailView
from django.views.generic.edit import FormMixin
from django_tables2 import SingleTableView
from member.forms import ProfileForm
from member.models import Membership, Club
from note.models import SpecialTransaction
from note.templatetags.pretty_money import pretty_money
from permission.backends import PermissionBackend
from permission.models import Role
from permission.views import ProtectQuerysetMixin
from .forms import SignUpForm, ValidationForm
from .tables import FutureUserTable
from .tokens import email_validation_token
class UserCreateView(CreateView):
"""
Une vue pour inscrire un utilisateur et lui créer un profil
"""
form_class = SignUpForm
template_name = 'registration/signup.html'
second_form = ProfileForm
extra_context = {"title": _("Register new user")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["profile_form"] = self.second_form(self.request.POST)
del context["profile_form"].fields["section"]
return context
def form_valid(self, form):
"""
If the form is valid, then the user is created with is_active set to False
so that the user cannot log in until the email has been validated.
The user must also wait that someone validate her/his account.
"""
profile_form = ProfileForm(data=self.request.POST)
if not profile_form.is_valid():
return self.form_invalid(form)
# Save the user and the profile
user = form.save(commit=False)
user.is_active = False
profile_form.instance.user = user
profile = profile_form.save(commit=False)
user.profile = profile
user.save()
user.refresh_from_db()
profile.user = user
profile.save()
user.profile.send_email_validation_link()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('registration: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):
# The user must wait that someone validates the account before the user can be active and login.
self.validlink = True
user.is_active = user.profile.registration_valid or user.is_superuser
user.profile.email_confirmed = True
user.save()
user.profile.save()
return self.render_to_response(self.get_context_data())
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 = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.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, ProtectQuerysetMixin, DetailView):
"""
Rensend the email validation link.
"""
model = User
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 'registration:future_user_detail'
return redirect(url, user.id)
class FutureUserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
Display pre-registered users, with a search bar
"""
model = User
table_class = FutureUserTable
template_name = 'registration/future_user_list.html'
extra_context = {"title": _("Pre-registered users list")}
def get_queryset(self, **kwargs):
"""
Filter the table with the given parameter.
:param kwargs:
:return:
"""
qs = super().get_queryset().distinct().filter(profile__registration_valid=False)
if "search" in self.request.GET:
pattern = self.request.GET["search"]
if not pattern:
return qs.none()
qs = qs.filter(
Q(first_name__iregex=pattern)
| Q(last_name__iregex=pattern)
| Q(profile__section__iregex=pattern)
| Q(username__iregex="^" + pattern)
)
return qs[:20]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = _("Unregistered users")
return context
class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView):
"""
Display information about a pre-registered user, in order to complete the registration.
"""
model = User
form_class = ValidationForm
context_object_name = "user_object"
template_name = "registration/future_profile_detail.html"
extra_context = {"title": _("Registration detail")}
def post(self, request, *args, **kwargs):
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_queryset(self, **kwargs):
"""
We only display information of a not registered user.
"""
return super().get_queryset().filter(profile__registration_valid=False)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
user = self.get_object()
fee = 0
bde = Club.objects.get(name="BDE")
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
kfet = Club.objects.get(name="Kfet")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
return ctx
def get_form(self, form_class=None):
form = super().get_form(form_class)
user = self.get_object()
form.fields["last_name"].initial = user.last_name
form.fields["first_name"].initial = user.first_name
return form
def form_valid(self, form):
user = self.get_object()
# Get form data
soge = form.cleaned_data["soge"]
credit_type = form.cleaned_data["credit_type"]
credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
join_BDE = form.cleaned_data["join_BDE"]
join_Kfet = form.cleaned_data["join_Kfet"]
if soge:
# If Société Générale pays the inscription, the user joins the two clubs
join_BDE = True
join_Kfet = True
if not join_BDE:
form.add_error('join_BDE', _("You must join the BDE."))
return super().form_invalid(form)
fee = 0
bde = Club.objects.get(name="BDE")
bde_fee = bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
if join_BDE:
fee += bde_fee
kfet = Club.objects.get(name="Kfet")
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
if join_Kfet:
fee += kfet_fee
if soge:
# If the bank pays, then we don't credit now. Treasurers will validate the transaction
# and credit the note later.
credit_type = None
if credit_type is None:
credit_amount = 0
if join_Kfet and not join_BDE:
form.add_error('join_Kfet', _("You must join BDE club before joining Kfet club."))
if fee > credit_amount and not soge:
# Check if the user credits enough money
form.add_error('credit_type',
_("The entered amount is not enough for the memberships, should be at least {}")
.format(pretty_money(fee)))
return self.form_invalid(form)
if credit_type is not None and credit_amount > 0:
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
if not last_name:
form.add_error('last_name', _("This field is required."))
if not first_name:
form.add_error('first_name', _("This field is required."))
if not bank and credit_type.special_type == "Chèque":
form.add_error('bank', _("This field is required."))
return self.form_invalid(form)
# Save the user and finally validate the registration
# Saving the user creates the associated note
ret = super().form_valid(form)
user.is_active = user.profile.email_confirmed or user.is_superuser
user.profile.registration_valid = True
user.save()
user.profile.save()
user.refresh_from_db()
if credit_type is not None and credit_amount > 0:
# Credit the note
SpecialTransaction.objects.create(
source=credit_type,
destination=user.note,
quantity=1,
amount=credit_amount,
reason="Crédit " + ("Société générale" if soge else credit_type.special_type) + " (Inscription)",
last_name=last_name,
first_name=first_name,
bank=bank,
valid=True,
)
if join_BDE:
# Create membership for the user to the BDE starting today
membership = Membership(
club=bde,
user=user,
fee=bde_fee,
)
if soge:
membership._soge = True
membership.save()
membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
membership.save()
if join_Kfet:
# Create membership for the user to the Kfet starting today
membership = Membership(
club=kfet,
user=user,
fee=kfet_fee,
)
if soge:
membership._soge = True
membership.save()
membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
membership.save()
return ret
def get_success_url(self):
return reverse_lazy('member:user_detail', args=(self.get_object().pk, ))
class FutureUserInvalidateView(ProtectQuerysetMixin, LoginRequiredMixin, View):
"""
Delete a pre-registered user.
"""
extra_context = {"title": _("Invalidate pre-registration")}
def get(self, request, *args, **kwargs):
"""
Delete the pre-registered user which id is given in the URL.
"""
user = User.objects.filter(profile__registration_valid=False)\
.filter(PermissionBackend.filter_queryset(request.user, User, "change", "is_valid"))\
.get(pk=self.kwargs["pk"])
user.delete()
return redirect('registration:future_user_list')