From c9ba71eed680537ef77ce4be2c1806b2fece8cf0 Mon Sep 17 00:00:00 2001 From: Serghei MIHAI Date: Tue, 18 Nov 2014 17:23:26 +0100 Subject: [PATCH 1/2] Registration refactored: email validation done first and registration process finished on profile completion. django-registration removed --- authentic2/app_settings.py | 3 + authentic2/auth2_auth/auth2_ssl/views.py | 8 +- authentic2/profile_urls.py | 29 ++++ authentic2/registration_backend/__init__.py | 60 ------- authentic2/registration_backend/forms.py | 120 +++++++++++-- authentic2/registration_backend/urls.py | 95 +++------- authentic2/registration_backend/views.py | 191 +++++++++++---------- authentic2/settings.py | 1 - authentic2/templates/auth/login_form.html | 2 +- authentic2/templates/registration/activate.html | 22 --- .../registration/activation_complete.html | 6 - .../templates/registration/activation_email.html | 8 + .../templates/registration/activation_email.txt | 4 +- .../registration/activation_email_subject.txt | 2 +- .../templates/registration/activation_expired.html | 11 ++ .../templates/registration/login_choices.html | 28 +++ .../registration/registration_complete.html | 2 +- .../registration/registration_completion_form.html | 23 +++ .../templates/registration/registration_form.html | 2 +- authentic2/tests.py | 41 ++++- authentic2/utils.py | 5 + diagnose.py | 4 - requirements.txt | 1 - setup.py | 1 - 24 files changed, 393 insertions(+), 276 deletions(-) delete mode 100644 authentic2/templates/registration/activate.html delete mode 100644 authentic2/templates/registration/activation_complete.html create mode 100644 authentic2/templates/registration/activation_email.html create mode 100644 authentic2/templates/registration/activation_expired.html create mode 100644 authentic2/templates/registration/login_choices.html create mode 100644 authentic2/templates/registration/registration_completion_form.html diff --git a/authentic2/app_settings.py b/authentic2/app_settings.py index a9d718d..b5a78ea 100644 --- a/authentic2/app_settings.py +++ b/authentic2/app_settings.py @@ -90,6 +90,8 @@ default_settings = dict( definition='Root urlconf for the /accounts endpoints'), A2_REGISTRATION_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.RegistrationForm', definition='Default registration form'), + A2_REGISTRATION_COMPLETION_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.RegistrationCompletionForm', + definition='Default registration completion form'), A2_REGISTRATION_SET_PASSWORD_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.SetPasswordForm', definition='Default set password form'), A2_REGISTRATION_CHANGE_PASSWORD_FORM_CLASS = Setting(default='authentic2.registration_backend.forms.PasswordChangeForm', @@ -131,6 +133,7 @@ default_settings = dict( A2_PASSWORD_POLICY_MIN_LENGTH=Setting(default=6, definition='Minimum number of characters in a password'), A2_AUTH_PASSWORD_ENABLE=Setting(default=True, definition='Activate login/password authentication', names=('AUTH_PASSWORD',)), PUSH_PROFILE_UPDATES=Setting(default=False, definition='Push profile update to linked services'), + A2_REGISTRATION_ID_NAME=Setting(default='registration_id', definition='Unique registration identifier name'), ) app_settings = AppSettings(default_settings) diff --git a/authentic2/auth2_auth/auth2_ssl/views.py b/authentic2/auth2_auth/auth2_ssl/views.py index 08af1ce..ad5c04f 100644 --- a/authentic2/auth2_auth/auth2_ssl/views.py +++ b/authentic2/auth2_auth/auth2_ssl/views.py @@ -19,7 +19,7 @@ from django.contrib.auth import authenticate, login from django.contrib.auth import REDIRECT_FIELD_NAME -import registration.views +from authentic2.registration_backend.views import RegistrationView from authentic2.constants import NONCE_FIELD_NAME @@ -188,7 +188,7 @@ error_ssl = SslErrorView.as_view() def register(request): '''Registration page for SSL auth without CA''' next_url = request.GET.get(REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL) - return registration.views.register(request, success_url=next_url, - form_class=functools.partial(forms.RegistrationForm, - request=request)) + return RegistrationView.as_view(request, success_url=next_url, + form_class=functools.partial(forms.RegistrationForm, + request=request)) diff --git a/authentic2/profile_urls.py b/authentic2/profile_urls.py index 0e87426..1a4f63c 100644 --- a/authentic2/profile_urls.py +++ b/authentic2/profile_urls.py @@ -1,4 +1,13 @@ from django.conf.urls import patterns, url +from django.contrib.auth import views as auth_views + +from authentic2.utils import get_form_class +from . import app_settings + +SET_PASSWORD_FORM_CLASS = get_form_class( + app_settings.A2_REGISTRATION_SET_PASSWORD_FORM_CLASS) +CHANGE_PASSWORD_FORM_CLASS = get_form_class( + app_settings.A2_REGISTRATION_CHANGE_PASSWORD_FORM_CLASS) urlpatterns = patterns('authentic2.views', url(r'^logged-in/$', 'logged_in', name='logged-in'), @@ -7,4 +16,24 @@ urlpatterns = patterns('authentic2.views', url(r'^change-email/verify/$', 'email_change_verify', name='email-change-verify'), url(r'^$', 'profile', name='account_management'), + url(r'^password/change/$', + auth_views.password_change, + {'password_change_form': CHANGE_PASSWORD_FORM_CLASS}, + name='auth_password_change'), + url(r'^password/change/done/$', + auth_views.password_change_done, + name='auth_password_change_done'), + url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', + auth_views.password_reset_confirm, + {'set_password_form': SET_PASSWORD_FORM_CLASS}, + name='auth_password_reset_confirm'), + url(r'^password/reset/$', + auth_views.password_reset, + name='auth_password_reset'), + url(r'^password/reset/complete/$', + auth_views.password_reset_complete, + name='auth_password_reset_complete'), + url(r'^password/reset/done/$', + auth_views.password_reset_done, + name='auth_password_reset_done'), ) diff --git a/authentic2/registration_backend/__init__.py b/authentic2/registration_backend/__init__.py index 777e572..e69de29 100644 --- a/authentic2/registration_backend/__init__.py +++ b/authentic2/registration_backend/__init__.py @@ -1,60 +0,0 @@ -from django.conf import settings -from django.template.loader import render_to_string - -from registration.models import RegistrationProfile - -def send_activation_email(self, site): - """ - Send an activation email to the user associated with this - ``RegistrationProfile``. - - The activation email will make use of two templates: - - ``registration/activation_email_subject.txt`` - This template will be used for the subject line of the - email. Because it is used as the subject line of an email, - this template's output **must** be only a single line of - text; output longer than one line will be forcibly joined - into only a single line. - - ``registration/activation_email.txt`` - This template will be used for the body of the email. - - These templates will each receive the following context - variables: - - ``user`` - The new user account - - ``activation_key`` - The activation key for the new account. - - ``expiration_days`` - The number of days remaining during which the account may - be activated. - - ``site`` - An object representing the site on which the user - registered; depending on whether ``django.contrib.sites`` - is installed, this may be an instance of either - ``django.contrib.sites.models.Site`` (if the sites - application is installed) or - ``django.contrib.sites.models.RequestSite`` (if - not). Consult the documentation for the Django sites - framework for details regarding these objects' interfaces. - - """ - ctx_dict = {'activation_key': self.activation_key, - 'user': self.user, - 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, - 'site': site} - subject = render_to_string('registration/activation_email_subject.txt', - ctx_dict) - # Email subject *must not* contain newlines - subject = ''.join(subject.splitlines()) - - message = render_to_string('registration/activation_email.txt', - ctx_dict) - - self.user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) -RegistrationProfile.send_activation_email = send_activation_email diff --git a/authentic2/registration_backend/forms.py b/authentic2/registration_backend/forms.py index e25ac44..e1df4d6 100644 --- a/authentic2/registration_backend/forms.py +++ b/authentic2/registration_backend/forms.py @@ -1,15 +1,76 @@ +from uuid import uuid1 + +from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from django.forms import Form, CharField, PasswordInput, EmailField from django.utils.datastructures import SortedDict from django.db.models import FieldDoesNotExist +from django.contrib.auth.models import BaseUserManager, Group from django.contrib.auth import forms as auth_forms +from django.core.mail import send_mail +from django.core import signing +from django import get_version +from django.template.loader import render_to_string +from django.core.urlresolvers import reverse + +from .. import app_settings, compat, forms, utils,\ + validators, widgets, fields, models -from .. import app_settings, compat, forms, utils, validators, widgets, fields +User = compat.get_user_model() +is_django_17 = get_version().startswith('1.7') +EXPIRATION = settings.ACCOUNT_ACTIVATION_DAYS +registration_id_name = app_settings.A2_REGISTRATION_ID_NAME -class RegistrationForm(forms.UserAttributeFormMixin, Form): +class RegistrationForm(Form): + error_css_class = 'form-field-error' + required_css_class = 'form-field-required' + + email = EmailField() + + def clean_email(self): + """ + Verify if email is unique + """ + User = compat.get_user_model() + if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE and \ + User.objects.filter(email__iexact=self.cleaned_data['email']).exists(): + raise ValidationError(_('This email address is already in ' + 'use. Please supply a different email address.')) + return self.cleaned_data['email'] + + def save(self, request): + data = self.cleaned_data + registration_id = uuid1().hex + data.update({'next_url': request.GET.get('next_url'), + registration_id_name: registration_id}) + registration_token = signing.dumps(data) + ctx_dict = {'registration_url': request.build_absolute_uri( + reverse('registration_activate', + kwargs={'registration_token': registration_token})), + 'expiration_days': EXPIRATION, + 'email': data['email']} + ctx_dict.update(self.cleaned_data) + + subject = render_to_string('registration/activation_email_subject.txt', + ctx_dict) + + subject = ''.join(subject.splitlines()) + message = render_to_string('registration/activation_email.txt', + ctx_dict) + if is_django_17: + html_message = render_to_string('registration/activation_email.html', + ctx_dict) + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, + [data['email']], fail_silently=True, + html_message=message) + else: + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, + [data['email']], fail_silently=True) + +class RegistrationCompletionForm(forms.UserAttributeFormMixin, Form): error_css_class = 'form-field-error' required_css_class = 'form-field-required' @@ -21,7 +82,7 @@ class RegistrationForm(forms.UserAttributeFormMixin, Form): """ Inject required fields in registration form """ - super(RegistrationForm, self).__init__(*args, **kwargs) + super(RegistrationCompletionForm, self).__init__(*args, **kwargs) User = compat.get_user_model() insert_idx = 0 field_names = compat.get_registration_fields() @@ -38,7 +99,7 @@ class RegistrationForm(forms.UserAttributeFormMixin, Form): kwargs['validators'] = model_field.validators field = model_field.formfield(**kwargs) if isinstance(field, EmailField): - field = fields.EmailFieldWithValidation(**kwargs) + continue self.fields.insert(insert_idx, field_name, field) insert_idx += 1 for field_name in self.fields: @@ -64,6 +125,7 @@ class RegistrationForm(forms.UserAttributeFormMixin, Form): self.fields['username'].help_text = app_settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT self.fields['username'].label = app_settings.A2_REGISTRATION_FORM_USERNAME_LABEL + def clean_username(self): """ Validate that the username is alphanumeric and is not already @@ -82,17 +144,6 @@ class RegistrationForm(forms.UserAttributeFormMixin, Form): else: return self.cleaned_data['username'] - def clean_email(self): - """ - Verify if email is unique - """ - User = compat.get_user_model() - if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE: - if User.objects.filter(email__iexact=self.cleaned_data['email']): - raise ValidationError(_('This email address is already in ' - 'use. Please supply a different email address.')) - return self.cleaned_data['email'] - def clean(self): """ Verifiy that the values entered into the two password fields @@ -105,6 +156,45 @@ class RegistrationForm(forms.UserAttributeFormMixin, Form): raise ValidationError(_("The two password fields didn't match.")) return self.cleaned_data + def save(self, *args, **kwargs): + user_fields = {} + for field in compat.get_registration_fields(): + # save User model fields + try: + User._meta.get_field(field) + except FieldDoesNotExist: + continue + if field.startswith('password'): + continue + user_fields[field] = kwargs[field] + if field == 'email': + user_fields[field] = BaseUserManager.normalize_email(kwargs[field]) + + new_user = User(is_active=True, **user_fields) + new_user.clean() + new_user.set_password(kwargs['password1']) + new_user.save() + + registration_id, created = models.Attribute.objects.get_or_create(label=registration_id_name, + name=registration_id_name, + kind='string', + asked_on_registration=False, + user_visible=False) + registration_id.set_value(new_user, kwargs[registration_id_name]) + + attributes = models.Attribute.objects.filter( + asked_on_registration=True) + if attributes: + for attribute in attributes: + attribute.set_value(new_user, kwargs[attribute.name]) + if app_settings.A2_REGISTRATION_GROUPS: + groups = [] + for name in app_settings.A2_REGISTRATION_GROUPS: + group, created = Group.objects.get_or_create(name=name) + groups.append(group) + new_user.groups = groups + return new_user, kwargs['next_url'] + class SetPasswordForm(auth_forms.SetPasswordForm): new_password1 = CharField(label=_("New password"), widget=PasswordInput, diff --git a/authentic2/registration_backend/urls.py b/authentic2/registration_backend/urls.py index 58a19a4..49ae5c9 100644 --- a/authentic2/registration_backend/urls.py +++ b/authentic2/registration_backend/urls.py @@ -1,71 +1,32 @@ from django.conf.urls import patterns from django.conf.urls import url from django.utils.importlib import import_module -from django.contrib.auth import views as auth_views from django.views.generic.base import TemplateView - - -from .. import app_settings - -from registration.backends.default.views import ActivationView -from .. import decorators - -def get_form_class(form_class): - module, form_class = form_class.rsplit('.', 1) - module = import_module(module) - return getattr(module, form_class) - - -SET_PASSWORD_FORM_CLASS = get_form_class( - app_settings.A2_REGISTRATION_SET_PASSWORD_FORM_CLASS) -CHANGE_PASSWORD_FORM_CLASS = get_form_class( - app_settings.A2_REGISTRATION_CHANGE_PASSWORD_FORM_CLASS) - - -password_change_view = decorators.setting_enabled( - 'A2_REGISTRATION_CAN_CHANGE_PASSWORD')(auth_views.password_change) - - -urlpatterns = patterns('authentic2.registration_backend.views', - url(r'^activate/complete/$', - TemplateView.as_view(template_name='registration/activation_complete.html'), - name='registration_activation_complete'), - # Activation keys get matched by \w+ instead of the more specific - # [a-fA-F0-9]{40} because a bad activation key should still get to the view; - # that way it can return a sensible "invalid key" message instead of a - # confusing 404. - url(r'^activate/(?P\w+)/$', - ActivationView.as_view(), - name='registration_activate'), - url(r'^register/$', - 'register', - name='registration_register'), - url(r'^register/complete/$', - TemplateView.as_view(template_name='registration/registration_complete.html'), - name='registration_complete'), - url(r'^register/closed/$', - TemplateView.as_view(template_name='registration/registration_closed.html'), - name='registration_disallowed'), - url(r'^password/change/$', password_change_view, - {'password_change_form': CHANGE_PASSWORD_FORM_CLASS}, - name='auth_password_change'), - url(r'^password/change/done/$', - auth_views.password_change_done, - name='auth_password_change_done'), - url(r'^password/reset/$', - auth_views.password_reset, - name='auth_password_reset'), - url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', - auth_views.password_reset_confirm, - {'set_password_form': SET_PASSWORD_FORM_CLASS}, - name='auth_password_reset_confirm'), - url(r'^password/reset/complete/$', - auth_views.password_reset_complete, - name='auth_password_reset_complete'), - url(r'^password/reset/done/$', - auth_views.password_reset_done, - name='auth_password_reset_done'), - url(r'^delete/$', - 'delete', - name='delete_account'), - ) +from django.contrib.auth.decorators import login_required + +from .views import RegistrationView, RegistrationCompletionView, DeleteView,\ + LoginView + +urlpatterns = patterns('', + url(r'^activate/expired/$', + TemplateView.as_view(template_name='registration/activation_expired.html'), + name='registration_activation_expired'), + url(r'^activate/(?P[\w:-]+)/$', + RegistrationCompletionView.as_view(), + name='registration_activate'), + url(r'^activate/(?P[\w:-]+)/(?P\w+)$', + LoginView.as_view(), + name='registration_login'), + url(r'^register/$', + RegistrationView.as_view(), + name='registration_register'), + url(r'^register/complete/$', + TemplateView.as_view(template_name='registration/registration_complete.html'), + name='registration_complete'), + url(r'^register/closed/$', + TemplateView.as_view(template_name='registration/registration_closed.html'), + name='registration_disallowed'), + url(r'^delete/$', + login_required(DeleteView.as_view()), + name='delete_account'), +) diff --git a/authentic2/registration_backend/views.py b/authentic2/registration_backend/views.py index 3602e9a..d81ac9a 100644 --- a/authentic2/registration_backend/views.py +++ b/authentic2/registration_backend/views.py @@ -1,104 +1,120 @@ import logging +from datetime import datetime - +from django.conf import settings from django.shortcuts import redirect, render from django.utils.translation import ugettext as _ from django.contrib import messages -from django.contrib.auth.decorators import login_required -from django.contrib.sites.models import RequestSite -from django.contrib.sites.models import Site -from django.contrib.auth.models import BaseUserManager, Group -from django.conf import settings +from django.contrib.auth import authenticate, login as django_login, logout from django.db.models import FieldDoesNotExist +from django.db import IntegrityError +from django.template.loader import render_to_string +from django.core import signing +from django.views.generic.edit import FormView +from django.views.generic.base import TemplateView, View - -from registration.views import RegistrationView as BaseRegistrationView -from registration.models import RegistrationProfile -from registration import signals - +from authentic2.utils import get_form_class from .. import models, app_settings, compat -from . import urls - +from .forms import EXPIRATION, registration_id_name logger = logging.getLogger(__name__) - -class RegistrationView(BaseRegistrationView): - form_class = urls.get_form_class(app_settings.A2_REGISTRATION_FORM_CLASS) - - def register(self, request, **cleaned_data): - User = compat.get_user_model() - if Site._meta.installed: - site = Site.objects.get_current() - else: - site = RequestSite(request) - user_fields = {} - for field in compat.get_registration_fields(): - # save User model fields +User = compat.get_user_model() + +def valid_token(method): + def f(obj, *args, **kwargs): + try: + registration_kwargs = signing.loads(kwargs['registration_token'], + max_age=EXPIRATION*3600*24) + params = kwargs.copy() + params.update(registration_kwargs) + except signing.SignatureExpired: + return redirect('registration_activation_expired') + return method(obj, *args, **params) + return f + +def login(request, user, redirect_url='auth_homepage'): + user.backend = settings.AUTHENTICATION_BACKENDS[1] + django_login(request, user) + return redirect(redirect_url) + +class LoginView(View): + redirect_url = 'auth_homepage' + + @valid_token + def get(self, request, *args, **kwargs): + try: + user = User.objects.get(email=kwargs['email'], username=kwargs['username']) + return login(request, user) + except User.DoesNotExist: + return redirect(self.redirect_url) + +class RegistrationView(FormView): + form_class = get_form_class(app_settings.A2_REGISTRATION_FORM_CLASS) + template_name = 'registration/registration_form.html' + + def form_valid(self, form): + form.save(self.request) + return redirect('registration_complete') + +class RegistrationCompletionView(FormView): + form_class = get_form_class(app_settings.A2_REGISTRATION_COMPLETION_FORM_CLASS) + http_method_names = ['get', 'post'] + template_name = 'registration/registration_completion_form.html' + + @valid_token + def get(self, request, *args, **kwargs): + if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE: + print "UNIQUE???" try: - User._meta.get_field(field) - except FieldDoesNotExist: - continue - if field.startswith('password'): - continue - user_fields[field] = cleaned_data[field] - if field == 'email': - user_fields[field] = BaseUserManager.normalize_email(user_fields[field]) - new_user = User(is_active=False, **user_fields) - new_user.clean() - new_user.set_password(cleaned_data['password1']) - new_user.save() - attributes = models.Attribute.objects.filter( - asked_on_registration=True) - if attributes: - for attribute in attributes: - attribute.set_value(new_user, cleaned_data[attribute.name]) - if app_settings.A2_REGISTRATION_GROUPS: - groups = [] - for name in app_settings.A2_REGISTRATION_GROUPS: - group, created = Group.objects.get_or_create(name=name) - groups.append(group) - new_user.groups = groups - registration_profile = RegistrationProfile.objects.create_profile(new_user) - registration_profile.send_activation_email(site) - - signals.user_registered.send(sender=self.__class__, - user=new_user, - request=request) - return new_user - - def registration_allowed(self, request): - """ - Indicate whether account registration is currently permitted, - based on the value of the setting ``REGISTRATION_OPEN``. This - is determined as follows: - - * If ``REGISTRATION_OPEN`` is not specified in settings, or is - set to ``True``, registration is permitted. - - * If ``REGISTRATION_OPEN`` is both specified and set to - ``False``, registration is not permitted. - - """ - return getattr(settings, 'REGISTRATION_OPEN', True) - - def get_success_url(self, request, user): - """ - Return the name of the URL to redirect to after successful - user registration. - - """ - return ('registration_complete', (), {}) - -register = RegistrationView.as_view() + user = User.objects.get(email__iexact=kwargs['email']) + except User.DoesNotExist: + return super(RegistrationCompletionView, self).get(request, *args, **kwargs) + return login(request, user) + else: + user_accounts = User.objects.filter(email__iexact=kwargs['email']) + if user_accounts: + has_active_account = False + for user in user_accounts: + registration_id = kwargs[registration_id_name] + if models.AttributeValue.objects.with_owner(user).filter(attribute__label=registration_id_name, + content='"%s"' % registration_id).exists(): + has_active_account = True + if has_active_account: + logout(request) + context = kwargs.copy() + context.update({'accounts': user_accounts}) + self.template_name = 'registration/login_choices.html' + return self.render_to_response(context) + else: + return super(RegistrationCompletionView, self).get(request, *args, **kwargs) + else: + return super(RegistrationCompletionView, self).get(request, *args, **kwargs) + + @valid_token + def post(self, request, *args, **kwargs): + form = self.get_form(self.form_class) + if form.is_valid(): + params = form.cleaned_data.copy() + params.update(kwargs) + user, next_url = form.save(**params) + if next_url: + return login(request, user, next_url) + return login(request, user) + else: + return self.form_invalid(form) +class DeleteView(TemplateView): + def get(self, request, *args, **kwargs): + next_url = request.build_absolute_uri(request.META.get('HTTP_REFERER')\ + or request.GET.get('next_url')) + if not app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT: + return redirect(next_url) + return render(request, 'registration/delete_account.html') -@login_required -def delete(request, next_url='/'): - next_url = request.build_absolute_uri(request.META.get('HTTP_REFERER') or next_url) - if not app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT: - return redirect(next_url) - if request.method == 'POST': + def post(self, request, *args, **kwargs): + next_url = request.build_absolute_uri(request.META.get('HTTP_REFERER')\ + or request.GET.get('next_url')) if 'submit' in request.POST: models.DeletedUser.objects.delete_user(request.user) logger.info(u'deletion of account %s requested' % request.user) @@ -106,4 +122,3 @@ def delete(request, next_url='/'): return redirect('auth_logout') else: return redirect(next_url) - return render(request, 'registration/delete_account.html') diff --git a/authentic2/settings.py b/authentic2/settings.py index 21cfe4e..78b8bf7 100644 --- a/authentic2/settings.py +++ b/authentic2/settings.py @@ -180,7 +180,6 @@ INSTALLED_APPS = ( 'admin_tools.menu', 'admin_tools.dashboard', 'django.contrib.admin', - 'registration', 'django_select2', 'django_tables2', 'authentic2.nonce', diff --git a/authentic2/templates/auth/login_form.html b/authentic2/templates/auth/login_form.html index c47bc0e..c5f8fb7 100644 --- a/authentic2/templates/auth/login_form.html +++ b/authentic2/templates/auth/login_form.html @@ -15,6 +15,6 @@

→ {% trans "Forgot password?" %} {% trans "Reset it!" %}

{% endif %} {% if registration_authorized %} -

→ {% trans "Not a member?" %} {% trans "Register!" %}

+

→ {% trans "Not a member?" %} {% trans "Register!" %}

{% endif %} diff --git a/authentic2/templates/registration/activate.html b/authentic2/templates/registration/activate.html deleted file mode 100644 index 640352e..0000000 --- a/authentic2/templates/registration/activate.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block title %} -{% trans "Account activation" %} -{% endblock %} - -{% block content %} - -{% if account %} - -

{% trans "Account successfully activated" %}

- -

{% trans "Log in" %}

- -{% else %} - -

{% trans "Account activation failed" %}

- -{% endif %} - -{% endblock %} diff --git a/authentic2/templates/registration/activation_complete.html b/authentic2/templates/registration/activation_complete.html deleted file mode 100644 index 49b05e9..0000000 --- a/authentic2/templates/registration/activation_complete.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block content %} -{% trans "Your account is now activated" %} -{% endblock %} diff --git a/authentic2/templates/registration/activation_email.html b/authentic2/templates/registration/activation_email.html new file mode 100644 index 0000000..ef449d9 --- /dev/null +++ b/authentic2/templates/registration/activation_email.html @@ -0,0 +1,8 @@ +{% load i18n %} +

{% trans "Account creation" %}

+

+{% blocktrans %} +To continue your account creation on {{ site }} please click here +{% endblocktrans %} +

+

{% blocktrans %}This link is valid for {{ expiration_days }} days.{% endblocktrans %}

diff --git a/authentic2/templates/registration/activation_email.txt b/authentic2/templates/registration/activation_email.txt index 911df01..fd984a4 100644 --- a/authentic2/templates/registration/activation_email.txt +++ b/authentic2/templates/registration/activation_email.txt @@ -1,6 +1,6 @@ {% load i18n %} -{% trans "Activate account at" %} {{ site.name }}: +{% trans "Activate account" %}: -http://{{ site.domain }}{% url 'registration_activate' activation_key %} +{{ registration_url }} {% blocktrans %}Link is valid for {{ expiration_days }} days.{% endblocktrans %} diff --git a/authentic2/templates/registration/activation_email_subject.txt b/authentic2/templates/registration/activation_email_subject.txt index 24f477c..7c30228 100644 --- a/authentic2/templates/registration/activation_email_subject.txt +++ b/authentic2/templates/registration/activation_email_subject.txt @@ -1 +1 @@ -{% load i18n %}{% trans "Account activation on" %} {{ site.name }} +{% load i18n %}{% trans "Account activation on" %} {{ site }} diff --git a/authentic2/templates/registration/activation_expired.html b/authentic2/templates/registration/activation_expired.html new file mode 100644 index 0000000..6ab9518 --- /dev/null +++ b/authentic2/templates/registration/activation_expired.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %} +{% trans "Account activation expired" %} +{% endblock %} + +{% block content %} +

{% trans "Account activation" %}

+

{% trans "Your activation key is expired" %}

+{% endblock %} diff --git a/authentic2/templates/registration/login_choices.html b/authentic2/templates/registration/login_choices.html new file mode 100644 index 0000000..a0cf643 --- /dev/null +++ b/authentic2/templates/registration/login_choices.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% load breadcrumbs i18n %} + +{% block title %} +{% trans "Login" %} +{% endblock %} + +{% block breadcrumbs %} +{{ block.super }} +{% breadcrumb_url 'Register' %} +{% endblock %} + + +{% block content %} + +

{% trans "Login" %}

+

{% trans "Multiple accounts are associated to this email." %} +{% trans "Please choose the account you want to log in with:" %} +

+ + +
    + {% for account in accounts %} +
  • {{ account }}
  • + {% endfor %} +
+ +{% endblock %} diff --git a/authentic2/templates/registration/registration_complete.html b/authentic2/templates/registration/registration_complete.html index bbc18d2..b74017b 100644 --- a/authentic2/templates/registration/registration_complete.html +++ b/authentic2/templates/registration/registration_complete.html @@ -6,6 +6,6 @@ {% endblock %} {% block content %} -

{% trans "You are now registered. Activation email sent." %}

+

{% trans "Thank you for registering. Activation email sent." %}

{% trans "Back" %}

{% endblock %} diff --git a/authentic2/templates/registration/registration_completion_form.html b/authentic2/templates/registration/registration_completion_form.html new file mode 100644 index 0000000..1156c42 --- /dev/null +++ b/authentic2/templates/registration/registration_completion_form.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %} +{% trans "Registration" %} +{% endblock %} + +{% load breadcrumbs %} +{% block breadcrumbs %} +{{ block.super }} +{% breadcrumb_url 'Register' %} +{% endblock %} + +{% block content %} + +

{% trans "Registration" %}

+

{% trans "Please fill the formm to complete your registration" %}

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/authentic2/templates/registration/registration_form.html b/authentic2/templates/registration/registration_form.html index f0ea73c..842a1dc 100644 --- a/authentic2/templates/registration/registration_form.html +++ b/authentic2/templates/registration/registration_form.html @@ -15,7 +15,7 @@

{% trans "Registration" %}

-
+ {% csrf_token %} {{ form.as_p }} diff --git a/authentic2/tests.py b/authentic2/tests.py index 89c4ce8..5073feb 100644 --- a/authentic2/tests.py +++ b/authentic2/tests.py @@ -1,6 +1,12 @@ -from django.test import TestCase +import re +from django.core import mail +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.test.client import Client +from django.test.utils import override_settings from django.contrib.auth.hashers import check_password +from django.conf import settings from . import hashers @@ -88,3 +94,36 @@ class SerializerTests(TestCase): self.assertEqual(User.objects.count(), 1) self.assertEqual(Attribute.objects.count(), 1) self.assertEqual(AttributeValue.objects.count(), 1) + +class RegistrationTests(TestCase): + def setUp(self): + self.client = Client() + + def test_registration(self): + response = self.client.post(reverse('registration_register'), + {'email': 'testbot@entrouvert.com'}) + self.assertRedirects(response, reverse('registration_complete')) + self.assertEqual(len(mail.outbox), 1) + links = re.findall('http[s]://.*/', mail.outbox[0].body) + self.assertIsInstance(links, list) and self.assertIsNot(links, []) + link = links[0] + completion = self.client.get(link) + self.assertEqual(completion.status_code, 200) + self.bad_password_test(link) + self.good_password_test(link) + mail.outbox = [] + + def bad_password_test(self, url): + """ + test short filled password + """ + completion = self.client.post(url, {'username': 'toto', + 'password1': 'toto', + 'password2': 'toto'}) + self.assertEqual(completion.status_code, 200) + + def good_password_test(self, url): + completion = self.client.post(url, {'username': 'toto', + 'password1': 'T0toto', + 'password2': 'T0toto'}) + self.assertEqual(completion.status_code, 302) diff --git a/authentic2/utils.py b/authentic2/utils.py index 6678ab2..115ca3b 100644 --- a/authentic2/utils.py +++ b/authentic2/utils.py @@ -206,3 +206,8 @@ def field_names(list_of_field_name_and_titles): yield t else: yield t[0] + +def get_form_class(form_class): + module, form_class = form_class.rsplit('.', 1) + module = import_module(module) + return getattr(module, form_class) diff --git a/diagnose.py b/diagnose.py index 686f11f..e926442 100644 --- a/diagnose.py +++ b/diagnose.py @@ -19,7 +19,3 @@ except ImportError: raise print 'django_authopenid is missing: easy_install django-authopenid' -try: - import registration -except ImportError: - print 'registration is missing: easy_install django-registration' diff --git a/requirements.txt b/requirements.txt index 1f322bc..3c278ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ Django<1.6 south>=0.8.4 requests django-model-utils -django-registration>=1 django-debug-toolbar>=1.2,<1.3 --allow-external django-admin-tools --allow-unverified django-admin-tools diff --git a/setup.py b/setup.py index 56f3a28..ee38a22 100755 --- a/setup.py +++ b/setup.py @@ -117,7 +117,6 @@ setup(name="authentic2", 'south>=0.8.4', 'requests', 'django-model-utils', - 'django-registration>=1', 'django-admin-tools>=0.5.1', 'dnspython', 'django-select2', -- 2.1.4