Commit d201da12 authored by Bryan  BRANCOTTE's avatar Bryan BRANCOTTE

Ability to validate validate email when creating an account

parent b1837f8c
Pipeline #26564 failed with stage
in 36 seconds
......@@ -55,6 +55,7 @@ BASETHEME_BOOTSTRAP_USER_PREFERENCE_MODEL_LOCATION_APP = "test_app_1"
BASETHEME_BOOTSTRAP_USER_PREFERENCE_MODEL_NAME = "MyUserPreferences"
BASETHEME_BOOTSTRAP_USERNAME_IS_EMAIL = False
BASETHEME_BOOTSTRAP_FIRST_LAST_NAME_REQUIRED = False
BASETHEME_BOOTSTRAP_VALIDATE_EMAIL_BEFORE_ACTIVATION = False
################################################################################
```
......
......@@ -52,6 +52,7 @@ Quick start
BASETHEME_BOOTSTRAP_USER_PREFERENCE_MODEL_NAME = "MyUserPreferences"
BASETHEME_BOOTSTRAP_USERNAME_IS_EMAIL = False
BASETHEME_BOOTSTRAP_FIRST_LAST_NAME_REQUIRED = False
BASETHEME_BOOTSTRAP_VALIDATE_EMAIL_BEFORE_ACTIVATION = False
################################################################################
......
from django.conf import settings
def is_username_is_email():
try:
return settings.BASETHEME_BOOTSTRAP_USERNAME_IS_EMAIL
except AttributeError:
return False
def is_first_last_name_required():
try:
return settings.BASETHEME_BOOTSTRAP_FIRST_LAST_NAME_REQUIRED
except AttributeError:
return False
def is_validating_email():
try:
return settings.BASETHEME_BOOTSTRAP_VALIDATE_EMAIL_BEFORE_ACTIVATION
except AttributeError:
return False
from django.conf import settings
from django.contrib.auth import get_user_model, forms as auth_forms
from django.db.models import Q
from django.forms import ModelForm
......@@ -6,19 +6,7 @@ from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
def is_username_is_email():
try:
return settings.BASETHEME_BOOTSTRAP_USERNAME_IS_EMAIL
except AttributeError:
return False
def is_first_last_name_required():
try:
return settings.BASETHEME_BOOTSTRAP_FIRST_LAST_NAME_REQUIRED
except AttributeError:
return False
from basetheme_bootstrap.default_settings import is_username_is_email, is_first_last_name_required
class CleanUsernameAndSuggestReset:
......
import os
import re
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth.models import AnonymousUser, Group
from django.core import mail
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.test import TestCase, RequestFactory, override_settings
from django.urls import reverse
from basetheme_bootstrap import user_preferences
from basetheme_bootstrap import user_preferences, tokens
from basetheme_bootstrap.user_preferences import get_user_preferences_for_user
......@@ -138,6 +140,99 @@ class SignUpWithFirstLastNameRequiredTests(TestCase):
self.assertEqual(response.status_code, 200)
@override_settings(
BASETHEME_BOOTSTRAP_VALIDATE_EMAIL_BEFORE_ACTIVATION=True,
PASSWORD_RESET_TIMEOUT_DAYS=1,
)
class SignUpWithValidationTests(TestCase):
def setUp(self):
cache.clear()
get_user_model().objects.create(
username="root",
)
self.data = {
'username': "userAAA",
'email': "userAAA@mp.com",
'password1': "user@mp.comuser@mp.comuser@mp.comuser@mp.com",
'password2': "user@mp.comuser@mp.comuser@mp.comuser@mp.com",
'first_name': "user"
}
def test_sign_up_form_view(self):
response = self.client.post(reverse('basetheme_bootstrap:signup'), self.data)
self.assertEqual(response.status_code, 200)
user = get_user_model().objects.last()
self.assertFalse(user.is_active)
self.assertIn(Group.objects.get(name="PendingAccountUser"), user.groups.all())
self.assertEqual(len(mail.outbox), 1)
activate_link_example = reverse('basetheme_bootstrap:activate', args=["AAA", "AAA-AAA"])
activate_link_example = activate_link_example.replace("AAA", "([a-zA-Z0-9]+)")
m = re.findall(activate_link_example, mail.outbox[0].body)
self.assertEqual(len(m), 1)
self.client.post(reverse('basetheme_bootstrap:activate', args=[m[0][0], m[0][1] + "-" + m[0][2]]))
user = get_user_model().objects.last()
self.assertTrue(user.is_active)
self.assertNotIn(Group.objects.get(name="PendingAccountUser"), user.groups.all())
## test an account cannot be re-activated with the link:
user.is_active=False
user.save()
self.client.post(reverse('basetheme_bootstrap:activate', args=[m[0][0], m[0][1] + "-" + m[0][2]]))
self.assertFalse(get_user_model().objects.last().is_active)
def test_sign_up_with_user_pending_resend_email(self):
user_count = get_user_model().objects.count()
response = self.client.post(reverse('basetheme_bootstrap:signup'), self.data)
self.assertEqual(get_user_model().objects.count(), user_count + 1)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
response = self.client.post(reverse('basetheme_bootstrap:signup'), self.data)
self.assertEqual(get_user_model().objects.count(), user_count + 1)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 2)
activate_link_example = reverse('basetheme_bootstrap:activate', args=["AAA", "AAA-AAA"])
activate_link_example = activate_link_example.replace("AAA", "([a-zA-Z0-9]+)")
m = re.findall(activate_link_example, mail.outbox[-1].body)
self.assertEqual(len(m), 1)
def test_activate_too_late_with_user_pending_resend_email(self):
actual_account_activation_token = tokens.account_activation_token
class MockedTokenGenerator(tokens.TokenGenerator):
def _today(self):
from datetime import date, timedelta
# Used for mocking in tests
return date.today() - timedelta(days=2)
tokens.account_activation_token = MockedTokenGenerator()
user_count = get_user_model().objects.count()
response = self.client.post(reverse('basetheme_bootstrap:signup'), self.data)
self.assertEqual(get_user_model().objects.count(), user_count + 1)
self.assertEqual(response.status_code, 200)
self.assertFalse(get_user_model().objects.last().is_active)
tokens.account_activation_token = actual_account_activation_token
self.assertEqual(len(mail.outbox), 1)
activate_link_example = reverse('basetheme_bootstrap:activate', args=["AAA", "AAA-AAA"])
activate_link_example = activate_link_example.replace("AAA", "([a-zA-Z0-9]+)")
m = re.findall(activate_link_example, mail.outbox[0].body)
self.assertEqual(len(m), 1)
self.client.post(reverse('basetheme_bootstrap:activate', args=[m[0][0], m[0][1] + "-" + m[0][2]]))
self.assertFalse(get_user_model().objects.last().is_active)
self.assertEqual(len(mail.outbox), 2)
activate_link_example = reverse('basetheme_bootstrap:activate', args=["AAA", "AAA-AAA"])
activate_link_example = activate_link_example.replace("AAA", "([a-zA-Z0-9]+)")
m = re.findall(activate_link_example, mail.outbox[1].body)
self.assertEqual(len(m), 1)
class TestWithTemplatesInPlace(SignUpTests):
def setUp(self):
......
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class TokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (
six.text_type(user.pk) + six.text_type(timestamp) +
six.text_type(user.is_active)
)
account_activation_token = TokenGenerator()
......@@ -19,6 +19,8 @@ urlpatterns = [
url(r'^accounts/signup/$', views.signup, name='signup'),
url(r'^accounts/edit/$', views.user_update, name='user-update'),
url(r'^accounts/remove/$', views.user_delete, name='user-delete'),
url(r'^accounts/activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
views.activate, name='activate'),
################################################################################
# Lost password
################################################################################
......
......@@ -6,15 +6,21 @@ from django.contrib import messages
from django.contrib.auth import update_session_auth_hash, authenticate, login, get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import Group
from django.core.mail import send_mail
from django.db.models import ProtectedError
from django.forms import widgets
from django.http import HttpResponseForbidden
from django.shortcuts import render, redirect
from django.template import TemplateDoesNotExist
from django.urls import reverse
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.translation import ugettext
from basetheme_bootstrap import tokens
from basetheme_bootstrap import user_preferences
from basetheme_bootstrap.default_settings import is_validating_email
from basetheme_bootstrap.forms import UserCreationFormWithMore, \
MyUserChangeForm, UserDeleteForm
......@@ -53,35 +59,38 @@ def signup(request):
if request.method == 'POST':
form = UserCreationFormWithMore(request.POST)
if form.is_valid():
auto_active = not is_validating_email()
user = form.save()
if get_user_model().objects.filter(pk__gt=1).count() == 0:
user.is_superuser = True
user.is_staff = True
user.save()
user.is_active = True
Group.objects.get_or_create(name="PendingAccountUser")
else:
if not auto_active:
g, created = Group.objects.get_or_create(name="PendingAccountUser")
user.groups.add(g)
user.is_active = auto_active
user.save()
send_account_created(request, user, auto_active=auto_active)
if not auto_active:
return account_is_pending_view(request, email=request.POST['email'])
username = user.username
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
request.user = user
try:
send_mail(
subject=ugettext('Account successfully created'),
message=ugettext(
'Dear %(first_name)s %(last_name)s\n\n'
'Your account have successfully been created on %(joined)s.\n\nBest regards') % dict(
first_name=request.user.first_name,
last_name=request.user.last_name,
joined=str(request.user.date_joined),
),
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[request.user.email],
fail_silently=False,
)
except Exception as e:
logging.exception("Sending email to user %i failed" % user.pk)
login(request, user)
return redirect('home')
else:
user = get_user_model().objects.filter(groups__name="PendingAccountUser",
email=request.POST['email']).first()
if user is not None:
send_account_created(request, user, auto_active=not is_validating_email())
return account_is_pending_view(request, email=request.POST['email'])
else:
if not request.user.is_anonymous:
return HttpResponseForbidden()
......@@ -89,6 +98,57 @@ def signup(request):
return render(request, 'registration/signup.html', {'form': form})
def account_is_pending_view(request, email):
return render(request, 'basetheme_bootstrap/simple_message_page.html', {
'page_title': ugettext('Account activation pending'),
'message': ugettext('An email was sent with a link to validate your account, '
'please click on the link to enable your account.'),
'sub_message': ugettext('The email has been addressed to %s.') % email,
})
def send_account_created(request, user, auto_active=False):
try:
activation_link = request.scheme + "://" + request.get_host()
activation_link += reverse('basetheme_bootstrap:activate', kwargs={
'uidb64': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
'token': tokens.account_activation_token.make_token(user)
})
if auto_active:
message = ugettext(
'Dear %(first_name)s %(last_name)s\n\n'
'Your account have successfully been created on %(joined)s.'
'\n\n'
'Best regards') % dict(
first_name=user.first_name,
last_name=user.last_name,
joined=str(user.date_joined),
)
else:
message = ugettext(
'Dear %(first_name)s %(last_name)s\n\n'
'Your account have successfully been created on %(joined)s.'
'\n\n'
'Please click on the link to confirm your registration\n'
'%(activation_link)s'
'\n\n'
'Best regards') % dict(
first_name=user.first_name,
last_name=user.last_name,
joined=str(user.date_joined),
activation_link=activation_link,
)
send_mail(
subject=ugettext('Account successfully created'),
message=message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email],
fail_silently=False,
)
except Exception as e:
logging.exception("Sending email to user %i failed" % user.pk)
def user_update(request):
if request.method == 'POST':
form = MyUserChangeForm(instance=request.user, data=request.POST)
......@@ -165,3 +225,31 @@ def account_detail(request):
'form_prefs': form_prefs,
'btn_classes': 'pull-right float-right'
})
def activate(request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = get_user_model().objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, get_user_model().DoesNotExist):
user = None
if user is not None and user.groups.filter(name="PendingAccountUser").exists():
if tokens.account_activation_token.check_token(user, token):
user.is_active = True
user.groups.remove(user.groups.get(name="PendingAccountUser"))
user.save()
login(request, user)
# return redirect('home')
# return HttpResponse('Thank you for your email confirmation. Now you can login your account.')
return render(request, 'basetheme_bootstrap/simple_message_page.html', {
'page_title': ugettext('Account activated'),
'message': ugettext('Thank you for your email confirmation, you account have been activated '
'and you are now logged in.'),
})
else:
send_account_created(request, user)
return account_is_pending_view(request, email=user.email)
else:
return render(request, 'basetheme_bootstrap/simple_message_page.html', {
'page_title': ugettext('Activation link is invalid!'),
})
......@@ -7,7 +7,7 @@ readme = open('README.rst').read()
setup(
name='django-basetheme-bootstrap',
version='0.2.29',
version='0.2.30',
description='Django Basetheme Bootstrap',
long_description=readme,
author='Bryan Brancotte',
......
......@@ -138,6 +138,7 @@ BASETHEME_BOOTSTRAP_USER_PREFERENCE_MODEL_LOCATION_APP = "test_app_1"
BASETHEME_BOOTSTRAP_USER_PREFERENCE_MODEL_NAME = "MyUserPreferences"
# BASETHEME_BOOTSTRAP_USERNAME_IS_EMAIL = True # default is False
# BASETHEME_BOOTSTRAP_FIRST_LAST_NAME_REQUIRED = True # default is False
# BASETHEME_BOOTSTRAP_VALIDATE_EMAIL_BEFORE_ACTIVATION = True # default is False
################################################################################
# Various debug stuff
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment