Skip to content

Commit

Permalink
Merge pull request bread-and-pepper#294 from nephila/feature/activati…
Browse files Browse the repository at this point in the history
…on_retry_1.2

Add activation retry option.
  • Loading branch information
wunki committed May 29, 2013
2 parents 32bea03 + 60fca6c commit 0ce83b6
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 16 deletions.
43 changes: 43 additions & 0 deletions userena/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,30 @@ def create_userena_profile(self, user):
return self.create(user=user,
activation_key=activation_key)

def reissue_activation(self, activation_key):
"""
Creates a new ``activation_key`` resetting activation timeframe when
users let the previous key expire.
:param activation_key:
String containing the secret SHA1 activation key.
"""
try:
userena = self.get(activation_key=activation_key)
except self.model.DoesNotExist:
return False
try:
salt, new_activation_key = generate_sha1(userena.user.username)
userena.activation_key = new_activation_key
userena.save(using=self._db)
userena.user.date_joined = get_datetime_now()
userena.user.save(using=self._db)
userena.send_activation_email()
return True
except Exception,e:
return False

def activate_user(self, activation_key):
"""
Activate an :class:`User` by supplying a valid ``activation_key``.
Expand Down Expand Up @@ -136,6 +160,25 @@ def activate_user(self, activation_key):
return user
return False

def check_expired_activation(self, activation_key):
"""
Check if ``activation_key`` is still valid.
Raises a ``self.model.DoesNotExist`` exception if key is not present or
``activation_key`` is not a valid string
:param activation_key:
String containing the secret SHA1 for a valid activation.
:return:
True if the ket has expired, False if still valid.
"""
if SHA1_RE.search(activation_key):
userena = self.get(activation_key=activation_key)
return userena.activation_key_expired()
raise self.model.DoesNotExist

def confirm_email(self, confirmation_key):
"""
Confirm an email address by checking a ``confirmation_key``.
Expand Down
4 changes: 4 additions & 0 deletions userena/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
'USERENA_ACTIVATION_NOTIFY_DAYS',
5)

USERENA_ACTIVATION_RETRY = getattr(settings,
'USERENA_ACTIVATION_RETRY',
False)

USERENA_ACTIVATED = getattr(settings,
'USERENA_ACTIVATED',
'ALREADY_ACTIVATED')
Expand Down
12 changes: 12 additions & 0 deletions userena/templates/userena/activate_retry.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends 'userena/base_userena.html' %}
{% load i18n %}
{% load url from future %}


{% block title %}{% trans "Account activation failed." %}{% endblock %}
{% block content_title %}<h2>{% trans "Your account could not be activated..." %}</h2>{% endblock %}

{% block content %}
<p>{% trans "Your account could not be activated because activation link is expired." %}</p>
<p><a href="{% url 'userena_activate_retry' activation_key %}">{% trans "Request a new activation link." %}</a></p>
{% endblock %}
13 changes: 13 additions & 0 deletions userena/templates/userena/activate_retry_success.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends 'userena/base_userena.html' %}
{% load i18n %}
{% load url from future %}


{% block title %}{% trans "Account re-activation succeded." %}{% endblock %}
{% block content_title %}<h2>{% trans "Account re-activation" %}</h2>{% endblock %}

{% block content %}
<p>{% blocktrans %}You requested a new activation of your account..{% endblocktrans %}</p>
<p>{% blocktrans %}You have been sent an e-mail with an activation link to the supplied email.{% endblocktrans %}</p>
<p>{% blocktrans %}We will store your signup information for {{ userena_activation_days }} days on our server. {% endblocktrans %}</p>
{% endblock %}
61 changes: 61 additions & 0 deletions userena/tests/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from datetime import datetime, timedelta
from django.core.urlresolvers import reverse
from django.core import mail
from django.contrib.auth.forms import PasswordChangeForm
from django.conf import settings
from django.test.utils import override_settings

from userena import forms
from userena import settings as userena_settings
Expand Down Expand Up @@ -33,6 +35,65 @@ def test_valid_activation(self):
user = User.objects.get(email='[email protected]')
self.failUnless(user.is_active)

def test_activation_expired_retry(self):
""" A ``GET`` to the activation view when activation link is expired """
# First, register an account.
userena_settings.USERENA_ACTIVATION_RETRY = True
self.client.post(reverse('userena_signup'),
data={'username': 'alice',
'email': '[email protected]',
'password1': 'swordfish',
'password2': 'swordfish',
'tos': 'on'})
user = User.objects.get(email='[email protected]')
user.date_joined = datetime.today() - timedelta(days=30)
user.save()
response = self.client.get(reverse('userena_activate',
kwargs={'activation_key': user.userena_signup.activation_key}))
self.assertContains(response, "Request a new activation link")

user = User.objects.get(email='[email protected]')
self.failUnless(not user.is_active)
userena_settings.USERENA_ACTIVATION_RETRY = False

def test_retry_activation_ask(self):
""" Ask for a new activation link """
# First, register an account.
userena_settings.USERENA_ACTIVATION_RETRY = True
self.client.post(reverse('userena_signup'),
data={'username': 'alice',
'email': '[email protected]',
'password1': 'swordfish',
'password2': 'swordfish',
'tos': 'on'})
user = User.objects.get(email='[email protected]')
user.date_joined = datetime.today() - timedelta(days=30)
user.save()
old_key = user.userena_signup.activation_key
response = self.client.get(reverse('userena_activate_retry',
kwargs={'activation_key': old_key}))

# We must reload the object from database to get the new key
user = User.objects.get(email='[email protected]')
self.assertContains(response, "Account re-activation succeded")

