1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2024-11-26 18:37:12 +00:00

Use a separate app for registration

This commit is contained in:
Yohann D'ANELLO 2020-04-05 05:17:28 +02:00
parent 0f77b9df9a
commit 49807d33d9
14 changed files with 168 additions and 133 deletions

View File

@ -2,9 +2,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
from permission.models import PermissionMask from permission.models import PermissionMask
@ -19,21 +18,6 @@ class CustomAuthenticationForm(AuthenticationForm):
) )
class SignUpForm(UserCreationForm):
def __init__(self, *args, **kwargs):
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', )
class ProfileForm(forms.ModelForm): class ProfileForm(forms.ModelForm):
""" """
A form for the extras field provided by the :model:`member.Profile` model. A form for the extras field provided by the :model:`member.Profile` model.

View File

@ -12,7 +12,6 @@ from django.urls import reverse, reverse_lazy
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode from django.utils.http import urlsafe_base64_encode
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.tokens import account_activation_token from member.tokens import account_activation_token
from note.models import MembershipTransaction from note.models import MembershipTransaction

View File

@ -7,11 +7,6 @@ from . import views
app_name = 'member' app_name = 'member'
urlpatterns = [ 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/', views.ClubListView.as_view(), name="club_list"),
path('club/create/', views.ClubCreateView.as_view(), name="club_create"), path('club/create/', views.ClubCreateView.as_view(), name="club_create"),
path('club/<int:pk>/', views.ClubDetailView.as_view(), name="club_detail"), path('club/<int:pk>/', views.ClubDetailView.as_view(), name="club_detail"),

View File

@ -12,12 +12,9 @@ from django.contrib.auth.views import LoginView
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
from django.forms import HiddenInput from django.forms import HiddenInput
from django.shortcuts import redirect, resolve_url from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
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.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 import CreateView, DetailView, UpdateView, TemplateView
from django.views.generic.base import View from django.views.generic.base import View
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
@ -30,10 +27,9 @@ from note.tables import HistoryTable, AliasTable
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin from permission.views import ProtectQuerysetMixin
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable from .tables import ClubTable, UserTable, MembershipTable
from .tokens import account_activation_token
class CustomLoginView(LoginView): class CustomLoginView(LoginView):
@ -44,98 +40,6 @@ class CustomLoginView(LoginView):
return super().form_valid(form) return super().form_valid(form)
class UserCreateView(CreateView):
"""
Une vue pour inscrire un utilisateur et lui créer un profil
"""
form_class = SignUpForm
success_url = reverse_lazy('member:login')
template_name = 'member/signup.html'
second_form = ProfileForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["profile_form"] = self.second_form()
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 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()
user.profile.send_email_validation_link()
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): class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = User model = User
fields = ['first_name', 'last_name', 'username', 'email'] fields = ['first_name', 'last_name', 'username', 'email']

View File

@ -0,0 +1,4 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'registration.apps.RegistrationConfig'

View File

10
apps/registration/apps.py Normal file
View File

@ -0,0 +1,10 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class RegistrationConfig(AppConfig):
name = 'registration'
verbose_name = _('registration')

View File

@ -0,0 +1,21 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
class SignUpForm(UserCreationForm):
def __init__(self, *args, **kwargs):
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', )

View File

13
apps/registration/urls.py Normal file
View File

@ -0,0 +1,13 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path
from . import views
app_name = 'registration'
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'),
]

110
apps/registration/views.py Normal file
View File

@ -0,0 +1,110 @@
# 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.models import User
from django.core.exceptions import ValidationError
from django.shortcuts import resolve_url
from django.urls import reverse_lazy
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.decorators.csrf import csrf_protect
from django.views.generic import CreateView, TemplateView
from member.forms import ProfileForm
from member.tokens import account_activation_token
from .forms import SignUpForm
class UserCreateView(CreateView):
"""
Une vue pour inscrire un utilisateur et lui créer un profil
"""
form_class = SignUpForm
success_url = reverse_lazy('member:login')
template_name = 'member/signup.html'
second_form = ProfileForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["profile_form"] = self.second_form()
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 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()
user.profile.send_email_validation_link()
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')

View File

@ -54,13 +54,14 @@ INSTALLED_APPS = [
'rest_framework.authtoken', 'rest_framework.authtoken',
# Note apps # Note apps
'api',
'activity', 'activity',
'logs',
'member', 'member',
'note', 'note',
'treasury',
'permission', 'permission',
'api', 'registration',
'logs', 'treasury',
] ]
LOGIN_REDIRECT_URL = '/note/transfer/' LOGIN_REDIRECT_URL = '/note/transfer/'

View File

@ -16,6 +16,7 @@ urlpatterns = [
# Include project routers # Include project routers
path('note/', include('note.urls')), path('note/', include('note.urls')),
path('accounts/', include('member.urls')), path('accounts/', include('member.urls')),
path('registration/', include('registration.urls')),
path('activity/', include('activity.urls')), path('activity/', include('activity.urls')),
path('treasury/', include('treasury.urls')), path('treasury/', include('treasury.urls')),
@ -37,14 +38,7 @@ if "cas_server" in settings.INSTALLED_APPS:
# Include CAS Server routers # Include CAS Server routers
path('cas/', include('cas_server.urls', namespace="cas_server")), path('cas/', include('cas_server.urls', namespace="cas_server")),
] ]
if "cas" in settings.INSTALLED_APPS:
from cas import views as cas_views
urlpatterns += [
# Include CAS Client routers
path('accounts/login/cas/', cas_views.login, name='cas_login'),
path('accounts/logout/cas/', cas_views.logout, name='cas_logout'),
]
if "debug_toolbar" in settings.INSTALLED_APPS: if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [

View File

@ -124,7 +124,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</li> </li>
{% else %} {% else %}
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="{% url 'member:signup' %}"> <a class="nav-link" href="{% url 'registration:signup' %}">
<i class="fa fa-user-plus"></i> S'inscrire <i class="fa fa-user-plus"></i> S'inscrire
</a> </a>
</li> </li>
@ -138,7 +138,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</nav> </nav>
<div class="container-fluid my-3" style="max-width: 1600px;"> <div class="container-fluid my-3" style="max-width: 1600px;">
{% if not user.profile.email_confirmed %} {% if user.is_authenticated and not user.profile.email_confirmed %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %} {% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %}
</div> </div>