James Bennett avatar James Bennett committed 7cd71a6

Complete rewrite to use class-based views, with full Django 1.5 support.

Comments (0)

Files changed (27)

docs/backend-api.rst

-.. _backend-api:
-
-User registration backends
-==========================
-
-At its core, django-registration is built around the idea of pluggable
-backends which can implement different workflows for user
-registration. Although :ref:`the default backend <default-backend>`
-uses a common two-phase system (registration followed by activation),
-backends are generally free to implement any workflow desired by their
-authors.
-
-This is deliberately meant to be complementary to Django's own
-`pluggable authentication backends
-<http://docs.djangoproject.com/en/dev/topics/auth/#other-authentication-sources>`_;
-a site which uses an OpenID authentication backend, for example, can
-and should make use of a registration backend which handles signups
-via OpenID. And, like a Django authentication backend, a registration
-backend is simply a class which implements a particular standard API
-(described below).
-
-This allows for a great deal of flexibility in the actual workflow of
-registration; backends can, for example, implement any of the
-following (not an exhaustive list):
-
-* One-step (register, and done) or multi-step (register and activate)
-  signup.
-
-* Invitation-based registration.
-
-* Selectively allowing or disallowing registration (e.g., by requiring
-  particular credentials to register).
-
-* Enabling/disabling registration entirely.
-
-* Registering via sources other than a standard username/password,
-  such as OpenID.
-
-* Selective customization of the registration process (e.g., using
-  different forms or imposing different requirements for different
-  types of users).
-
-
-Specifying the backend to use
------------------------------
-
-To determine which backend to use, the :ref:`views in
-django-registration <views>` accept a keyword argument ``backend``; in
-all cases, this should be a string containing the full dotted Python
-import path to the backend class to be used. So, for example, to use
-the default backend, you'd pass the string
-``'registration.backends.default.DefaultBackend'`` as the value of the
-``backend`` argument (and the default URLconf included with that
-backend does so). The specified backend class will then be imported
-and instantiated (by calling its constructor with no arguments), and
-the resulting instance will be used for all backend-specific
-functionality.
-
-If the specified backend class cannot be imported, django-registration
-will raise ``django.core.exceptions.ImproperlyConfigured``.
-
-
-Backend API
------------
-
-To be used as a registration backend, a class must implement the
-following methods. For many cases, subclassing the default backend and
-selectively overriding behavior will be suitable, but for other
-situations (e.g., workflows significantly different from the default)
-a full implementation is needed.
-
-
-register(request, \*\*kwargs)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This method implements the logic of actually creating the new user
-account. Often, but not necessarily always, this will involve creating
-an instance of ``django.contrib.auth.models.User`` from the supplied
-data.
-
-This method will only be called after a signup form has been
-displayed, and the data collected by the form has been properly
-validated.
-
-Arguments to this method are:
-
-``request``
-    The Django `HttpRequest
-    <http://docs.djangoproject.com/en/dev/ref/request-response/#httprequest-objects>`_
-    object in which a new user is attempting to register.
-
-``**kwargs``
-    A dictionary of the ``cleaned_data`` from the signup form.
-
-After creating the new user account, this method should create or
-obtain an instance of ``django.contrib.auth.models.User`` representing
-that account. It should then send the signal
-:data:`registration.signals.user_registered`, with three arguments:
-
-``sender``
-    The backend class (e.g., ``self.__class__``).
-
-``user``
-    The ``User`` instance representing the new account.
-
-``request``
-    The ``HttpRequest`` in which the user registered.
-
-Finally, this method should return the ``User`` instance.
-
-
-activate(request, \*\*kwargs)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-For workflows which require a separate activation step, this method
-should implement the necessary logic for account activation.
-
-Arguments to this method are:
-
-``request``
-    The Django ``HttpRequest`` object in which the account is being
-    activated.
-
-``**kwargs``
-    A dictionary of any additional arguments (e.g., information
-    captured from the URL, such as an activation key) received by the
-    :func:`~registration.views.activate` view. The combination of the
-    ``HttpRequest`` and this additional information must be sufficient
-    to identify the account which will be activated.
-
-If the account cannot be successfully activated (for example, in the
-default backend if the activation period has expired), this method
-should return ``False``.
-
-If the account is successfully activated, this method should create or
-obtain an instance of ``django.contrib.auth.models.User`` representing
-the activated account. It should then send the signal
-:data:`registration.signals.user_activated`, with three arguments:
-
-``sender``
-    The backend class.
-
-``user``
-    The ``User`` instance representing the activated account.
-
-``request``
-    The ``HttpRequest`` in which the user activated.
-
-This method should then return the ``User`` instance.
-
-For workflows which do not require a separate activation step, this
-method can and should raise ``NotImplementedError``.
-
-
-registration_allowed(request)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This method returns a boolean value indicating whether the given
-``HttpRequest`` is permitted to register a new account (``True`` if
-registration is permitted, ``False`` otherwise). It may determine this
-based on some aspect of the ``HttpRequest`` (e.g., the presence or
-absence of an invitation code in the URL), based on a setting (in the
-default backend, a setting can be used to disable registration),
-information in the database or any other information it can access.
-
-Arguments to this method are:
-
-``request``
-    The Django ``HttpRequest`` object in which a new user is
-    attempting to register.
-
-If this method returns ``False``, the
-:func:`~registration.views.register` view will not display a form for
-account creation; instead, it will issue a redirect to a URL
-explaining that registration is not permitted.
-
-
-get_form_class(request)
-~~~~~~~~~~~~~~~~~~~~~~~
-
-This method should return a form class -- a subclass of
-``django.forms.Form`` -- suitable for use in registering users with
-this backend. As such, it should collect and validate any information
-required by the backend's ``register`` method.
-
-Arguments to this method are:
-
-``request``
-    The Django ``HttpRequest`` object in which a new user is
-    attempting to register.
-
-
-post_registration_redirect(request, user)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This method should return a location to which the user will be
-redirected after successful registration. This should be a tuple of
-``(to, args, kwargs)``, suitable for use as the arguments to `Django's
-"redirect" shortcut
-<http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#redirect>`_.
-
-Arguments to this method are:
-
-``request``
-    The Django ``HttpRequest`` object in which the user registered.
-
-``user``
-    The ``User`` instance representing the new user account.
-
-
-post_activation_redirect(request, user)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-For workflows which require a separate activation step, this method
-should return a location to which the user will be redirected after
-successful activation.  This should be a tuple of ``(to, args,
-kwargs)``, suitable for use as the arguments to `Django's "redirect"
-shortcut
-<http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#redirect>`_.
-
-Arguments to this method are:
-
-``request``
-    The Django ``HttpRequest`` object in which the user activated.
-
-``user``
-    The ``User`` instance representing the activated user account.
-
-For workflows which do not require a separate activation step, this
-method can and should raise ``NotImplementedError``.
 # built documents.
 #
 # The short X.Y version.
-version = '0.8'
+version = '0.9'
 # The full version, including alpha/beta/rc tags.
-release = '0.8'
+release = '0.9'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

docs/default-backend.rst

 The default backend
 ===================
 
-A default :ref:`registration backend <backend-api>` is bundled with
-django-registration, as the class
-``registration.backends.default.DefaultBackend``, and implements a
+A default registration backend` is bundled with django-registration,
+as the module ``registration.backends.default``, and implements a
 simple two-step workflow in which a new user first registers, then
 confirms and activates the new account by following a link sent to the
 email address supplied during registration.
 Default behavior and configuration
 ----------------------------------
 
+To make use of this backend, simply include the URLConf
+``registration.backends.default.urls`` at whatever location you choose
+in your URL hierarchy.
+
 This backend makes use of the following settings:
 
 ``ACCOUNT_ACTIVATION_DAYS``
 argument ``form_class`` to the :func:`~registration.views.register`
 view.
 
+Two views are provided:
+``registration.backends.default.views.RegistrationView`` and
+``registration.backends.default.views.ActivationView``. These views
+subclass django-registration's base
+:class:`~registration.views.RegistrationView` and
+:class:`~registration.views.ActivationView`, respectively, and
+implement the two-step registration/activation process.
+
 Upon successful registration -- not activation -- the default redirect
 is to the URL pattern named ``registration_complete``; this can be
-overridden by passing the keyword argument ``success_url`` to the
-:func:`~registration.views.register` view.
+overridden in subclasses by changing
+:attr:`~registration.views.RegistrationView.success_url` or
+implementing
+:meth:`~registration.views.RegistrationView.get_success_url()`
 
 Upon successful activation, the default redirect is to the URL pattern
-named ``registration_activation_complete``; this can be overridden by
-passing the keyword argument ``success_url`` to the
-:func:`~registration.views.activate` view.
+named ``registration_activation_complete``; this can be overridden in
+subclasses by implementing
+:meth:`~registration.views.ActivationView.get_success_url()`.
 
 
 How account data is stored for activation
 -------------
 
 **Do I need to rewrite the views to change the way they behave?**
-    No. There are several ways you can customize behavior without
-    making any changes whatsoever:
 
-    * Pass custom arguments -- e.g., to specify forms, template names,
-      etc. -- to :ref:`the registration views <views>`.
+    Not always. Any behavior controlled by an attribute on a
+    class-based view can be changed by passing a different value for
+    that attribute in the URLConf. See `Django's class-based view
+    documentation
+    <https://docs.djangoproject.com/en/1.5/topics/class-based-views/#simple-usage-in-your-urlconf>`_
+    for examples of this.
 