self.failIfEqual(old_key, user.userena_signup.activation_key)
user = User.objects.get(email='[email protected]')
self.failUnless(not user.is_active)

self.failUnlessEqual(len(mail.outbox), 2)
self.assertEqual(mail.outbox[1].to, ['[email protected]'])
self.assertTrue(mail.outbox[1].body.find("activate your account ")>-1)

response = self.client.get(reverse('userena_activate',
kwargs={'activation_key': user.userena_signup.activation_key}))
self.assertRedirects(response,
reverse('userena_profile_detail', kwargs={'username': user.username}))

user = User.objects.get(email='[email protected]')
self.failUnless(user.is_active)
userena_settings.USERENA_ACTIVATION_RETRY = False

def test_invalid_activation(self):
"""
A ``GET`` to the activation view with a wrong ``activation_key``.
Expand Down
5 changes: 5 additions & 0 deletions userena/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
userena_views.activate,
name='userena_activate'),

# Retry activation
url(r'^activate/retry/(?P<activation_key>\w+)/$',
userena_views.activate_retry,
name='userena_activate_retry'),

# Change email and confirm it
url(r'^(?P<username>[\.\w-]+)/email/$',
userena_views.email_change,
Expand Down
96 changes: 80 additions & 16 deletions userena/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def signup(request, signup_form=SignupForm,
@secure_required
def activate(request, activation_key,
template_name='userena/activate_fail.html',
retry_template_name='userena/activate_retry.html',
success_url=None, extra_context=None):
"""
Activate a user with an activation key.
Expand All @@ -155,6 +156,8 @@ def activate(request, activation_key,
activated. After a successful activation the view will redirect to
``success_url``. If the SHA1 is not found, the user will be shown the
``template_name`` template displaying a fail message.
If the SHA1 is found but expired, ``retry_template_name`` is used instead,
so the user can proceed to :func:`activate_retry` to get a new actvation key.
:param activation_key:
String of a SHA1 string of 40 characters long. A SHA1 is always 160bit
Expand All @@ -164,7 +167,12 @@ def activate(request, activation_key,
:param template_name:
String containing the template name that is used when the
``activation_key`` is invalid and the activation fails. Defaults to
``userena/activation_fail.html``.
``userena/activate_fail.html``.
:param retry_template_name:
String containing the template name that is used when the
``activation_key`` is expired. Defaults to
``userena/activate_retry.html``.
:param success_url:
String containing the URL where the user should be redirected to after
Expand All @@ -177,25 +185,81 @@ def activate(request, activation_key,
context. Default to an empty dictionary.
"""
user = UserenaSignup.objects.activate_user(activation_key)
if user:
# Sign the user in.
auth_user = authenticate(identification=user.email,
check_password=False)
login(request, auth_user)
try:
if (not UserenaSignup.objects.check_expired_activation(activation_key)
or not userena_settings.USERENA_ACTIVATION_RETRY):
user = UserenaSignup.objects.activate_user(activation_key)
if user:
# Sign the user in.
auth_user = authenticate(identification=user.email,
check_password=False)
login(request, auth_user)

if userena_settings.USERENA_USE_MESSAGES:
messages.success(request, _('Your account has been activated and you have been signed in.'),
fail_silently=True)
if userena_settings.USERENA_USE_MESSAGES:
messages.success(request, _('Your account has been activated and you have been signed in.'),
fail_silently=True)

if success_url: redirect_to = success_url % {'username': user.username }
else: redirect_to = reverse('userena_profile_detail',
kwargs={'username': user.username})
return redirect(redirect_to)
else:
if success_url: redirect_to = success_url % {'username': user.username }
else: redirect_to = reverse('userena_profile_detail',
kwargs={'username': user.username})
return redirect(redirect_to)
else:
if not extra_context: extra_context = dict()
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(
request)
else:
if not extra_context: extra_context = dict()
extra_context['activation_key'] = activation_key
return ExtraContextTemplateView.as_view(template_name=retry_template_name,
extra_context=extra_context)(request)
except UserenaSignup.DoesNotExist,e:
if not extra_context: extra_context = dict()
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(request)
extra_context=extra_context)(request)

@secure_required
def activate_retry(request, activation_key,
template_name='userena/activate_retry_success.html',
extra_context=None):
"""
Reissue a new ``activation_key`` for the user with the expired
``activation_key``.
If ``activation_key`` does not exists, or ``USERENA_ACTIVATION_RETRY`` is
set to False and for any other error condition user is redirected to
:func:`activate` for error message display.
:param activation_key:
String of a SHA1 string of 40 characters long. A SHA1 is always 160bit
long, with 4 bits per character this makes it --160/4-- 40 characters
long.
:param template_name:
String containing the template name that is used when new
``activation_key`` has been created. Defaults to
``userena/activate_retry_success.html``.
:param extra_context:
Dictionary containing variables which could be added to the template
context. Default to an empty dictionary.
"""
if not userena_settings.USERENA_ACTIVATION_RETRY:
return redirect(reverse('userena_activate', args=(activation_key,)))
try:
if UserenaSignup.objects.check_expired_activation(activation_key):
new_key = UserenaSignup.objects.reissue_activation(activation_key)
if new_key:
if not extra_context: extra_context = dict()
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(request)
else:
return redirect(reverse('userena_activate',args=(activation_key,)))
else:
return redirect(reverse('userena_activate',args=(activation_key,)))
except UserenaSignup.DoesNotExist:
return redirect(reverse('userena_activate',args=(activation_key,)))

@secure_required
def email_confirm(request, confirmation_key,
Expand Down

0 comments on commit 0ce83b6

Please sign in to comment.