Commits

James Bennett committed ab21a26 Merge

Merge Django 1.5 compatibilty branch into default.

  • Participants
  • Parent commits 27bccd1, 7cd71a6

Comments (11)

          1. Raúl Pedro Santos

            Well, I meant fork it here and do a pull request, instead of creating a completely separate project on Github. A fork + pull request here would help the project improve, instead of dividing efforts.

            I just looked at the current source and indeed, this change has been done. But since you didn't reply here and none of the following commit messages gave any indication of this change, I assumed it had not been made and that maybe you weren't receiving notifications for pull requests, or comments (just like I explained in the private message I sent you).

            I apologise if I came across as rude or anything.

        1. mdeboard

          I didn't do so because

          1. all the rest of my work is on github and I'm lazy,

          2. the repo is managed with hg and I'm not interested in learning hg specifically for working on a fork of someone else's work

          3. the repo owner seemed somewhat hostile to/about pull requests and I didn't want to get involved

  1. mdeboard

    What was the rationale behind leaving the reference to the built-in User model in place for this branch, vice e.g.

    from django.contrib.auth import get_user_model
    User = get_user_model()
    

    I was going to fork and extend but I thought I'd ask first.

  2. AJ Bahnken
    ajvb$ python manage.py migrate
    CommandError: One or more models did not validate:
    registration.registrationprofile: 'user' defines a relation with the model 'auth.User', which has been swapped out. Update the relation to point at settings.AUTH_USER_MODEL.
    

    This is caused when installing directly from default.

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)
-