-    * Use the :ref:`signals <signals>` sent by the views to add custom
-      behavior.
-
-    * Write a custom :ref:`registration backend <backend-api>` which
-      implements the behavior you need, and have the views use your
-      backend.
-
-    If none of these are sufficient, your best option is likely to
-    simply write your own views; however, it is hoped that the level
-    of customization exposed by these options will be sufficient for
-    nearly all user-registration workflows.
-
-**How do I pass custom arguments to the views?**
-    Part 3 of the official Django tutorial, when it `introduces
-    generic views
-    <http://docs.djangoproject.com/en/dev/intro/tutorial04/#use-generic-views-less-code-is-better>`_,
-    covers the necessary mechanism: simply provide a dictionary of
-    keyword arguments in your URLconf.
-
-**Does that mean I should rewrite django-registration's default URLconf?**
-    No; if you'd like to pass custom arguments to the registration
-    views, simply write and include your own URLconf instead of
-    including the default one provided with django-registration.
-
+    For more complex or fine-grained control, you will likely want to
+    subclass :class:`~registration.views.RegistrationView` or
+    :class:`~registration.views.ActivationView`, or both, add your
+    custom logic to your subclasses, and then create a URLConf which
+    makes use of your subclasses.
+    
 **I don't want to write my own URLconf because I don't want to write patterns for all the auth views!**
     You're in luck, then; django-registration provides a URLconf which
     *only* contains the patterns for the auth views, and which you can
     In that case, you should feel free to set up your own URLconf
     which uses the names you want.
 
+**I'm using Django 1.5 and a custom user model; how do I make that work?**
+    Although the two built-in backends supplied with
+    django-registration both assume Django's default ``User`` model,
+    :ref:`the base view classes <views>` are deliberately
+    user-model-agnostic. Simply subclass them, and implement logic for
+    your custom user model.
+
 
 Troubleshooting
 ---------------
 ---------------
 
 **How do I log a user in immediately after registration or activation?**
-    You can most likely do this simply by writing a function which
-    listens for the appropriate :ref:`signal <signals>`; your function
-    should set the ``backend`` attribute of the user to the correct
-    authentication backend, and then call
-    ``django.contrib.auth.login()`` to log the user in.
+    Take a look at the implementation of :ref:`the simple backend
+    <simple-backend>`, which logs a user in immediately after
+    registration.
+
 
 **How do I re-send an activation email?**
     Assuming you're using :ref:`the default backend
 django-registration 0.8 documentation
 =====================================
 
-This documentation covers the 0.8 release of django-registration, a
-simple but extensible application providing user registration
-functionality for `Django <http://www.djangoproject.com>`_-powered
-websites.
+This documentation covers the |version| release of
+django-registration, a simple but extensible application providing
+user registration functionality for `Django
+<http://www.djangoproject.com>`_-powered websites.
 
 Although nearly all aspects of the registration process are
-customizable, the default setup of django-registration attempts to
-cover the most common use case: two-phase registration, consisting of
-initial signup followed by a confirmation email which contains
-instructions for activating the new account.
+customizable, out-of-the-box support is provided for two common use
+cases:
+
+* Two-phase registration, consisting of initial signup followed by a
+  confirmation email with instructions for activating the new account.
+
+* One-phase registration, where a user signs up and is immediately
+  active and logged in.
 
 To get up and running quickly, consult the :ref:`quick-start guide
 <quickstart>`, which describes all the necessary steps to install
    quickstart
    release-notes
    upgrade
-   backend-api
+   api
    default-backend
    simple-backend
    forms
      <http://docs.djangoproject.com/en/dev/topics/auth/>`_; Django's
      authentication system is used by django-registration's default
      configuration.
-
-   * `django-profiles
-     <http://bitbucket.org/ubernostrum/django-profiles/>`_, an
-     application which provides simple user-profile management.

docs/quickstart.rst

 
 Before installing django-registration, you'll need to have a copy of
 `Django <http://www.djangoproject.com>`_ already installed. For the
-|version| release, Django 1.1 or newer is required.
+|version| release, Django 1.4 or newer is required.
 
 For further information, consult the `Django download page
 <http://www.djangoproject.com/download/>`_, which offers convenient
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Several automatic package-installation tools are available for Python;
-the most popular are `easy_install
-<http://peak.telecommunity.com/DevCenter/EasyInstall>`_ and `pip
-<http://pip.openplans.org/>`_. Either can be used to install
-django-registration.
-
-Using ``easy_install``, type::
-
-    easy_install -Z django-registration
-
-Note that the ``-Z`` flag is required, to tell ``easy_install`` not to
-create a zipped package; zipped packages prevent certain features of
-Django from working properly.
+the recommended one is `pip <http://pip.openplans.org/>`_.
 
 Using ``pip``, type::
 
 
 Once you've downloaded the package, unpack it (on most operating
 systems, simply double-click; alternately, type ``tar zxvf
-django-registration-0.8.tar.gz`` at a command line on Linux, Mac OS X
+django-registration-0.9.tar.gz`` at a command line on Linux, Mac OS X
 or other Unix-like systems). This will create the directory
-``django-registration-0.8``, which contains the ``setup.py``
+``django-registration-0.9``, which contains the ``setup.py``
 installation script. From a command line in that directory, type::
 
     python setup.py install
 You can also obtain a copy of a particular release of
 django-registration by specifying the ``-r`` argument to ``hg clone``;
 each release is given a tag of the form ``vX.Y``, where "X.Y" is the
-release number. So, for example, to check out a copy of the 0.8
+release number. So, for example, to check out a copy of the |version|
 release, type::
 
-    hg clone -r v0.8 http://bitbucket.org/ubernostrum/django-registration/
+    hg clone -r v|version| http://bitbucket.org/ubernostrum/django-registration/
 
 In either case, this will create a copy of the django-registration
 Mercurial repository on your computer; you can then add the
 ``/accounts/register/``, login (once activated) at
 ``/accounts/login/``, etc.
 
+Another ``URLConf`` is also provided -- at ``registration.auth_urls``
+-- which just handles the Django auth views, should you want to put
+those at a different location.
+
 
 Required templates
 ~~~~~~~~~~~~~~~~~~
 up for you by the default URLconf mentioned above), you will also need
 to create the templates required by those views. Consult `the
 documentation for Django's authentication system
-<http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.login>`_
-for details regarding these templates.
+<http://docs.djangoproject.com/en/dev/topics/auth/>`_ for details
+regarding these templates.

docs/release-notes.rst

 =============
 
 The |version| release of django-registration represents a complete
-rewrite of the previous codebase, and as such introduces a number of
-new features and greatly enhances the flexibility and customizability
-of django-registration. This document summarizes those features; for a
-list of changes which impact existing installations, consult :ref:`the
-upgrade guide <upgrade>`.
+rewrite of the previous codebase. For information on upgrading,
+consult :ref:`the upgrade guide <upgrade>`.
 
 
 The backend system
 ------------------
 
-The largest overall change consists of factoring out the logic of user
-registration into pluggable/swappable backend classes. The
-:ref:`registration views <views>` now accept a (required) argument,
-``backend``, which indicates the backend class to use, and that class
-has full control over the registration (and, if needed, activation)
-process, including:
+The largest overall change is that in place of the monolithic backend
+classes and function-based views found in django-registration 0.8, in
+|version| all views are class-based. A "backend" now consists of,
+typically, one or two subclasses of :ref:`the built-in base views
+<views>`.
 
