mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 09:12:11 +01:00 
			
		
		
		
	Send an e-mail verification to a new registered user
This commit is contained in:
		@@ -4,6 +4,7 @@
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
 | 
			
		||||
from permission.models import PermissionMask
 | 
			
		||||
 | 
			
		||||
@@ -23,10 +24,14 @@ class SignUpForm(UserCreationForm):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.fields['username'].widget.attrs.pop("autofocus", None)
 | 
			
		||||
        self.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"})
 | 
			
		||||
        self.fields['first_name'].required = True
 | 
			
		||||
        self.fields['last_name'].required = True
 | 
			
		||||
        self.fields['email'].required = True
 | 
			
		||||
        self.fields['email'].help_text = _("This address must be valid.")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ['first_name', 'last_name', 'username', 'email']
 | 
			
		||||
        fields = ('first_name', 'last_name', 'username', 'email', )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProfileForm(forms.ModelForm):
 | 
			
		||||
@@ -37,7 +42,7 @@ class ProfileForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Profile
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        exclude = ['user']
 | 
			
		||||
        exclude = ('user', 'email_confirmed', )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClubForm(forms.ModelForm):
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,12 @@ class Profile(models.Model):
 | 
			
		||||
    )
 | 
			
		||||
    paid = models.BooleanField(
 | 
			
		||||
        verbose_name=_("paid"),
 | 
			
		||||
        help_text=_("Tells if the user receive a salary."),
 | 
			
		||||
        default=False,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    email_confirmed = models.BooleanField(
 | 
			
		||||
        verbose_name=_("email confirmed"),
 | 
			
		||||
        default=False,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								apps/member/tokens.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								apps/member/tokens.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
# Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
 | 
			
		||||
    """
 | 
			
		||||
    Create a unique token generator to confirm email addresses.
 | 
			
		||||
    """
 | 
			
		||||
    def _make_hash_value(self, user, timestamp):
 | 
			
		||||
        """
 | 
			
		||||
        Hash the user's primary key and some user state that's sure to change
 | 
			
		||||
        after an account validation to produce a token that invalidated when
 | 
			
		||||
        it's used:
 | 
			
		||||
        1. The user.profile.email_confirmed field will change upon an account
 | 
			
		||||
        validation.
 | 
			
		||||
        2. The last_login field will usually be updated very shortly after
 | 
			
		||||
           an account validation.
 | 
			
		||||
        Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually
 | 
			
		||||
        invalidates the token.
 | 
			
		||||
        """
 | 
			
		||||
        # Truncate microseconds so that tokens are consistent even if the
 | 
			
		||||
        # database doesn't support microseconds.
 | 
			
		||||
        login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
 | 
			
		||||
        return str(user.pk) + str(user.profile.email_confirmed) + str(login_timestamp) + str(timestamp)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
account_activation_token = AccountActivationTokenGenerator()
 | 
			
		||||
@@ -8,6 +8,9 @@ from . import views
 | 
			
		||||
app_name = 'member'
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('signup/', views.UserCreateView.as_view(), name="signup"),
 | 
			
		||||
    path('accounts/activate/sent', views.UserActivationEmailSentView.as_view(), name='account_activation_sent'),
 | 
			
		||||
    path('accounts/activate/<uidb64>/<token>', views.UserActivateView.as_view(), name='account_activation'),
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    path('club/', views.ClubListView.as_view(), name="club_list"),
 | 
			
		||||
    path('club/create/', views.ClubCreateView.as_view(), name="club_create"),
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,18 @@ from django.conf import settings
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.contrib.auth.views import LoginView
 | 
			
		||||
from django.contrib.sites.shortcuts import get_current_site
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.forms import HiddenInput
 | 
			
		||||
from django.shortcuts import redirect
 | 
			
		||||
from django.shortcuts import redirect, resolve_url
 | 
			
		||||
from django.template import loader
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.decorators import method_decorator
 | 
			
		||||
from django.utils.encoding import force_bytes
 | 
			
		||||
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.views.decorators.csrf import csrf_protect
 | 
			
		||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
 | 
			
		||||
from django.views.generic.base import View
 | 
			
		||||
from django.views.generic.edit import FormMixin
 | 
			
		||||
@@ -30,6 +36,7 @@ from permission.views import ProtectQuerysetMixin
 | 
			
		||||
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm
 | 
			
		||||
from .models import Club, Membership
 | 
			
		||||
from .tables import ClubTable, UserTable, MembershipTable
 | 
			
		||||
from .tokens import account_activation_token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomLoginView(LoginView):
 | 
			
		||||
@@ -57,15 +64,90 @@ class UserCreateView(CreateView):
 | 
			
		||||
        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.
 | 
			
		||||
        """
 | 
			
		||||
        profile_form = ProfileForm(self.request.POST)
 | 
			
		||||
        if form.is_valid() and profile_form.is_valid():
 | 
			
		||||
            user = form.save(commit=False)
 | 
			
		||||
            user.profile = profile_form.save(commit=False)
 | 
			
		||||
            user.save()
 | 
			
		||||
            user.profile.save()
 | 
			
		||||
        if not profile_form.is_valid():
 | 
			
		||||
            return self.form_invalid(form)
 | 
			
		||||
 | 
			
		||||
        user = form.save(commit=False)
 | 
			
		||||
        user.is_active = False
 | 
			
		||||
        user.profile = profile_form.save(commit=False)
 | 
			
		||||
        user.save()
 | 
			
		||||
        user.profile.save()
 | 
			
		||||
        site = get_current_site(self.request)
 | 
			
		||||
        subject = "Activate your {} account".format(site.name)
 | 
			
		||||
        message = loader.render_to_string('registration/account_activation_email.html',
 | 
			
		||||
                                          {
 | 
			
		||||
                                              'user': user,
 | 
			
		||||
                                              'domain': site.domain,
 | 
			
		||||
                                              'site_name': "La Note Kfet",
 | 
			
		||||
                                              'protocol': 'https',
 | 
			
		||||
                                              'token': account_activation_token.make_token(user),
 | 
			
		||||
                                              'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode('UTF-8'),
 | 
			
		||||
                                          })
 | 
			
		||||
        user.email_user(subject, message)
 | 
			
		||||
        return super().form_valid(form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserActivateView(TemplateView):
 | 
			
		||||
    title = _("Account Activation")
 | 
			
		||||
    template_name = 'registration/account_activation_complete.html'
 | 
			
		||||
 | 
			
		||||
    @method_decorator(csrf_protect)
 | 
			
		||||
    def dispatch(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        The dispatch method looks at the request to determine whether it is a GET, POST, etc,
 | 
			
		||||
        and relays the request to a matching method if one is defined, or raises HttpResponseNotAllowed
 | 
			
		||||
        if not. We chose to check the token in the dispatch method to mimic PasswordReset from
 | 
			
		||||
        django.contrib.auth
 | 
			
		||||
        """
 | 
			
		||||
        assert 'uidb64' in kwargs and 'token' in kwargs
 | 
			
		||||
 | 
			
		||||
        self.validlink = False
 | 
			
		||||
        user = self.get_user(kwargs['uidb64'])
 | 
			
		||||
        token = kwargs['token']
 | 
			
		||||
 | 
			
		||||
        if user is not None and account_activation_token.check_token(user, token):
 | 
			
		||||
            self.validlink = True
 | 
			
		||||
            user.is_active = True
 | 
			
		||||
            user.profile.email_confirmed = True
 | 
			
		||||
            user.save()
 | 
			
		||||
            return super().dispatch(*args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            # Display the "Account Activation unsuccessful" page.
 | 
			
		||||
            return self.render_to_response(self.get_context_data())
 | 
			
		||||
 | 
			
		||||
    def get_user(self, uidb64):
 | 
			
		||||
        print(uidb64)
 | 
			
		||||
        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['login_url'] = resolve_url(settings.LOGIN_URL)
 | 
			
		||||
        if self.validlink:
 | 
			
		||||
            context['validlink'] = True
 | 
			
		||||
        else:
 | 
			
		||||
            context.update({
 | 
			
		||||
                'title': _('Account Activation unsuccessful'),
 | 
			
		||||
                'validlink': False,
 | 
			
		||||
            })
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserActivationEmailSentView(TemplateView):
 | 
			
		||||
    template_name = 'registration/account_activation_email_sent.html'
 | 
			
		||||
    title = _('Account activation email sent')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    model = User
 | 
			
		||||
    fields = ['first_name', 'last_name', 'username', 'email']
 | 
			
		||||
@@ -75,14 +157,20 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        form = context['form']
 | 
			
		||||
        form.fields['username'].widget.attrs.pop("autofocus", None)
 | 
			
		||||
        form.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"})
 | 
			
		||||
        form.fields['first_name'].required = True
 | 
			
		||||
        form.fields['last_name'].required = True
 | 
			
		||||
        form.fields['email'].required = True
 | 
			
		||||
        form.fields['email'].help_text = _("This address must be valid.")
 | 
			
		||||
 | 
			
		||||
        context['profile_form'] = self.profile_form(instance=context['user_object'].profile)
 | 
			
		||||
        context['title'] = _("Update Profile")
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get_form(self, form_class=None):
 | 
			
		||||
        form = super().get_form(form_class)
 | 
			
		||||
        if 'username' not in form.data:
 | 
			
		||||
            return form
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        new_username = form.data['username']
 | 
			
		||||
        # Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant
 | 
			
		||||
        note = NoteUser.objects.filter(
 | 
			
		||||
@@ -90,9 +178,8 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
        if note.exists() and note.get().user != self.object:
 | 
			
		||||
            form.add_error('username',
 | 
			
		||||
                           _("An alias with a similar name already exists."))
 | 
			
		||||
        return form
 | 
			
		||||
            return super().form_invalid(form)
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        profile_form = ProfileForm(
 | 
			
		||||
            data=self.request.POST,
 | 
			
		||||
            instance=self.object.profile,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user