-* Determining whether registration will be allowed at all, on a
-  per-request basis.
+Implementing these as class-based views allows for far simpler
+configuration and customization, without the overhead involved in
+supporting large numbers of optional keyword arguments to
+function-based views, or the need to provide a separate class-based
+infrastructure for implementing the logic of registration.
 
-* Specifying a form class to use for account registration.
-
-* Implementing the actual process of account creation.
-
-* Determining whether a separate activation step is needed, and if so
-  what it will entail.
-
-* Specifying actions to take (e.g., redirects, automatic login, etc.)
-  following successful registration or activation.
-
-For full details, see the documentation for :ref:`the backend API
-<backend-api>`.
-
-The workflow used by previous releases of django-registration
-(two-step registration/activation) has been implemented using this
-system, and is shipped as :ref:`the default backend <default-backend>`
-in django-registration |version|.
-
-
-Other new features
-------------------
-
-An alternate :ref:`one-step registration system <simple-backend>` is
-provided, for use by sites which do not require a two-step
-registration/activation system.
-
-During the registration and (optional) activation process,
-:ref:`custom signals <signals>` are now sent, allowing easy injection
-of custom processing into the registration workflow without needing to
-write a full backend.
-
-The default backend now supplies several `custom admin actions
-<http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/>`_ to
-make the process of administering a site with django-registration
-simpler.
-
-The :func:`~registration.views.activate` view now supplies any
-captured keyword arguments from the URL (in the case of the default
-backend, this is the activation key) to its template in case of
-unsuccessful activation; this greatly simplifies the process of
-determining why activation failed and displaying appropriate error
-messages.
-
+Notably, this implementation is also completely backwards-compatible
+for users of django-registration 0.8 who simply used the recommended
+default URLConf for one of the supplied backends; those URLConfs exist
+in the same locations, and have been rewritten to point to the
+appropriate class-based views with the appropriate options.

docs/simple-backend.rst

 ===============================
 
 As an alternative to :ref:`the default backend <default-backend>`, and
-an example of writing :ref:`registration backends <backend-api>`,
-django-registration bundles a one-step registration system in
+an example of writing alternate workflows, django-registration bundles
+a one-step registration system in
 ``registration.backend.simple``. This backend's workflow is
 deliberately as simple as possible:
 
 Upon successful registration, the default redirect is to the URL
 specified by the ``get_absolute_url()`` method of the newly-created
 ``User`` object; by default, this will be ``/users/<username>/``,
-although it can be overridden in either of two ways:
-
-1. Specify a custom URL pattern for the
-   :func:`~registration.views.register` view, passing the keyword
-   argument ``success_url``.
-
-2. Override the default ``get_absolute_url()`` of the ``User`` model
-   in your Django configuration, as covered in `Django's settings
-   documentation
-   <http://docs.djangoproject.com/en/dev/ref/settings/#absolute-url-overrides>`_.
+although it can be overridden by implementing
+:meth:`~registration.views.RegistrationView.get_success_url()` on a
+subclass of ``registration.backends.simple.views.RegistrationView``.
 
 The default form class used for account registration will be
 :class:`registration.forms.RegistrationForm`, although this can be
-overridden by supplying a custom URL pattern for the ``register()``
-view and passing the keyword argument ``form_class``.
-
-Note that because this backend does not use an activation step,
-attempting to use the :func:`~registration.views.activate` view with
-this backend or calling the backend's ``activate()`` or
-``post_activation_redirect()`` methods will raise
-``NotImplementedError``.
+overridden by supplying a custom URL pattern for the registration view
+and passing the keyword argument ``form_class``, or by subclassing
+``registration.backends.simple.views.RegistrationView`` and either
+overriding ``form_class`` or implementing
+:meth:`~registration.views.RegistrationView.get_form_class()`.
 Django version requirement
 --------------------------
 
-As of |version|, django-registration requires Django 1.3 or newer;
+As of |version|, django-registration requires Django 1.4 or newer;
 older Django releases may work, but are officially unsupported.
 
 
 
 If you're upgrading from an older release of django-registration, and
 if you were using the default setup (i.e., the included default
-URLconf and no custom URL patterns or custom arguments to views), most
-things will continue to work as normal (although you will need to
-create one new template; see the section on views below). However, the
-old default URLconf has been deprecated and will be removed in version
-1.0 of django-registration, so it is recommended that you begin
-migrating now. To do so, change any use of ``registration.urls`` to
-``registration.backends.default.urls``. For example, if you had the
-following in your root URLconf::
+URLconf and no custom URL patterns or custom arguments to views), you
+do not need to make any chances.
 
-    (r'^accounts/', include('registration.urls')),
-
-you should change it to::
-
-    (r'^accounts/', include('registration.backends.default.urls')),
-
-The older include will continue to work until django-registration 1.0;
-in |version| it raises a ``PendingDeprecationWarning`` (which is
-ignored by default in Python), in 0.9 it will raise
-``DeprecationWarning`` (which will begin printing warning messages on
-import) and in 1.0 it will be removed entirely.
-
-
-Changes to registration views
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:ref:`The views used to handle user registration <views>` have changed
-significantly as of django-registration |version|. Both views now
-require the keyword argument ``backend``, which specifies the
-:ref:`registration backend <backend-api>` to use, and so any URL
-pattern for these views must supply that argument. The URLconf
-provided with :ref:`the default backend <default-backend>` properly
-passes this argument.
-
-The ``profile_callback`` argument of the
-:func:`~registration.views.register` view has been removed; the
-functionality it provided can now be implemented easily via a custom
-backend, or by connecting listeners to :ref:`the signals sent during
-the registration process <signals>`.
-
-The :func:`~registration.views.activate` view now issues a redirect
-upon successful activation; in the default backend this is to the URL
-pattern named ``registration_activation_complete``; in the default
-setup, this will redirect to a view which renders the template
-``registration/activation_complete.html``, and so this template should
-be present when using the default backend and default
-configuration. Other backends can specify the location to redirect to
-through their ``post_activation_redirect()`` method, and this can be
-overridden on a case-by-case basis by passing the (new) keyword
-argument ``success_url`` to the ``activate()`` view. On unsuccessful
-activation, the ``activate()`` view still displays the same template,
-but its context has changed: the context will simply consist of any
-keyword arguments captured in the URL and passed to the view.
-
-
-Changes to registration forms
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Previously, the form used to collect data during registration was
-expected to implement a ``save()`` method which would create the new
-user account. This is no longer the case; creating the account is
-handled by the backend, and so any custom logic should be moved into a
-custom backend, or by connecting listeners to :ref:`the signals sent
-during the registration process <signals>`.
-
-
-Changes to the :class:`~registration.models.RegistrationProfile` model
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The
-:meth:`~registration.models.RegistrationManager.create_inactive_user`
-method of :class:`~registration.models.RegistrationManager` now has an
-additional required argument: ``site``. This allows
-django-registration to easily be used regardless of whether
-``django.contrib.sites`` is installed, since a ``RequestSite`` object
-can be passed in place of a regular ``Site`` object.
-
-The :data:`~registration.signals.user_registered` signal is no longer
-sent by ``create_inactive_user()``, and the
-:data:`~registration.signals.user_activated` signal is no longer sent
-by :meth:`~registration.models.RegistrationManager.activate_user`;
-these signals are now sent by the backend after these methods have
-been called. Note that :ref:`these signals <signals>` were added after
-the django-registration 0.7 release but before the refactoring which
-introduced :ref:`the backend API <backend-api>`, so only installations
-which were tracking the in-development codebase will have made use of
-them.
-
-The sending of activation emails has been factored out of
-``create_inactive_user()``, and now exists as the method
-:meth:`~registration.models.RegistrationProfile.send_activation_email`
-on instances of ``RegistrationProfile``.
+If you had customized django-registration by writing your own backend,
+you will now need to implement that backend by subclassing :ref:`the
+built-in views <views>` and overriding or implementing your
+customizations appropriately. Much of this is similar to previous
+backend class implementations, so minimal changes to existing code
+should be required.
 Registration views
 ==================
 
-In order to allow users to register using whatever workflow is
-implemented by the :ref:`registration backend <backend-api>` in use,
-django-registration provides two views. Both are designed to allow
-easy configurability without writing or rewriting view code.
+In order to allow the utmost flexibility in customizing and supporting
+different workflows, django-registration makes use of Django's support
+for `class-based views
+<https://docs.djangoproject.com/en/dev/topics/class-based-views/>`_. Included
+in django-registration are two base classes which can be subclassed to
+implement whatever workflow is required.
 
-.. function:: activate(request, backend[, template_name[, success_url[, extra_context[, **kwargs]]]])
+.. class:: RegistrationView
 
-   Activate a user's account, for workflows which require a separate
-   activation step.
+   A subclass of Django's `FormView
+   <https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-editing/#formview>`_,
+   which provides the infrastructure for supporting user registration.
 
-   The actual activation of the account will be delegated to the
-   backend specified by the ``backend`` keyword argument; the
-   backend's ``activate()`` method will be called, passing the
-   ``HttpRequest`` and any keyword arguments captured from the URL,
-   and will be assumed to return a ``User`` if activation was
-   successful, or a value which evaluates to ``False`` in boolean
-   context if not.
+   Since it's a subclass of ``FormView``, ``RegistrationView`` has all
+   the usual attributes and methods you can override; however, there
+   is one key difference. In order to support additional
+   customization, ``RegistrationView`` also passes the ``HttpRequest``
+   to most of its methods. Subclasses do need to take this into
+   account, and accept the ``request`` argument.
 
-   Upon successful activation, the backend's
-   ``post_activation_redirect()`` method will be called, passing the
-   ``HttpRequest`` and the activated ``User`` to determine the URL to
-   redirect the user to. To override this, pass the argument
-   ``success_url`` (see below).
+   Useful places to override or customize on a ``RegistrationView``
+   subclass are:
 
-   On unsuccessful activation, will render the template
-   ``registration/activate.html`` to display an error message; to
-   override thise, pass the argument ``template_name`` (see below).
+   .. attribute:: disallowed_url
 
-   **Context**
+      The URL to redirect to when registration is disallowed. Should
+      be a string, `the name of a URL pattern
+      <https://docs.djangoproject.com/en/dev/topics/http/urls/#naming-url-patterns>`_. Default
+      value is ``registration_disallowed``.
 
-   The context will be populated from the keyword arguments captured
-   in the URL. This view uses ``RequestContext``, so variables
-   populated by context processors will also be present in the
-   context.
+   .. attribute:: form_class
 
-   :param backend: The dotted Python path to the backend class to use.
-   :type backend: string
-   :param extra_context: Optionally, variables to add to the template
-      context. Any callable object in this dictionary will be called
-      to produce the final result which appears in the context.
-   :type extra_context: dict
-   :param template_name: Optional. A custom template name to use. If
-      not specified, this will default to
-      ``registration/activate.html``.
-   :type template_name: string
-   :param **kwargs: Any keyword arguments captured from the URL, such
-      as an activation key, which will be passed to the backend's
-      ``activate()`` method.
+      The form class to use for user registration. Can be overridden
+      on a per-request basis (see below). Should be the actual class
+      object; by default, this class is
+      :class:`registration.forms.RegistrationForm`.
 
+   .. attribute:: success_url
 
-.. function:: register(request, backend[, success_url[, form_class[, disallowed_url[, template_name[, extra_context]]]]])
+      The URL to redirect to after successful registration. Should be
+      a string, the name of a URL pattern, or a 3-tuple of arguments
+      suitable for passing to Django's `redirect shortcut
+      <https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#redirect>`. Can
+      be overridden on a per-request basis (see below). Default value
+      is ``None``, so that per-request customization is used instead.
 
-   Allow a new user to register an account.
+   .. attribute:: template_name
 
-   The actual registration of the account will be delegated to the
-   backend specified by the ``backend`` keyword argument. The backend
-   is used as follows:
+      The template to use for user registration. Should be a
+      string. Default value is
+      ``registration/registration_form.html``.
 
-   1. The backend's ``registration_allowed()`` method will be called,
-      passing the ``HttpRequest``, to determine whether registration
-      of an account is to be allowed; if not, a redirect is issued to
-      a page indicating that registration is not permitted.
+   .. method:: get_form_class(request)
 
-   2. The form to use for account registration will be obtained by
-      calling the backend's ``get_form_class()`` method, passing the
-      ``HttpRequest``. To override this, pass the keyword argument
-      ``form_class``.
+      Select a form class to use on a per-request basis. If not
+      overridden, will use :attr:`~form_class`. Should be the actual
+      class object.
 
-   3. If valid, the form's ``cleaned_data`` will be passed (as keyword
-      arguments, and along with the ``HttpRequest``) to the backend's
-      ``register()`` method, which should return a ``User`` object
-      representing the new account.
+   .. method:: get_success_url(request, user)
 
-   4. Upon successful registration, the backend's
-      ``post_registration_redirect()`` method will be called, passing
-      the ``HttpRequest`` and the new ``User``, to determine the URL
-      to redirect to. To override this, pass the keyword argument
-      ``success_url``.
+      Return a URL to redirect to after successful registration, on a
+      per-request or per-user basis. If not overridden, will use
+      :attr:`~success_url`. Should be a string, the name of a URL
+      pattern, or a 3-tuple of arguments suitable for passing to
+      Django's ``redirect`` shortcut.
 
-   **Context**
+   .. method:: registration_allowed(request)
 
-   ``form``
-        The form instance being used to collect registration data.
+      Should return a boolean indicating whether user registration is
+      allowed, either in general or for this specific request.
 
-   This view uses ``RequestContext``, so variables populated by
-   context processors will also be present in the context.
+   .. method:: register(request, **cleaned_data)
 
-   :param backend: The dotted Python path to the backend class to use.
-   :type backend: string
-   :param disallowed_url: The URL to redirect to if registration is
-      not permitted (e.g., if registration is closed). This should be
-      a string suitable for passing as the ``to`` argument to
-      `Django's "redirect" shortcut
-      <http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#redirect>`_. If
-      not specified, this will default to ``registration_disallowed``.
-   :type disallowed_url: string
-   :param extra_context: Optionally, variables to add to the template
-      context. Any callable object in this dictionary will be called
-      to produce the final result which appears in the context.
-   :type extra_context: dict
-   :param form_class: The form class to use for registration; this
-      should be some subclass of ``django.forms.Form``. If not
-      specified, the backend's ``get_form_class()`` method will be
-      called to obtain the form class.
-   :type form_class: subclass of ``django.forms.Form``
-   :param success_url: The URL to redirect to after successful
-      registration. This should be a string suitable for passing as
-      the ``to`` argument to `Django's "redirect" shortcut
-      <http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#redirect>`_. If
-      not specified, the backend's ``post_registration_redirect()``
-      method will be called to obtain the URL.
-   :type success_url: string
-   :param template_name: Optional. A custom template name to use. If
-      not specified, this will default to
-      ``registration/registration_form.html``.
-   :type template_name: string
+      Actually perform the business of registering a new
+      user. Receives both the ``HttpRequest`` object and all of the
+      ``cleaned_data`` from the registration form. Should return the
+      new user who was just registered.
+
+
+.. class:: ActivationView
+
+   A subclass of Django's `TemplateView
+   <https://docs.djangoproject.com/en/1.5/ref/class-based-views/base/#templateview>`_
+   which provides support for a separate account-activation step, in
+   workflows which require that.
+
+   Useful places to override or customize on an ``ActivationView``
+   subclass are:
+
+   .. attribute:: template_name
+
+      The template to use for user activation. Should be a
+      string. Default value is ``registration/activate.html``.
+
+   .. method:: activate(request, *args, **kwargs)
+
+      Actually perform the business of activating a user
+      account. Receives the ``HttpRequest`` object and any positional
+      or keyword arguments passed to the view. Should return the
+      activated user account if activation is successful, or any value
+      which evaluates ``False`` in boolean context if activation is
+      unsuccessful.
+
+   .. method:: get_success_url(request, user)
+
+      Return a URL to redirect to after successful registration, on a
+      per-request or per-user basis. If not overridden, will use
+      :attr:`~success_url`. Should be a string, the name of a URL
+      pattern, or a 3-tuple of arguments suitable for passing to
+      Django's ``redirect`` shortcut.

registration/__init__.py

-VERSION = (0, 8, 0, 'final', 0)
+from django.utils.version import get_version as django_get_version
 
-def get_version(version=None):
-    """Derives a PEP386-compliant version number from VERSION."""
-    if version is None:
-        version = VERSION
-    assert len(version) == 5
-    assert version[3] in ('alpha', 'beta', 'rc', 'final')
 
-    # Now build the two parts of the version number:
-    # main = X.Y[.Z]
-    # sub = .devN - for pre-alpha releases
-    #     | {a|b|c}N - for alpha, beta and rc releases
+VERSION = (0, 9, 0, 'beta', 1)
 
-    parts = 2 if version[2] == 0 else 3
-    main = '.'.join(str(x) for x in version[:parts])
 
-    sub = ''
-    if version[3] == 'alpha' and version[4] == 0:
-        # At the toplevel, this would cause an import loop.
-        from django.utils.version import get_svn_revision
-        svn_revision = get_svn_revision()[4:]
-        if svn_revision != 'unknown':
-            sub = '.dev%s' % svn_revision
-
-    elif version[3] != 'final':
-        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
-        sub = mapping[version[3]] + str(version[4])
-
-    return main + sub
+def get_version():
+    return django_get_version(VERSION) # pragma: no cover

registration/auth_urls.py

 
 """
 
-from django.conf.urls.defaults import *
+from django.conf.urls import include
+from django.conf.urls import patterns
+from django.conf.urls import url
 
 from django.contrib.auth import views as auth_views
 

registration/backends/__init__.py

-from django.core.exceptions import ImproperlyConfigured
-
-
-# Python 2.7 has an importlib with import_module; for older Pythons,
-# Django's bundled copy provides it.
-try: # pragma: no cover
-    from importlib import import_module # pragma: no cover
-except ImportError: # pragma: no cover
-    from django.utils.importlib import import_module # pragma: no cover
-
-def get_backend(path):
-    """
-    Return an instance of a registration backend, given the dotted
-    Python import path (as a string) to the backend class.
-
-    If the backend cannot be located (e.g., because no such module
-    exists, or because the module does not contain a class of the
-    appropriate name), ``django.core.exceptions.ImproperlyConfigured``
-    is raised.
-    
-    """
-    i = path.rfind('.')
-    module, attr = path[:i], path[i+1:]
-    try:
-        mod = import_module(module)
-    except ImportError, e:
-        raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
-    try:
-        backend_class = getattr(mod, attr)
-    except AttributeError:
-        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
-    return backend_class()

registration/backends/default/__init__.py

-from django.conf import settings
-from django.contrib.sites.models import RequestSite
-from django.contrib.sites.models import Site
-
-from registration import signals
-from registration.forms import RegistrationForm
-from registration.models import RegistrationProfile
-
-
-class DefaultBackend(object):
-    """
-    A registration backend which follows a simple workflow:
-
-    1. User signs up, inactive account is created.
-
-    2. Email is sent to user with activation link.
-
-    3. User clicks activation link, account is now active.
-
-    Using this backend requires that
-
-    * ``registration`` be listed in the ``INSTALLED_APPS`` setting
-      (since this backend makes use of models defined in this
-      application).
-
-    * The setting ``ACCOUNT_ACTIVATION_DAYS`` be supplied, specifying
-      (as an integer) the number of days from registration during
-      which a user may activate their account (after that period
-      expires, activation will be disallowed).
-
-    * The creation of the templates
-      ``registration/activation_email_subject.txt`` and
-      ``registration/activation_email.txt``, which will be used for
-      the activation email. See the notes for this backends
-      ``register`` method for details regarding these templates.
-
-    Additionally, registration can be temporarily closed by adding the
-    setting ``REGISTRATION_OPEN`` and setting it to
-    ``False``. Omitting this setting, or setting it to ``True``, will
-    be interpreted as meaning that registration is currently open and
-    permitted.
-
-    Internally, this is accomplished via storing an activation key in
-    an instance of ``registration.models.RegistrationProfile``. See
-    that model and its custom manager for full documentation of its
-    fields and supported operations.
-    
-    """
-    def register(self, request, **kwargs):
-        """
-        Given a username, email address and password, register a new
-        user account, which will initially be inactive.
-
-        Along with the new ``User`` object, a new
-        ``registration.models.RegistrationProfile`` will be created,
-        tied to that ``User``, containing the activation key which
-        will be used for this account.
-
-        An email will be sent to the supplied email address; this
-        email should contain an activation link. The email will be
-        rendered using two templates. See the documentation for
-        ``RegistrationProfile.send_activation_email()`` for
-        information about these templates and the contexts provided to
-        them.
-
-        After the ``User`` and ``RegistrationProfile`` are created and
-        the activation email is sent, the signal
-        ``registration.signals.user_registered`` will be sent, with
-        the new ``User`` as the keyword argument ``user`` and the
-        class of this backend as the sender.
-
-        """
-        username, email, password = kwargs['username'], kwargs['email'], kwargs['password1']
-        if Site._meta.installed:
-            site = Site.objects.get_current()
-        else:
-            site = RequestSite(request)
-        new_user = RegistrationProfile.objects.create_inactive_user(username, email,
-                                                                    password, site)
-        signals.user_registered.send(sender=self.__class__,
-                                     user=new_user,
-                                     request=request)
-        return new_user
-
-    def activate(self, request, activation_key):
-        """
-        Given an an activation key, look up and activate the user
-        account corresponding to that key (if possible).
-
-        After successful activation, the signal
-        ``registration.signals.user_activated`` will be sent, with the
-        newly activated ``User`` as the keyword argument ``user`` and
-        the class of this backend as the sender.
-        
-        """
-        activated = RegistrationProfile.objects.activate_user(activation_key)
-        if activated:
-            signals.user_activated.send(sender=self.__class__,
-                                        user=activated,
-                                        request=request)
-        return activated
-
-    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_form_class(self, request):
-        """
-        Return the default form class used for user registration.
-        
-        """
-        return RegistrationForm
-
-    def post_registration_redirect(self, request, user):
-        """
-        Return the name of the URL to redirect to after successful
-        user registration.
-        
-        """
-        return ('registration_complete', (), {})
-
-    def post_activation_redirect(self, request, user):
-        """
-        Return the name of the URL to redirect to after successful
-        account activation.
-        
-        """
-        return ('registration_activation_complete', (), {})

registration/backends/default/urls.py

 This will also automatically set up the views in
 ``django.contrib.auth`` at sensible default locations.
 
-If you'd like to customize the behavior (e.g., by passing extra
-arguments to the various views) or split up the URLs, feel free to set
-up your own URL patterns for these views instead.
+If you'd like to customize registration behavior, feel free to set up
+your own URL patterns for these views instead.
 
 """
 
 
-from django.conf.urls.defaults import *
-from django.views.generic.simple import direct_to_template
+from django.conf.urls import patterns
+from django.conf.urls import include
+from django.conf.urls import url
+from django.views.generic.base import TemplateView
 
-from registration.views import activate
-from registration.views import register
+from registration.backends.default.views import ActivationView
+from registration.backends.default.views import RegistrationView
 
 
 urlpatterns = patterns('',
                        url(r'^activate/complete/$',
-                           direct_to_template,
-                           {'template': 'registration/activation_complete.html'},
+                           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<activation_key>\w+)/$',
-                           activate,
-                           {'backend': 'registration.backends.default.DefaultBackend'},
+                           ActivationView.as_view(),
                            name='registration_activate'),
                        url(r'^register/$',
-                           register,
-                           {'backend': 'registration.backends.default.DefaultBackend'},
+                           RegistrationView.as_view(),
                            name='registration_register'),
                        url(r'^register/complete/$',
-                           direct_to_template,
-                           {'template': 'registration/registration_complete.html'},
+                           TemplateView.as_view(template_name='registration/registration_complete.html'),
                            name='registration_complete'),
                        url(r'^register/closed/$',
-                           direct_to_template,
-                           {'template': 'registration/registration_closed.html'},
+                           TemplateView.as_view(template_name='registration/registration_closed.html'),
                            name='registration_disallowed'),
                        (r'', include('registration.auth_urls')),
                        )

registration/backends/default/views.py

+from django.conf import settings
+from django.contrib.sites.models import RequestSite
+from django.contrib.sites.models import Site
+
+from registration import signals
+from registration.models import RegistrationProfile
+from registration.views import ActivationView as BaseActivationView
+from registration.views import RegistrationView as BaseRegistrationView
+
+
+class RegistrationView(BaseRegistrationView):
+    """
+    A registration backend which follows a simple workflow:
+
+    1. User signs up, inactive account is created.
+
+    2. Email is sent to user with activation link.
+
+    3. User clicks activation link, account is now active.
+
+    Using this backend requires that
+
+    * ``registration`` be listed in the ``INSTALLED_APPS`` setting
+      (since this backend makes use of models defined in this
+      application).
+
+    * The setting ``ACCOUNT_ACTIVATION_DAYS`` be supplied, specifying
+      (as an integer) the number of days from registration during
+      which a user may activate their account (after that period
+      expires, activation will be disallowed).
+
+    * The creation of the templates
+      ``registration/activation_email_subject.txt`` and
+      ``registration/activation_email.txt``, which will be used for
+      the activation email. See the notes for this backends
+      ``register`` method for details regarding these templates.
+
+    Additionally, registration can be temporarily closed by adding the
+    setting ``REGISTRATION_OPEN`` and setting it to
+    ``False``. Omitting this setting, or setting it to ``True``, will
+    be interpreted as meaning that registration is currently open and
+    permitted.
+
+    Internally, this is accomplished via storing an activation key in
+    an instance of ``registration.models.RegistrationProfile``. See
+    that model and its custom manager for full documentation of its
+    fields and supported operations.
+    
+    """
+    def register(self, request, **cleaned_data):
+        """
+        Given a username, email address and password, register a new
+        user account, which will initially be inactive.
+
+        Along with the new ``User`` object, a new
+        ``registration.models.RegistrationProfile`` will be created,
+        tied to that ``User``, containing the activation key which
+        will be used for this account.
+
+        An email will be sent to the supplied email address; this
+        email should contain an activation link. The email will be
+        rendered using two templates. See the documentation for
+        ``RegistrationProfile.send_activation_email()`` for
+        information about these templates and the contexts provided to
+        them.
+
+        After the ``User`` and ``RegistrationProfile`` are created and
+        the activation email is sent, the signal
+        ``registration.signals.user_registered`` will be sent, with
+        the new ``User`` as the keyword argument ``user`` and the
+        class of this backend as the sender.
+
+        """
+        username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1']
+        if Site._meta.installed:
+            site = Site.objects.get_current()
+        else:
+            site = RequestSite(request)
+        new_user = RegistrationProfile.objects.create_inactive_user(username, email,
+                                                                    password, 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', (), {})
+
+
+class ActivationView(BaseActivationView):
+    def activate(self, request, activation_key):
+        """
+        Given an an activation key, look up and activate the user
+        account corresponding to that key (if possible).
+
+        After successful activation, the signal
+        ``registration.signals.user_activated`` will be sent, with the
+        newly activated ``User`` as the keyword argument ``user`` and
+        the class of this backend as the sender.
+        
+        """
+        activated_user = RegistrationProfile.objects.activate_user(activation_key)
+        if activated_user:
+            signals.user_activated.send(sender=self.__class__,
+                                        user=activated_user,
+                                        request=request)
+        return activated_user
+
+    def get_success_url(self, request, user):
+        return ('registration_activation_complete', (), {})

registration/backends/simple/__init__.py

-from django.conf import settings
-from django.contrib.auth import authenticate
-from django.contrib.auth import login
-from django.contrib.auth.models import User
-
-from registration import signals
-from registration.forms import RegistrationForm
-
-
-class SimpleBackend(object):
-    """
-    A registration backend which implements the simplest possible
-    workflow: a user supplies a username, email address and password
-    (the bare minimum for a useful account), and is immediately signed
-    up and logged in.
-    
-    """
-    def register(self, request, **kwargs):
-        """
-        Create and immediately log in a new user.
-        
-        """
-        username, email, password = kwargs['username'], kwargs['email'], kwargs['password1']
-        User.objects.create_user(username, email, password)
-        
-        # authenticate() always has to be called before login(), and
-        # will return the user we just created.
-        new_user = authenticate(username=username, password=password)
-        login(request, new_user)
-        signals.user_registered.send(sender=self.__class__,
-                                     user=new_user,
-                                     request=request)
-        return new_user
-
-    def activate(self, **kwargs):
-        raise NotImplementedError
-
-    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_form_class(self, request):
-        return RegistrationForm
-
-    def post_registration_redirect(self, request, user):
-        """
-        After registration, redirect to the user's account page.
-        
-        """
-        return (user.get_absolute_url(), (), {})
-
-    def post_activation_redirect(self, request, user):
-        raise NotImplementedError

registration/backends/simple/urls.py

 This will also automatically set up the views in
 ``django.contrib.auth`` at sensible default locations.
 
-If you'd like to customize the behavior (e.g., by passing extra
-arguments to the various views) or split up the URLs, feel free to set
-up your own URL patterns for these views instead.
+If you'd like to customize registration behavior, feel free to set up
+your own URL patterns for these views instead.
 
 """
 
 
-from django.conf.urls.defaults import *
-from django.views.generic.simple import direct_to_template
+from django.conf.urls import include
+from django.conf.urls import patterns
+from django.conf.urls import url
+from django.views.generic.base import TemplateView
 
-from registration.views import activate
-from registration.views import register
+from registration.backends.simple.views import RegistrationView
 
 
 urlpatterns = patterns('',
                        url(r'^register/$',
-                           register,
-                           {'backend': 'registration.backends.simple.SimpleBackend'},
+                           RegistrationView.as_view(),
                            name='registration_register'),
                        url(r'^register/closed/$',
-                           direct_to_template,
-                           {'template': 'registration/registration_closed.html'},
+                           TemplateView.as_view(template_name='registration/registration_closed.html'),
                            name='registration_disallowed'),
                        (r'', include('registration.auth_urls')),
                        )

registration/backends/simple/views.py

+from django.conf import settings
+from django.contrib.auth import authenticate
+from django.contrib.auth import login
+from django.contrib.auth.models import User
+
+from registration import signals
+from registration.views import RegistrationView as BaseRegistrationView
+
+
+class RegistrationView(BaseRegistrationView):
+    """
+    A registration backend which implements the simplest possible
+    workflow: a user supplies a username, email address and password
+    (the bare minimum for a useful account), and is immediately signed
+    up and logged in).
+    
+    """
+    def register(self, request, **cleaned_data):
+        username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1']
+        User.objects.create_user(username, email, password)
+
+        new_user = authenticate(username=username, password=password)
+        login(request, new_user)
+        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 (user.get_absolute_url(), (), {})

registration/forms.py

 """
 Forms and validation code for user registration.
 
+Note that all of these forms assume Django's bundle default ``User``
+model; since it's not possible for a form to anticipate in advance the
+needs of custom user models, you will need to write your own forms if
+you're using a custom model.
+
 """
 
 
 from django.utils.translation import ugettext_lazy as _
 
 
-# I put this on all required fields, because it's easier to pick up
-# on them with CSS or JavaScript if they have a class of "required"
-# in the HTML. Your mileage may vary. If/when Django ticket #3515
-# lands in trunk, this will no longer be necessary.
-attrs_dict = {'class': 'required'}
-
-
 class RegistrationForm(forms.Form):
     """
     Form for registering a new user account.
     need, but should avoid defining a ``save()`` method -- the actual
     saving of collected user data is delegated to the active
     registration backend.
+
+    """
+    required_css_class = 'required'
     
-    """
     username = forms.RegexField(regex=r'^[\w.@+-]+$',
                                 max_length=30,
-                                widget=forms.TextInput(attrs=attrs_dict),
                                 label=_("Username"),
                                 error_messages={'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")})
-    email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
-                                                               maxlength=75)),
-                             label=_("E-mail"))
-    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
+    email = forms.EmailField(label=_("E-mail"))
+    password1 = forms.CharField(widget=forms.PasswordInput,
                                 label=_("Password"))
-    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
+    password2 = forms.CharField(widget=forms.PasswordInput,
                                 label=_("Password (again)"))
     
     def clean_username(self):
     for agreeing to a site's Terms of Service.
     
     """
-    tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
+    tos = forms.BooleanField(widget=forms.CheckboxInput,
                              label=_(u'I have read and agree to the Terms of Service'),
                              error_messages={'required': _("You must agree to the terms to register")})
 

registration/tests/__init__.py

-from django.test import TestCase
-
-import registration
-
-from registration.tests.backends import *
+from registration.tests.default_backend import *
 from registration.tests.forms import *
 from registration.tests.models import *
-from registration.tests.views import *
-
-
-class RegistrationVersionInfoTests(TestCase):
-    """
-    Test django-registration's internal version-reporting
-    infrastructure.
-    
-    """
-    def setUp(self):
-        self.version = registration.VERSION
-
-    def tearDown(self):
-        registration.VERSION = self.version
-    
-    def test_get_version(self):
-        """
-        Test the version-info reporting.
-        
-        """
-        versions = [
-            {'version': (1, 0, 0, 'alpha', 0),
-             'expected': "1.0 pre-alpha"},
-            {'version': (1, 0, 1, 'alpha', 1),
-             'expected': "1.0.1 alpha 1"},
-            {'version': (1, 1, 0, 'beta', 2),
-             'expected': "1.1 beta 2"},
-            {'version': (1, 2, 1, 'rc', 3),
-             'expected': "1.2.1 rc 3"},
-            {'version': (1, 3, 0, 'final', 0),
-             'expected': "1.3"},
-            {'version': (1, 4, 1, 'beta', 0),
-             'expected': "1.4.1 beta"},
-            ]
-        
-        for version_dict in versions:
-            registration.VERSION = version_dict['version']
-            self.assertEqual(registration.get_version(), version_dict['expected'])
+from registration.tests.simple_backend import *

registration/tests/backends.py

-import datetime
-
-from django.conf import settings
-from django.contrib import admin
-from django.contrib.auth.models import User
-from django.contrib.sessions.middleware import SessionMiddleware
-from django.contrib.sites.models import Site
-from django.core import mail
-from django.core.exceptions import ImproperlyConfigured
-from django.core.handlers.wsgi import WSGIRequest
-from django.test import Client
-from django.test import TestCase
-
-from registration import forms
-from registration import signals
-from registration.admin import RegistrationAdmin
-from registration.backends import get_backend
-from registration.backends.default import DefaultBackend
-from registration.backends.simple import SimpleBackend
-from registration.models import RegistrationProfile
-
-
-class _MockRequestClient(Client):
-    """
-    A ``django.test.Client`` subclass which can return mock
-    ``HttpRequest`` objects.
-    
-    """
-    def request(self, **request):
-        """
-        Rather than issuing a request and returning the response, this
-        simply constructs an ``HttpRequest`` object and returns it.
-        
-        """
-        environ = {
-            'HTTP_COOKIE': self.cookies,
-            'PATH_INFO': '/',
-            'QUERY_STRING': '',
-            'REMOTE_ADDR': '127.0.0.1',
-            'REQUEST_METHOD': 'GET',
-            'SCRIPT_NAME': '',
-            'SERVER_NAME': 'testserver',
-            'SERVER_PORT': '80',
-            'SERVER_PROTOCOL': 'HTTP/1.1',
-            'wsgi.version': (1,0),
-            'wsgi.url_scheme': 'http',
-            'wsgi.errors': self.errors,
-            'wsgi.multiprocess':True,
-            'wsgi.multithread': False,
-            'wsgi.run_once': False,
-            'wsgi.input': None,
-            }
-        environ.update(self.defaults)
-        environ.update(request)
-        request = WSGIRequest(environ)
-
-        # We have to manually add a session since we'll be bypassing
-        # the middleware chain.
-        session_middleware = SessionMiddleware()
-        session_middleware.process_request(request)
-        return request
-
-
-def _mock_request():
-    """
-    Construct and return a mock ``HttpRequest`` object; this is used
-    in testing backend methods which expect an ``HttpRequest`` but
-    which are not being called from views.
-    
-    """
-    return _MockRequestClient().request()
-
-
-class BackendRetrievalTests(TestCase):
-    """
-    Test that utilities for retrieving the active backend work
-    properly.
-
-    """
-    def test_get_backend(self):
-        """
-        Verify that ``get_backend()`` returns the correct value when
-        passed a valid backend.
-
-        """
-        self.failUnless(isinstance(get_backend('registration.backends.default.DefaultBackend'),
-                                   DefaultBackend))
-
-    def test_backend_error_invalid(self):
-        """
-        Test that a nonexistent/unimportable backend raises the
-        correct exception.
-
-        """
-        self.assertRaises(ImproperlyConfigured, get_backend,
-                          'registration.backends.doesnotexist.NonExistentBackend')
-
-    def test_backend_attribute_error(self):
-        """
-        Test that a backend module which exists but does not have a
-        class of the specified name raises the correct exception.
-        
-        """
-        self.assertRaises(ImproperlyConfigured, get_backend,
-                          'registration.backends.default.NonexistentBackend')
-
-
-class DefaultRegistrationBackendTests(TestCase):
-    """
-    Test the default registration backend.
-
-    Running these tests successfully will require two templates to be
-    created for the sending of activation emails; details on these
-    templates and their contexts may be found in the documentation for
-    the default backend.
-
-    """
-    backend = DefaultBackend()
-    
-    def setUp(self):
-        """
-        Create an instance of the default backend for use in testing,
-        and set ``ACCOUNT_ACTIVATION_DAYS`` if it's not set already.
-
-        """
-        self.old_activation = getattr(settings, 'ACCOUNT_ACTIVATION_DAYS', None)
-        if self.old_activation is None:
-            settings.ACCOUNT_ACTIVATION_DAYS = 7 # pragma: no cover
-
-    def tearDown(self):
-        """
-        Yank out ``ACCOUNT_ACTIVATION_DAYS`` back out if it wasn't
-        originally set.
-
-        """
-        if self.old_activation is None:
-            settings.ACCOUNT_ACTIVATION_DAYS = self.old_activation # pragma: no cover
-
-    def test_registration(self):
-        """
-        Test the registration process: registration creates a new
-        inactive account and a new profile with activation key,
-        populates the correct account data and sends an activation
-        email.
-
-        """
-        new_user = self.backend.register(_mock_request(),
-                                         username='bob',
-                                         email='bob@example.com',
-                                         password1='secret')
-
-        # Details of the returned user must match what went in.
-        self.assertEqual(new_user.username, 'bob')
-        self.failUnless(new_user.check_password('secret'))
-        self.assertEqual(new_user.email, 'bob@example.com')
-
-        # New user must not be active.
-        self.failIf(new_user.is_active)
-
-        # A registration profile was created, and an activation email
-        # was sent.
-        self.assertEqual(RegistrationProfile.objects.count(), 1)
-        self.assertEqual(len(mail.outbox), 1)
-
-    def test_registration_no_sites(self):
-        """
-        Test that registration still functions properly when
-        ``django.contrib.sites`` is not installed; the fallback will
-        be a ``RequestSite`` instance.
-        
-        """
-        Site._meta.installed = False
-        new_user = self.backend.register(_mock_request(),
-                                         username='bob',
-                                         email='bob@example.com',
-                                         password1='secret')
-
-        self.assertEqual(new_user.username, 'bob')
-        self.failUnless(new_user.check_password('secret'))
-        self.assertEqual(new_user.email, 'bob@example.com')
-
-        self.failIf(new_user.is_active)
-
-        self.assertEqual(RegistrationProfile.objects.count(), 1)
-        self.assertEqual(len(mail.outbox), 1)
-        
-        Site._meta.installed = True
-
-    def test_valid_activation(self):
-        """
-        Test the activation process: activating within the permitted
-        window sets the account's ``is_active`` field to ``True`` and
-        resets the activation key.
-
-        """
-        valid_user = self.backend.register(_mock_request(),
-                                           username='alice',
-                                           email='alice@example.com',
-                                           password1='swordfish')
-
-        valid_profile = RegistrationProfile.objects.get(user=valid_user)
-        activated = self.backend.activate(_mock_request(),
-                                          valid_profile.activation_key)
-        self.assertEqual(activated.username, valid_user.username)
-        self.failUnless(activated.is_active)
-
-        # Fetch the profile again to verify its activation key has
-        # been reset.
-        valid_profile = RegistrationProfile.objects.get(user=valid_user)
-        self.assertEqual(valid_profile.activation_key,
-                         RegistrationProfile.ACTIVATED)
-
-    def test_invalid_activation(self):
-        """
-        Test the activation process: trying to activate outside the
-        permitted window fails, and leaves the account inactive.
-
-        """
-        expired_user = self.backend.register(_mock_request(),
-                                             username='bob',
-                                             email='bob@example.com',
-                                             password1='secret')
-
-        expired_user.date_joined = expired_user.date_joined - datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
-        expired_user.save()
-        expired_profile = RegistrationProfile.objects.get(user=expired_user)
-        self.failIf(self.backend.activate(_mock_request(),
-                                          expired_profile.activation_key))
-        self.failUnless(expired_profile.activation_key_expired())
-
-    def test_allow(self):
-        """
-        Test that the setting ``REGISTRATION_OPEN`` appropriately
-        controls whether registration is permitted.
-
-        """
-        old_allowed = getattr(settings, 'REGISTRATION_OPEN', True)
-        settings.REGISTRATION_OPEN = True
-        self.failUnless(self.backend.registration_allowed(_mock_request()))
-
-        settings.REGISTRATION_OPEN = False
-        self.failIf(self.backend.registration_allowed(_mock_request()))
-        settings.REGISTRATION_OPEN = old_allowed
-
-    def test_form_class(self):
-        """
-        Test that the default form class returned is
-        ``registration.forms.RegistrationForm``.
-
-        """
-        self.failUnless(self.backend.get_form_class(_mock_request()) is forms.RegistrationForm)
-
-    def test_post_registration_redirect(self):
-        """
-        Test that the default post-registration redirect is the named
-        pattern ``registration_complete``.
-
-        """
-        self.assertEqual(self.backend.post_registration_redirect(_mock_request(), User()),
-                         ('registration_complete', (), {}))
-
-    def test_registration_signal(self):
-        """
-        Test that registering a user sends the ``user_registered``
-        signal.
-        
-        """
-        def receiver(sender, **kwargs):
-            self.failUnless('user' in kwargs)
-            self.assertEqual(kwargs['user'].username, 'bob')
-            self.failUnless('request' in kwargs)
-            self.failUnless(isinstance(kwargs['request'], WSGIRequest))
-            received_signals.append(kwargs.get('signal'))
-
-        received_signals = []
-        signals.user_registered.connect(receiver, sender=self.backend.__class__)
-
-        self.backend.register(_mock_request(),
-                              username='bob',
-                              email='bob@example.com',
-                              password1='secret')
-
-        self.assertEqual(len(received_signals), 1)
-        self.assertEqual(received_signals, [signals.user_registered])
-
-    def test_activation_signal_success(self):
-        """
-        Test that successfully activating a user sends the
-        ``user_activated`` signal.
-        
-        """
-        def receiver(sender, **kwargs):
-            self.failUnless('user' in kwargs)
-            self.assertEqual(kwargs['user'].username, 'bob')
-            self.failUnless('request' in kwargs)
-            self.failUnless(isinstance(kwargs['request'], WSGIRequest))
-            received_signals.append(kwargs.get('signal'))
-
-        received_signals = []
-        signals.user_activated.connect(receiver, sender=self.backend.__class__)
-
-        new_user = self.backend.register(_mock_request(),
-                                         username='bob',
-                                         email='bob@example.com',
-                                         password1='secret')
-        profile = RegistrationProfile.objects.get(user=new_user)
-        self.backend.activate(_mock_request(), profile.activation_key)
-
-        self.assertEqual(len(received_signals), 1)
-        self.assertEqual(received_signals, [signals.user_activated])
-
-    def test_activation_signal_failure(self):
-        """
-        Test that an unsuccessful activation attempt does not send the
-        ``user_activated`` signal.
-        
-        """
-        receiver = lambda sender, **kwargs: received_signals.append(kwargs.get('signal'))
-
-        received_signals = []
-        signals.user_activated.connect(receiver, sender=self.backend.__class__)
-
-        new_user = self.backend.register(_mock_request(),
-                                         username='bob',
-                                         email='bob@example.com',
-                                         password1='secret')
-        new_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
-        new_user.save()
-        profile = RegistrationProfile.objects.get(user=new_user)
-        self.backend.activate(_mock_request(), profile.activation_key)
-
-        self.assertEqual(len(received_signals), 0)
-
-    def test_email_send_action(self):
-        """
-        Test re-sending of activation emails via admin action.
-        
-        """
-        admin_class = RegistrationAdmin(RegistrationProfile, admin.site)
-        
-        alice = self.backend.register(_mock_request(),
-                                      username='alice',
-                                      email='alice@example.com',
-                                      password1='swordfish')
-        
-        admin_class.resend_activation_email(_mock_request(),
-                                            RegistrationProfile.objects.all())
-        self.assertEqual(len(mail.outbox), 2) # One on registering, one more on the resend.
-        
-        RegistrationProfile.objects.filter(user=alice).update(activation_key=RegistrationProfile.ACTIVATED)
-        admin_class.resend_activation_email(_mock_request(),
-                                            RegistrationProfile.objects.all())
-        self.assertEqual(len(mail.outbox), 2) # No additional email because the account has activated.
-
-    def test_email_send_action_no_sites(self):
-        """
-        Test re-sending of activation emails via admin action when
-        ``django.contrib.sites`` is not installed; the fallback will
-        be a ``RequestSite`` instance.
-        
-        """
-        Site._meta.installed = False
-        admin_class = RegistrationAdmin(RegistrationProfile, admin.site)
-        
-        alice = self.backend.register(_mock_request(),
-                                      username='alice',
-                                      email='alice@example.com',
-                                      password1='swordfish')
-        
-        admin_class.resend_activation_email(_mock_request(),
-                                            RegistrationProfile.objects.all())
-        self.assertEqual(len(mail.outbox), 2) # One on registering, one more on the resend.
-        
-        RegistrationProfile.objects.filter(user=alice).update(activation_key=RegistrationProfile.ACTIVATED)
-        admin_class.resend_activation_email(_mock_request(),
-                                            RegistrationProfile.objects.all())
-        self.assertEqual(len(mail.outbox), 2) # No additional email because the account has activated.
-        Site._meta.installed = True
-
-    def test_activation_action(self):
-        """
-        Test manual activation of users view admin action.
-        
-        """
-        admin_class = RegistrationAdmin(RegistrationProfile, admin.site)
-
-        alice = self.backend.register(_mock_request(),
-                                      username='alice',
-                                      email='alice@example.com',
-                                      password1='swordfish')
-
-        admin_class.activate_users(_mock_request(),
-                                   RegistrationProfile.objects.all())
-        self.failUnless(User.objects.get(username='alice').is_active)
-
-
-class SimpleRegistrationBackendTests(TestCase):
-    """
-    Test the simple registration backend, which does signup and
-    immediate activation.
-    
-    """
-    backend = SimpleBackend()
-    
-    def test_registration(self):
-        """
-        Test the registration process: registration creates a new
-        inactive account and a new profile with activation key,
-        populates the correct account data and sends an activation
-        email.
-
-        """
-        new_user = self.backend.register(_mock_request(),
-                                         username='bob',
-                                         email='bob@example.com',
-                                         password1='secret')
-
-        # Details of the returned user must match what went in.
-        self.assertEqual(new_user.username, 'bob')
-        self.failUnless(new_user.check_password('secret'))
-        self.assertEqual(new_user.email, 'bob@example.com')
-
-        # New user must not be active.
-        self.failUnless(new_user.is_active)
-
-    def test_allow(self):
-        """
-        Test that the setting ``REGISTRATION_OPEN`` appropriately
-        controls whether registration is permitted.
-
-        """
-        old_allowed = getattr(settings, 'REGISTRATION_OPEN', True)
-        settings.REGISTRATION_OPEN = True
-        self.failUnless(self.backend.registration_allowed(_mock_request()))
-
-        settings.REGISTRATION_OPEN = False
-        self.failIf(self.backend.registration_allowed(_mock_request()))
-        settings.REGISTRATION_OPEN = old_allowed
-
-    def test_form_class(self):
-        """
-        Test that the default form class returned is
-        ``registration.forms.RegistrationForm``.
-
-        """
-        self.failUnless(self.backend.get_form_class(_mock_request()) is forms.RegistrationForm)
-
-    def test_post_registration_redirect(self):
-        """
-        Test that the default post-registration redirect is the public
-        URL of the new user account.
-
-        """
-        new_user = self.backend.register(_mock_request(),
-                                         username='bob',
-                                         email='bob@example.com',
-                                         password1='secret')
-        
-        self.assertEqual(self.backend.post_registration_redirect(_mock_request(), new_user),
-                         (new_user.get_absolute_url(), (), {}))
-
-    def test_registration_signal(self):
-        """
-        Test that registering a user sends the ``user_registered``
-        signal.
-        
-        """
-        def receiver(sender, **kwargs):
-            self.failUnless('user' in kwargs)
-            self.assertEqual(kwargs['user'].username, 'bob')
-            self.failUnless('request' in kwargs)
-            self.failUnless(isinstance(kwargs['request'], WSGIRequest))
-            received_signals.append(kwargs.get('signal'))
-
-        received_signals = []
-        signals.user_registered.connect(receiver, sender=self.backend.__class__)
-
-        self.backend.register(_mock_request(),
-                              username='bob',
-                              email='bob@example.com',
-                              password1='secret')
-
-        self.assertEqual(len(received_signals), 1)