Commits

Anonymous committed de1e97d

Preparing for the 0.3 release

Comments (0)

Files changed (13)

 =============================
 
 
+Version 0.3, 10 September 2007:
+-------------------------------
+
+Packaged from revision ? in Subversion; download at
+http://django-registration.googlecode.com/files/registration-0.3.tar.gz
+
+* Changed ``register`` and ``activate`` views to accept
+  ``template_name`` keyword argument for selecting a custom template.
+
+* Changed ``register`` view to accept ``form_class`` keyword
+  argument specifying the form to use.
+
+* BACKWARDS-INCOMPATIBLE CHANGE: Changed
+  ``RegistrationManager.create_inactive_user`` to use a template for
+  the subject of the activation email.
+
+* BACKWARDS-INCOMPATIBLE CHANGE: Removed the ``tos`` field from
+  ``RegistrationForm``; if you were relying on it, switch to using
+  ``RegistrationFormTermsOfService`` instead.
+
+* Added script ``bin/delete_expired_users.py`` with instructions on
+  how to use it as a cron job to clean up expired/inactive accounts.
+
+* Marked strings for translation and added ``locale`` directory so
+  that translations can be added.
+
+
 Version 0.2, 29 May 2007:
 -------------------------
 
 include MANIFEST.in
 include README.txt
 recursive-include docs *
-recursive-include registration/templates *
+recursive-include registration/conf *
+recursive-include registration/templates *
+=====
+Forms
+=====
+
+
+To ease and automate the process of validating user information during
+registration, several form classes (built using Django's `newforms
+library`_) are provided: a base ``RegistrationForm`` and subclasses
+which provide specific customized functionality. All of the forms
+described below are found in ``registration.forms``.
+
+.. _newforms library: http://www.djangoproject.com/documentation/newforms/
+
+
+``RegistrationForm``
+====================
+
+Form for registering a new user account.
+
+Validates that the request username is not already in use, and
+requires the password to be entered twice to catch typos.
+
+Subclasses should feel free to add any additional validation they
+need, but should either preserve the base ``save()`` or implement a
+``save()`` which accepts the ``profile_callback`` keyword argument and
+passes it through to
+``RegistrationProfile.objects.create_inactive_user()``.
+    
+Fields:
+
+``username``
+    The new user's requested username. Will be validated according to
+    the same regular expression Django's authentication system uses to
+    validate usernames.
+
+``email``
+    The new user's email address. Must be a well-formed email address.
+
+``password1``
+    The new user's password.
+
+``password2``
+    The password, again, to catch typos.
+
+
+Non-validation methods:
+
+``save()``
+    Creates the new ``User`` and ``RegistrationProfile``, and returns
+    the ``User``.
+    
+    This is essentially a light wrapper around
+    ``RegistrationProfile.objects.create_inactive_user()``, feeding it
+    the form data and a profile callback (see the documentation on
+    ``create_inactive_user()`` for details) if supplied.
+
+
+Subclasses of ``RegistrationForm``
+==================================
+
+As explained above, subclasses may add any additional validation they
+like, but must either preserve the ``save()`` method or implement a
+``save()`` method with an identical signature.
+
+Three subclasses are included as examples, and as ready-made
+implementations of useful customizations:
+
+``RegistrationFormTermsOfService``
+    Subclass of ``RegistrationForm`` which adds a required checkbox
+    for agreeing to a site's Terms of Service.
+
+``RegistrationFormUniqueEmail``
+    Subclass of ``RegistrationForm`` which enforces uniqueness of
+    email addresses.
+
+``RegistrationFormNoFreeEmail``
+    Subclass of ``RegistrationForm`` which disallows registration with
+    email addresses from popular free webmail services; moderately
+    useful for preventing automated spam registrations.
+    
+    To change the list of banned domains, subclass this form and
+    override the attribute ``bad_domains``.
+===================
+Models and managers
+===================
+
+
+Because the two-step process of registration and activation requires
+some means of temporarily storing activation key and retrieving it for
+verification, a simple model --
+``registration.models.RegistrationProfile`` -- is provided in this
+application, and a custom manager --
+``registration.models.RegistrationManager`` -- is included and defines
+several useful methods for interacting with ``RegistrationProfile``.
+
+Both the ``RegistrationProfile`` model and the ``RegistrationManager``
+are found in ``registration.models``.
+
+
+The ``RegistrationProfile`` model
+=================================
+
+A simple profile which stores an activation key for use during user
+account registration.
+
+Generally, you will not want to interact directly with instances of
+this model; the provided manager includes methods for creating and
+activating new accounts, as well as for cleaning out accounts which
+have never been activated.
+
+While it is possible to use this model as the value of the
+``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do
+so. This model's sole purpose is to store data temporarily during
+account registration and activation, and a mechanism for automatically
+creating an instance of a site-specific profile model is provided via
+the ``create_inactive_user`` on ``RegistrationManager``.
+    
+``RegistrationProfile`` objects have the following fields:
+
+``activation_key``
+    A SHA1 hash used as an account's activation key.
+
+``user``
+    The ``User`` object for which activation information is being
+    stored.
+
+``RegistrationProfile`` also has one custom method defined:
+
+``activation_key_expired()``
+    Determines whether this ``RegistrationProfile``'s activation key
+    has expired.
+    
+    Returns ``True`` if the key has expired, ``False`` otherwise.
+    
+    Key expiration is determined by the setting
+    ``ACCOUNT_ACTIVATION_DAYS``, which should be the number of days a
+    key should remain valid after an account is registered.
+
+
+The ``RegistrationManager``
+===========================
+
+Custom manager for the ``RegistrationProfile`` model.
+    
+The methods defined here provide shortcuts for account creation and
+activation (including generation and emailing of activation keys), and
+for cleaning out expired inactive accounts.
+    
+Methods:
+
+``activate_user(activation_key)``
+    Validates an activation key and activates the corresponding
+    ``User`` if valid.
+    
+    If the key is valid and has not expired, returns the ``User``
+    after activating.
+    
+    If the key is not valid or has expired, returns ``False``.
+    
+    If the key is valid but the ``User`` is already active, returns
+    the ``User``.
+
+``create_inactive_user(username, password, email, send_email=True, profile_callback=None)``
+    Creates a new, inactive ``User``, generates a
+    ``RegistrationProfile`` and emails its activation key to the
+    ``User``. Returns the new ``User``.
+    
+    To disable the email, call with ``send_email=False``.
+    
+    To enable creation of a custom user profile along with the
+    ``User`` (e.g., the model specified in the ``AUTH_PROFILE_MODULE``
+    setting), define a function which knows how to create and save an
+    instance of that model with appropriate default values, and pass
+    it as the keyword argument ``profile_callback``. This function
+    should accept one keyword argument:
+    
+    ``user``
+        The ``User`` to relate the profile to.
+
+``create_profile(user)``
+    Creates a ``RegistrationProfile`` for a given ``User``. Returns
+    the ``RegistrationProfile``.
+    
+    The activation key for the ``RegistrationProfile`` will be a SHA1
+    hash, generated from a combination of the ``User``'s username and
+    a random salt.
+
+``deleted_expired_users()``
+    Removes expired instances of ``RegistrationProfile`` and their
+    associated ``User`` objects.
+    
+    Accounts to be deleted are identified by searching for instances
+    of ``RegistrationProfile`` with expired activation keys, and then
+    checking to see if their associated ``User`` instances have the
+    field ``is_active`` set to ``False``; any ``User`` who is both
+    inactive and has an expired activation key will be deleted.
+    
+    It is recommended that this method be executed regularly as part
+    of your routine site maintenance; the file
+    ``bin/delete_expired_users.py`` in this application provides a
+    standalone script, suitable for use as a cron job, which will call
+    this method.
+    
+    Regularly clearing out accounts which have never been activated
+    serves two useful purposes:
+    
+    1. It alleviates the ocasional need to reset a
+       ``RegistrationProfile`` and/or re-send an activation email when
+       a user does not receive or does not act upon the initial
+       activation email; since the account will be deleted, the user
+       will be able to simply re-register and receive a new activation
+       key.
+    
+    2. It prevents the possibility of a malicious user registering one
+       or more accounts and never activating them (thus denying the
+       use of those usernames to anyone else); since those accounts
+       will be deleted, the usernames will become available for use
+       again.
+    
+    If you have a troublesome ``User`` and wish to disable their
+    account while keeping it in the database, simply delete the
+    associated ``RegistrationProfile``; an inactive ``User`` which
+    does not have an associated ``RegistrationProfile`` will not be
+    deleted.

docs/overview.txt

 Django registration
 ===================
 
+
 This is a fairly simple user-registration application for Django_,
 designed to make allowing user signups as painless as possible.
 
 .. _Django: http://www.djangoproject.com/
 
 
+Overview
+========
+
+This application enables a common user-registration workflow:
+
+1. User fills out a registration form, selecting a username and
+   password and entering an email address.
+
+2. An inactive account is created, and an activation link is sent to
+   the user's email address.
+
+3. User clicks the activation link, the account becomes active and the
+   user is able to log in and begin contributing to your site.
+
+Various methods of extending and customizing the registration process
+are also provided.
+
+
+Installation
+============
+
+In order to use django-registration, you will need to have a recent
+Subversion checkout of Django's development version; this application
+is keeping up with changes in Django's development version (and taking
+advantage of new features available there). Because of this,
+django-registration will not work with the Django 0.96 release.
+
+Also, if you haven't downloaded a copy of django-registration already,
+you'll need to do so. You can download a packaged version of the
+latest release here::
+
+    http://django-registration.googlecode.com/files/registration-0.3.tar.gz
+
+Open up the package (on most operating systems you can double-click,
+or you can use the command ``tar zxvf registration-0.3.tar.gz`` to
+manually unpack it), and, at a command line, navigate to the directory
+``registration-0.3``, then type:
+
+    python setup.py install
+
+This will install django-registration into a directory on your Python
+import path. For system-wide installation on Linux/Unix and Mac OS,
+you can use ``sudo``::
+
+    sudo python setup.py install
+
+Alternatively, you can do a Subversion checkout to get the latest
+development code (though this may also include bugs which have not yet
+been fixed)::
+
+    svn co http://django-registration.googlecode.com/svn/trunk/registration/
+
+For best results, do that in a directory that's on your Python import
+path.
+
+
+Basic use
+=========
+
+To use the registration system with all its default settings, you'll
+need to do the following:
+
+1. Add ``registration`` to the ``INSTALLED_APPS`` setting of your
+   Django project.
+
+2. Add the setting ``ACCOUNT_ACTIVATION_DAYS`` to your settings file;
+   this should be the number of days activation keys will remain valid
+   after an account is registered.
+
+3. Edit the example templates included with django-registration to
+   suit your own site, or create your own set of templates (see the
+   section on templates below for details).
+
+4. Add this line to your site's root URLConf::
+   
+       (r'^accounts/$', include('registration.urls')),
+
+5. Link people to ``/accounts/register/`` so they can start signing
+   up.
+
+
+Templates used by django-registration
+=====================================
+
+The views included in django-registration make use of five templates:
+
+* ``registration/registration_form.html`` displays the registration
+  form for users to sign up.
+
+* ``registration/registration_complete.html`` is displayed after the
+  activation email has been sent, to tell the new user to check
+  his/her email.
+
+* ``registration/activation_email_subject.txt`` is used for the
+  subject of the activation email.
+
+* ``registration/activation_email.txt`` is used for the body of the
+  activation email.
+
+* ``registration/activate.html`` is displayed when a user attempts to
+  activate his/her account.
+
+Examples of all of these templates are provided, but remember that
+**they are examples** and you will almost certainly have to edit them
+to suit your site's HTML and template block structure. For views
+defined in this application, see the included `views documentation`_
+for details on available context variables, and for details on the
+templates used by the activation email see the included `models
+documentation`_.
+
+Additionally, the URLConf provided with django-registration includes
+URL patterns for useful views in Django's built-in authentication
+application -- this means that a single ``include`` in your root
+URLConf can wire up registration and the auth application's login,
+logout, and password change/reset views. If you choose to use these
+views you will need to provide your own templates for them; consult
+`the Django authentication documentation`_ for details on the
+templates and contexts used by these views.
+
+.. _views documentation: views.txt
+.. _models documentation: models.txt
+.. _the Django authentication documentation: http://www.djangoproject.com/documentation/authentication/
+
+
+How it works
+============
+
+Using the recommended default configuration, the URL
+``/accounts/register/`` will map to the view
+``registration.views.register``, which displays a registration form
+(an instance of ``registration.forms.RegistrationForm``); this form
+asks for a username, email address and password, and verifies that the
+username is available and requires the password to be entered twice
+(to catch typos). It then does three things:
+
+1. Creates an instance of ``django.contrib.models.auth.User``, using
+   the supplied username, email address and password; the
+   ``is_active`` field on the new ``User`` will be set to ``False``,
+   meaning that the account is inactive and the user will not be able
+   to log in yet.
+
+2. Creates an instance of ``registration.models.RegistrationProfile``,
+   stores an activation key (a SHA1 hash generated from the new user's
+   username plus a randomly-generated "salt"), and relates that
+   ``RegistrationProfile`` to the ``User`` it just created.
+
+3. Sends an email to the user (at the address they supplied)
+   containing a link which can be clicked to activate the account.
+
+For details on customizing this process, including use of alternate
+registration form classes and automatic creation of a site-specific
+profile, see the sections on customization below.
+
+After the activation email has been sent,
+``registration.views.register`` issues a redirect to the URL
+``/accounts/register/complete/``. By default, this is mapped to the
+``direct_to_template`` generic view, and displays the template
+``registration/registration_complete.html``; this is intended to show
+a short message telling the user to check his/her email for the
+activation link.
+
+The activation link will map to the view
+``registration.views.activate``, which will attempt to activate the
+account by setting the ``is_active`` field on the ``User`` to
+``True``. If the activation key for the ``User`` has expired (this is
+controlled by the setting ``ACCOUNT_ACTIVATION_DAYS``, as described
+above), the account will not be activated (see the section on
+maintenance below for instructions on cleaning out expired accounts
+which have not been activated).
+
+
+Maintenance
+===========
+
+Inevitably, a site which uses a two-step process for user signup --
+registration followed by activation -- will accumulate a certain
+number of accounts which were registered but never activated. These
+accounts clutter up the database and tie up usernames which might
+otherwise be actively used, so it's desirable to clean them out
+periodically. For this purpose, a script,
+``registration/bin/deleted_expired_users.py``, is provided, which is
+suitable for use as a regular cron job. See that file for notes on how
+to add it to your crontab, and the included models documentation (see
+below) for discussion of how it works and some caveats.
+
+
+Where to go from here
+=====================
+
+Full documentation for all included components is bundled in the
+packaged release; see the following files for details:
+
+* `Forms documentation`_ for details on ``RegistrationForm``,
+  pre-packaged subclasses and available customizations.
+
+* `Models documentation`_ for details on ``RegistrationProfile`` and
+  its custom manager.
+
+* `Views documentation`_ for details on the ``register`` and
+  ``activate`` views, and methods for customizing them.
+
+.. _Forms documentation: forms.txt
+.. _Models documentation: models.txt
+.. _Views documentation: views.txt
+
+
 Development
 ===========
 
-The `latest released version`_ of this application is 0.2, and is
-quite stable; it's already been deployed on a number of sites. You can
-also obtain the absolute freshest code from `a Subversion checkout`_,
-but be warned that the code in SVN may not always be
-backwards-compatible, and may well contain bugs that haven't yet been
-fixed.
+The `latest released version`_ of this application is 0.3, and is
+quite stable; it's already been deployed on a number of sites,
+including djangoproject.com. You can also obtain the absolute freshest
+code from `a Subversion checkout`_, but be warned that the code in SVN
+may not always be backwards-compatible, and may well contain bugs that
+haven't yet been fixed.
 
-This document covers the 0.2 release of django-registration; new
+This document covers the 0.3 release of django-registration; new
 features introduced in Subversion will be added to the documentation
 at the time of the next packaged release.
 
-.. _latest released version: http://django-registration.googlecode.com/files/registration-0.2.tar.gz
+.. _latest released version: 
 .. _a Subversion checkout: http://django-registration.googlecode.com/svn/trunk/registration/
 
 
 Changes from previous versions
 ==============================
 
-Several new features were added between version 0.1 and version 0.2;
+Several new features were added between version 0.2 and version 0.3;
 for details, see the CHANGELOG.txt file distributed with the packaged
-0.2 release.
+0.3 release.
 
 One important change to note before upgrading an installation of
 version 0.1 is a change to the ``RegistrationProfile`` model; the
 Due to use of the ``newforms`` library, this application cannot work
 with Django 0.95.
 
-
-Basic use
-=========
-
-This application enables a fairly common workflow for user signups:
-
-1. User signs up for account.
-2. User gets emailed an activation link.
-3. User clicks the activation link before it expires.
-4. User becomes a happy and productive contributor to your site.
-
-To make this work, start by putting this app into your
-``INSTALLED_APPS`` setting and running ``manage.py syncdb``. Then, add
-a new setting in your settings file: ``ACCOUNT_ACTIVATION_DAYS``. This
-should be a number, and will be used as the number of days before an
-activation key expires.
-
-Next, either edit the included templates (see below) to suit your
-site, or create your own templates which integrate with your site's
-look and feel.
-
-Finally, drop this line into your root URLConf::
-
-    (r'^accounts/', include('registration.urls')),
-
-And point people at the URL ``/accounts/register/``. Things should
-just work.
-
-
-Templates
-=========
-
-The download includes a set of **example** templates, which are meant
-to give you an idea of how things work. However, they're taken
-directly from a site which uses this application, and so they make
-assumptions about that site's template structure that probably aren't
-true of your site. So **it is expected that you will edit these
-templates before using them**, or use them as guides to create your
-own set of templates. Either way, you'll want to make sure you have a
-set of templates that works with your site, and with its look and
-feel.
-
-The default template included for the activation email makes use of
-the ``humanize`` template tag library, so if you use that template
-"as-is" you'll need to have ``django.contrib.humanize`` in your
-``INSTALLED_APPS`` setting.
-
-
-Models
-======
-
-This app includes one model: ``RegistrationProfile``, which is tied to
-the ``User`` model in ``django.contrib.auth`` via a unique foreign
-key. ``RegistrationProfile`` simply stores the user's activation key.
-
-``RegistrationProfile`` has one custom method,
-``activation_key_expired``, which returns ``True`` if the key has
-expired and ``False`` otherwise.
-
-There's also a custom manager on ``RegistrationProfile`` which defines
-a few useful methods:
-
-    ``activate_user``
-        Takes an activation key, looks up the ``RegistrationProfile``
-        for that key, and sets the ``is_active`` field to ``True`` on
-        the ``User`` associated with it. If this is successful, it
-        returns the ``User``. If the key was expired, or if the key
-        didn't correspond to any ``RegistrationProfile``, this method
-        returns ``False``.
-
-    ``create_inactive_user``
-        Takes a username, email address and password, and first
-        creates a new ``User`` with those values, setting the
-        ``is_active`` field to ``False``. Then it generates an
-        activation key and stores a ``RegistrationProfile`` for the
-        ``User``. Finally, it sends an email to the user containing
-        the activation key. The email is generated by rendering the
-        template ``registration/activation_email.txt``, so edit that
-        template to customize the text of the email. This method
-        returns the new ``User`` object, in case you want to do
-        something with it afterwards.
-        
-        To enable creation of a site-specific user profile (e.g., the
-        model specified in the ``AUTH_PROFILE_MODULE`` setting), pass
-        the optional keyword argument ``profile_callback``; the value
-        of this argument should be a function which, given a ``User``,
-        will create and save an instance of the site-specific profile
-        with appropriate default values.
-        
-    ``create_profile`` Given a ``User`` object, creates, saves and
-        returns a ``RegistrationProfile`` for that ``User``,
-        generating the activation key from a combinatino og the
-        ``User``'s username and a random salt.
-
-    ``delete_inactive_users``
-        Clears out accounts which were never activated; it does this
-        by finding accounts which still have ``is_active`` set to
-        ``False`` but have a ``RegistrationProfile`` with an expired
-        key, and deleting them. This is intended to keep your database
-        uncluttered and free up inactive usernames for registration,
-        but if for some reason you need to deactivate an account while
-        keeping the ``User`` in the database (say, to disable a
-        troublesome user's login), you can manually delete that
-        account's ``RegistrationProfile`` and this method will leave
-        it alone.
-
-Most of the time you won't need to manually write code which calls
-these methods, though; the included views (see below) will take care
-of them for you.
-
-
-Forms
-=====
-
-One form is included, because it's really all that's needed to handle
-user signups.
-
-``registration.forms.RegistrationForm``
----------------------------------------
-
-This form collects a username, email address and password from a
-prospective user, and does a little bit of validation:
-
-* It checks that the username is not already in use.
-
-* It requires the user to enter the password twice (to avoid typos),
-  and checks that both versions match.
-
-This is really the minimal level of validation that most people seem
-to want; more advanced schemes like requiring that usernames or
-passwords be at least a certain number of characters long, or also
-validating the uniqueness of the email address, are best handled by
-customizing the form to your own needs.
-
-
-Views
-=====
-
-Two views are included, and between them they handle all the actual
-work of registering and activating users.
-
-
-``registration.views.activate``
--------------------------------
-
-This view accepts an activation key, and calls
-``RegistrationProfile.objects.activate_user`` (see above) to activate
-the account associated with that key.
-
-The template used is ``registration/activate.html``, and it receives
-two context variables:
-
-    ``account``
-        The ``User`` who was activated, if activation was successful,
-        or ``False`` if the key was expired or didn't match any
-        existing account.
-
-    ``expiration_days``
-        The number of days activation keys last before they expire;
-        this is useful for displaying a "perhaps the account expired"
-        message on an unsuccessful activation.
-
-
-``registration.views.register``
--------------------------------
-
-This view signs up a new user account, by displaying and validating a
-``RegistrationForm``, and calling
-``RegistrationProfile.objects.create_inactive_user`` (see above) once
-it gets valid data.
-
-The template used is ``registration/registration_form.html``, and it
-receives a context variable named ``form``, which is the
-``RegistrationForm`` instance. It also uses ``RequestContext``, so any
-context processors you've enabled on your site will be applied as
-well.
-
-On successful registration, this view issues a redirect; the default
-URL of the redirect is ``/accounts/register/complete/``, but you can
-override this by passing the keyword argument ``success_url`` into the
-view.
-
-To enable creation of a site-specific user profile, pass the optional
-keyword argument ``profile_callback`` to this view; see the
-``create_inactive_user`` method of the manager on
-``RegistrationProfile`` (documented above) for details.
-
-URLs
-====
-
-The default URLConf included with this application maps the following
-URL fragments to the following views:
-
-    ``activate/<activation key>/``
-        Maps to ``registration.views.activate``. Note that this URL
-        will match any string as ``activation_key``; the pattern will
-        be checked to see if it matches the format of a SHA1 hash
-        before any database lookup happens, but allowing anything to
-        match here means you can show a useful "this key is invalid"
-        message instead of a confusing 404 when the format isn't
-        correct.
-    
-    ``register/``
-        Maps to ``registration.views.register``.
-
-    ``register/complete/``
-        Maps to ``django.views.generic.simple.direct_to_template``,
-        using the template
-        ``registration/registration_complete.html``.
-
-    ``login/``
-        Maps to ``django.contrib.auth.views.login``.
-
-    ``logout/``
-        Maps to ``django.contrib.auth.views.logout``.
-
-A good place to include this URLConf if you use it directly is at
-``accounts/``.
-
-
-Troubleshooting
-===============
-
-There are two problems you're likely to run into: one is not editing
-the default template set (see above for why you need to do that), and
-will lead either to errors about a base template not existing, or to a
-mismatch between names of blocks in your site's base template and
-names of blocks in the default template set.
-
-The other problem you're likely to see is users complaining that they
-didn't get their activation emails. This is most likely due to
-overzealous spam filtering by their ISP or email provider;
-unfortunately, many spam filters eat account registration emails for
-breakfast. If you can solve that problem, you will make millions of
-dollars.
-
-
 What this application does not do
 =================================
 
-This application also does not integrate in any way with OpenID, nor
-should it; one of the key selling points of OpenID is that users
-**don't** have to walk through an explicit registration step on every
-site they want to use :)
+This application does not integrate in any way with OpenID, nor should
+it; one of the key selling points of OpenID is that users **don't**
+have to walk through an explicit registration step for every site or
+service they want to use :)
 
 
 If you spot a bug
+=====
+Views
+=====
+
+
+Two views are included which, between them, handle the process of
+first registering and then activating new user accounts; both views
+are found in ``registration.views``.
+
+
+``activate``
+=====================================
+
+Activates a ``User``'s account, if their key is valid and hasn't
+expired.
+
+By default, uses the template ``registration/activate.html``; to
+change this, pass the name of a template as the keyword argument
+``template_name``.
+
+**Required arguments**
+
+``activation_key``
+    The activation key to validate and use for activating the
+    ``User``.
+    
+**Optional arguments**
+
+``template_name``
+    A custom template to use.
+
+**Context:**
+
+``account``
+    The ``User`` object corresponding to the account, if the
+    activation was successful. ``False`` if the activation was not
+    successful.
+    
+``expiration_days``
+    The number of days for which activation keys stay valid after
+    registration.
+
+**Template:**
+    
+registration/activate.html or ``template_name`` keyword argument.
+
+
+``register``
+============
+
+Allows a new user to register an account.
+    
+Following successful registration, redirects to either
+``/accounts/register/complete/`` or, if supplied, the URL specified in
+the keyword argument ``success_url``.
+
+By default, ``registration.forms.RegistrationForm`` will be used as
+the registration form; to change this, pass a different form class as
+the ``form_class`` keyword argument. The form class you specify must
+have a method ``save`` which will create and return the new ``User``,
+and that method must accept the keyword argument ``profile_callback``
+(see below).
+
+To enable creation of a site-specific user profile object for the new
+user, pass a function which will create the profile object as the
+keyword argument ``profile_callback``. See
+``RegistrationManager.create_inactive_user`` in the file ``models.py``
+for details on how to write this function.
+
+By default, uses the template ``registration/registration_form.html``;
+to change this, pass the name of a template as the keyword argument
+``template_name``.
+
+**Required arguments**
+
+None.
+
+**Optional arguments**
+
+``form_class``
+    The form class to use for registration.
+
+``profile_callback``
+    A function which will be used to create a site-specific profile
+    instance for the new ``User``.
+
+``success_url``
+    The URL to redirect to on successful registration.
+
+``template_name``
+    A custom template to use.
+
+**Context:**
+
+``form``
+    The registration form.
+    
+**Template:**
+
+registration/registration_form.html or ``template_name`` keyword
+argument.

registration/bin/delete_expired_users.py

+"""
+A script which removes expired/inactive user accounts from the
+database.
+
+This is intended to be run as a cron job; for example, to have it run
+at midnight each Sunday, you could add lines like the following to
+your crontab::
+
+    DJANGO_SETTINGS_MODULE=yoursite.settings
+    0 0 * * sun python /path/to/registration/bin/delete_expired_users.py
+
+See the method ``delete_expired_users`` of the ``RegistrationManager``
+class in ``registration/models.py`` for further documentation.
+
+"""
+
+if __name__ == '__main__':
+    from registration.models import RegistrationProfile
+    RegistrationProfile.objects.delet_expired_users()

registration/conf/locale/en/LC_MESSAGES/django.po

+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr ""
+
+#: forms.py:41
+msgid "email address"
+msgstr ""
+
+#: forms.py:43
+msgid "password"
+msgstr ""
+
+#: forms.py:45
+msgid "password (again)"
+msgstr ""
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr ""
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr ""
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr ""
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr ""
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr ""
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+
+#: models.py:188
+msgid "user"
+msgstr ""
+
+#: models.py:189
+msgid "activation key"
+msgstr ""
+
+#: models.py:194
+msgid "registration profile"
+msgstr ""
+
+#: models.py:195
+msgid "registration profiles"
+msgstr ""

registration/forms.py

 """
 
 
-import re
-
 from django import newforms as forms
+from django.core.validators import alnum_re
+from django.utils.translation import ugettext as _
 from django.contrib.auth.models import User
 
 from registration.models import RegistrationProfile
 
 # 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.
+# 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' }
 
 
-username_re = re.compile(r'^\w+$')
-
-
 class RegistrationForm(forms.Form):
     """
     Form for registering a new user account.
     
-    Validates that the password is entered twice and matches,
-    and that the username is not already taken.
+    Validates that the request username is not already in use, and
+    requires the password to be entered twice to catch typos.
+    
+    Subclasses should feel free to add any additional validation they
+    need, but should either preserve the base ``save()`` or implement
+    a ``save()`` which accepts the ``profile_callback`` keyword
+    argument and passes it through to
+    ``RegistrationProfile.objects.create_inactive_user()``.
     
     """
     username = forms.CharField(max_length=30,
                                widget=forms.TextInput(attrs=attrs_dict),
-                               label=u'Username')
+                               label=_(u'username'))
     email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
                                                                maxlength=200)),
-                             label=u'Email address')
+                             label=_(u'email address'))
     password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
-                                label=u'Password')
+                                label=_(u'password'))
     password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
-                                label=u'Password (again, to catch typos)')
-    tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
-                             label=u'I have read and agree to the Terms of Service')
+                                label=_(u'password (again)'))
     
     def clean_username(self):
         """
         in use.
         
         """
-        if 'username' in self.cleaned_data:
-            if not username_re.search(self.cleaned_data['username']):
-                raise forms.ValidationError(u'Usernames can only contain letters, numbers and underscores')
-            try:
-                user = User.objects.get(username__exact=self.cleaned_data['username'])
-            except User.DoesNotExist:
-                return self.cleaned_data['username']
-            raise forms.ValidationError(u'This username is already taken. Please choose another.')
+        if not alnum_re.search(self.cleaned_data['username']):
+            raise forms.ValidationError(_(u'Usernames can only contain letters, numbers and underscores'))
+        try:
+            user = User.objects.get(username__exact=self.cleaned_data['username'])
+        except User.DoesNotExist:
+            return self.cleaned_data['username']
+        raise forms.ValidationError(_(u'This username is already taken. Please choose another.'))
     
     def clean_password2(self):
         """
         Validates that the two password inputs match.
         
         """
-        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data and \
-           self.cleaned_data['password1'] == self.cleaned_data['password2']:
+        if self.cleaned_data['password1'] == self.cleaned_data['password2']:
             return self.cleaned_data['password2']
-        raise forms.ValidationError(u'You must type the same password each time')
+        raise forms.ValidationError(_(u'You must type the same password each time'))
+    
+    def save(self, profile_callback=None):
+        """
+        Creates the new ``User`` and ``RegistrationProfile``, and
+        returns the ``User``.
+        
+        This is essentially a light wrapper around
+        ``RegistrationProfile.objects.create_inactive_user()``,
+        feeding it the form data and a profile callback (see the
+        documentation on ``create_inactive_user()`` for details) if
+        supplied.
+        
+        """
+        new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
+                                                                    password=self.cleaned_data['password1'],
+                                                                    email=self.cleaned_data['email'],
+                                                                    profile_callback=profile_callback)
+        return new_user
+
+
+class RegistrationFormTermsOfService(RegistrationForm):
+    """
+    Subclass of ``RegistrationForm`` which adds a required checkbox
+    for agreeing to a site's Terms of Service.
+    
+    """
+    tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
+                             label=_(u'I have read and agree to the Terms of Service'))
     
     def clean_tos(self):
         """
         """
         if self.cleaned_data.get('tos', False):
             return self.cleaned_data['tos']
-        raise forms.ValidationError(u'You must agree to the terms to register')
-
-    def save(self, profile_callback):
-        """
-        Creates the new ``User`` and ``RegistrationProfile``, and
-        returns the ``User``.
-
-        Passes ``profile_callback`` through to the user-creation
-        function, to be used in creating a site-specific profile for
-        the new ``User``.
-
-        """
-        new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
-                                                                    password=self.cleaned_data['password1'],
-                                                                    email=self.cleaned_data['email'],
-                                                                    profile_callback=profile_callback)
-        return new_user
+        raise forms.ValidationError(_(u'You must agree to the terms to register'))
 
 
 class RegistrationFormUniqueEmail(RegistrationForm):
         site.
         
         """
-        if 'email' in self.cleaned_data:
-            try:
-                user = User.objects.get(email__exact=self.cleaned_data['email'])
-            except User.DoesNotExist:
-                return self.cleaned_data['email']
-            raise forms.ValidationError(u'This email address is already in use. Please supply a different email address.')
+        try:
+            user = User.objects.get(email__exact=self.cleaned_data['email'])
+        except User.DoesNotExist:
+            return self.cleaned_data['email']
+        raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.'))
 
 
 class RegistrationFormNoFreeEmail(RegistrationForm):
     Subclass of ``RegistrationForm`` which disallows registration with
     email addresses from popular free webmail services; moderately
     useful for preventing automated spam registrations.
-
-    To change the list of banned domains, override the attribute
-    ``bad_domains``.
+    
+    To change the list of banned domains, subclass this form and
+    override the attribute ``bad_domains``.
     
     """
     bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com',
                    'googlemail.com', 'hotmail.com', 'hushmail.com',
-                   'live.com', 'msn.com', 'mail.ru']
+                   'msn.com', 'mail.ru', 'mailinator.com', 'live.com']
     
     def clean_email(self):
         """
         webmail domains.
         
         """
-        if 'email' in self.cleaned_data:
-            email_domain = self.cleaned_data['email'].split('@')[1]
-            if email_domain in self.bad_domains:
-                raise forms.ValidationError(u'Registration using free email addresses is prohibited. Please supply a different email address.')
-            return self.cleaned_data['email']
+        email_domain = self.cleaned_data['email'].split('@')[1]
+        if email_domain in self.bad_domains:
+            raise forms.ValidationError(_(u'Registration using free email addresses is prohibited. Please supply a different email address.'))
+        return self.cleaned_data['email']

registration/models.py

 """
-A registration profile model and associated manager.
-
-The ``RegistrationProfile`` model and especially its custom manager
-implement nearly all the logic needed to handle user registration and
-account activation, so before implementing something in a view or
-form, check here to see if they can take care of it for you.
-
-Also, be sure to see the note on ``RegistrationProfile`` about use of the
-``AUTH_PROFILE_MODULE`` setting.
+A model (``RegistrationProfile``) for storing user-registration data,
+and an associated custom manager (``RegistrationManager``).
 
 """
 
 
 from django.conf import settings
 from django.db import models
-from django.template import Context, loader
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth.models import User
 from django.contrib.sites.models import Site
 
     """
     def activate_user(self, activation_key):
         """
-        Given the activation key, makes a ``User``'s account active if the
-        activation key is valid and has not expired.
+        Validates an activation key and activates the corresponding
+        ``User`` if valid.
         
-        Returns the ``User`` if successful, or False if the account was
-        not found or the key had expired.
+        If the key is valid and has not expired, returns the ``User``
+        after activating.
+        
+        If the key is not valid or has expired, returns ``False``.
+        
+        If the key is valid but the ``User`` is already active,
+        returns the ``User``.
         
         """
         # Make sure the key we're trying conforms to the pattern of a
-        # SHA1 hash; if it doesn't, no point even trying to look it up
-        # in the DB.
+        # SHA1 hash; if it doesn't, no point trying to look it up in
+        # the database.
         if SHA1_RE.search(activation_key):
             try:
-                user_profile = self.get(activation_key=activation_key)
+                profile = self.get(activation_key=activation_key)
             except self.model.DoesNotExist:
                 return False
-            if not user_profile.activation_key_expired():
-                # Account exists and has a non-expired key. Activate it.
-                user = user_profile.user
+            if not profile.activation_key_expired():
+                user = profile.user
                 user.is_active = True
                 user.save()
                 return user
         return False
     
-    def create_inactive_user(self, username, password, email, send_email=True, profile_callback=None):
+    def create_inactive_user(self, username, password, email,
+                             send_email=True, profile_callback=None):
         """
-        Creates a new ``User`` and a new ``RegistrationProfile`` for that
-        ``User``, generates an activation key, and mails it.
+        Creates a new, inactive ``User``, generates a
+        ``RegistrationProfile`` and emails its activation key to the
+        ``User``. Returns the new ``User``.
         
-        Pass ``send_email=False`` to disable sending the email.
-
+        To disable the email, call with ``send_email=False``.
+        
         To enable creation of a custom user profile along with the
         ``User`` (e.g., the model specified in the
         ``AUTH_PROFILE_MODULE`` setting), define a function which
         knows how to create and save an instance of that model with
         appropriate default values, and pass it as the keyword
         argument ``profile_callback``. This function should accept one
-        keyword argument: ``user``, the ``User`` to relate the profile
-        to.
+        keyword argument:
+
+        ``user``
+            The ``User`` to relate the profile to.
         
         """
-        # Create the user.
         new_user = User.objects.create_user(username, email, password)
         new_user.is_active = False
         new_user.save()
         
-        # And finally create the registration profile.
         registration_profile = self.create_profile(new_user)
         
-        # Create site-specific profile, if specified.
         if profile_callback is not None:
             profile_callback(user=new_user)
         
         if send_email:
             from django.core.mail import send_mail
-            current_domain = Site.objects.get_current().domain
-            subject = "Activate your new account at %s" % current_domain
-            message_template = loader.get_template('registration/activation_email.txt')
-            message_context = Context({ 'site_url': 'http://%s/' % current_domain,
-                                        'activation_key': registration_profile.activation_key,
-                                        'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS })
-            message = message_template.render(message_context)
+            current_site = Site.objects.get_current()
+            
+            subject = render_to_string('registration/activation_email_subject.txt',
+                                       { 'site': current_site })
+            # Email subject *must not* contain newlines
+            subject = ''.join(subject.splitlines())
+            
+            message = render_to_string('registration/activation_email.txt',
+                                       { 'activation_key': registration_profile.activation_key,
+                                         'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
+                                         'site': current_site })
+            
             send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [new_user.email])
         return new_user
     
     def create_profile(self, user):
         """
-        Given a ``User``, creates, saves and returns a
-        ``RegistrationProfile`` for that ``User``, generating the
-        activation key from a combination of the ``User``'s username
-        and a random salt.
+        Creates a ``RegistrationProfile`` for a given
+        ``User``. Returns the ``RegistrationProfile``.
+        
+        The activation key for the ``RegistrationProfile`` will be a
+        SHA1 hash, generated from a combination of the ``User``'s
+        username and a random salt.
         
         """
         salt = sha.new(str(random.random())).hexdigest()[:5]
         
     def delete_expired_users(self):
         """
-        Removes unused profiles and their associated accounts.
-
-        This is provided largely as a convenience for maintenance
-        purposes; if a ``RegistrationProfile``'s key expires without the
-        account being activated, then both the ``RegistrationProfile`` and
-        the associated ``User`` become clutter in the database, and (more
-        importantly) it won't be possible for anyone to ever come back
-        and claim the username. For best results, set this up to run
-        regularly as a cron job.
+        Removes expired instances of ``RegistrationProfile`` and their
+        associated ``User``s.
         
-        If you have a ``User`` whose account you want to keep in the
-        database even though it's inactive (say, to prevent a
-        troublemaker from accessing or re-creating his account), just
-        delete that ``User``'s ``RegistrationProfile`` and this method will
-        leave it alone.
+        Accounts to be deleted are identified by searching for
+        instances of ``RegistrationProfile`` with expired activation
+        keys, and then checking to see if their associated ``User``
+        instances have the field ``is_active`` set to ``False``; any
+        ``User`` who is both inactive and has an expired activation
+        key will be deleted.
+        
+        It is recommended that this method be executed regularly as
+        part of your routine site maintenance; the file
+        ``bin/delete_expired_users.py`` in this application provides a
+        standalone script, suitable for use as a cron job, which will
+        call this method.
+        
+        Regularly clearing out accounts which have never been
+        activated serves two useful purposes:
+        
+        1. It alleviates the ocasional need to reset a
+           ``RegistrationProfile`` and/or re-send an activation email
+           when a user does not receive or does not act upon the
+           initial activation email; since the account will be
+           deleted, the user will be able to simply re-register and
+           receive a new activation key.
+        
+        2. It prevents the possibility of a malicious user registering
+           one or more accounts and never activating them (thus
+           denying the use of those usernames to anyone else); since
+           those accounts will be deleted, the usernames will become
+           available for use again.
+        
+        If you have a troublesome ``User`` and wish to disable their
+        account while keeping it in the database, simply delete the
+        associated ``RegistrationProfile``; an inactive ``User`` which
+        does not have an associated ``RegistrationProfile`` will not
+        be deleted.
         
         """
         for profile in self.all():
             if profile.activation_key_expired():
                 user = profile.user
                 if not user.is_active:
-                    user.delete() # Removing the ``User`` will remove the ``RegistrationProfile``, too.
+                    user.delete()
 
 
 class RegistrationProfile(models.Model):
     """
-    Simple profile model for a ``User``, storing an activation key for the
-    account.
+    A simple profile which stores an activation key for use during
+    user account registration.
+    
+    Generally, you will not want to interact directly with instances
+    of this model; the provided manager includes methods
+    for creating and activating new accounts, as well as for cleaning
+    out accounts which have never been activated.
     
     While it is possible to use this model as the value of the
     ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do
-    so. This model is intended solely to store some data needed for
-    user registration, and can do that regardless of what you set in
-    ``AUTH_PROFILE_MODULE``, so if you want to use user profiles in a
-    project, it's far better to develop a customized model for that
-    purpose and just let this one handle registration.
+    so. This model's sole purpose is to store data temporarily during
+    account registration and activation, and a mechanism for
+    automatically creating an instance of a site-specific profile
+    model is provided via the ``create_inactive_user`` on
+    ``RegistrationManager``.
     
     """
-    user = models.ForeignKey(User, unique=True)
-    activation_key = models.CharField(maxlength=40)
+    user = models.ForeignKey(User, unique=True, verbose_name=_('user'))
+    activation_key = models.CharField(_('activation key'), maxlength=40)
     
     objects = RegistrationManager()
     
+    class Meta:
+        verbose_name = _('registration profile')
+        verbose_name_plural = _('registration profiles')
+    
     class Admin:
         list_display = ('__str__', 'activation_key_expired')
         search_fields = ('user__username', 'user__first_name')
         
-    def __str__(self):
-        return "Registration information for %s" % self.user.username
+    def __unicode__(self):
+        return u"Registration information for %s" % self.user
     
     def activation_key_expired(self):
         """
         Determines whether this ``RegistrationProfile``'s activation
-        key has expired, based on the value of the setting
-        ``ACCOUNT_ACTIVATION_DAYS``.
+        key has expired.
+        
+        Returns ``True`` if the key has expired, ``False`` otherwise.
+        
+        Key expiration is determined by the setting
+        ``ACCOUNT_ACTIVATION_DAYS``, which should be the number of
+        days a key should remain valid after an account is registered.
         
         """
         expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)

registration/templates/registration/login.html

 
 {% block main_content %}
 
-{% if form.errors %}
+{% if errors %}
 <p class="error">Please correct the errors below:</p>
 {% endif %}
 
 {% endblock %}
 
 {% block sidebar %}
-<p>If you don't have an account, you can <a href="/accounts/register/">sign up</a> for one; it's free, and you'll get access to a bunch of nifty features.</p>
+<p>If you don't have an account, you can <a href="{% url registration_register %}">sign up</a> for one; it's free, and you'll get access to a bunch of nifty features.</p>
 {% endblock %}

registration/views.py

     """
     Activates a ``User``'s account, if their key is valid and hasn't
     expired.
-
-    Context::
+    
+    By default, uses the template ``registration/activate.html``; to
+    change this, pass the name of a template as the keyword argument
+    ``template_name``.
+    
+    Context:
+    
         account
-            The ``User`` object corresponding to the account,
-            if the activation was successful.
-
+            The ``User`` object corresponding to the account, if the
+            activation was successful. ``False`` if the activation was
+            not successful.
+    
         expiration_days
-            The number of days for which activation keys stay valid.
-
-    Template::
-        registration/activate.html
+            The number of days for which activation keys stay valid
+            after registration.
+    
+    Template:
+    
+        registration/activate.html or ``template_name`` keyword
+        argument.
     
     """
     activation_key = activation_key.lower() # Normalize before trying anything with it.
                               context_instance=RequestContext(request))
 
 
-def register(request, success_url='/accounts/register/complete/', form_class=RegistrationForm, profile_callback=None):
+def register(request, success_url='/accounts/register/complete/',
+             form_class=RegistrationForm, profile_callback=None,
+             template_name='registration/registration_form.html'):
     """
     Allows a new user to register an account.
     
-    On successful registration, an email will be sent to the new user
-    with an activation link to click to make the account active. This
-    view will then redirect to ``success_url``, which defaults to
-    '/accounts/register/complete/'. This application has a URL pattern
-    for that URL and routes it to the ``direct_to_template`` generic
-    view to display a short message telling the user to check their
-    email for the account activation link.
-
-    By default, uses ``registration.forms.RegistrationForm`` as the
-    registration form; to change this, pass a different form class as
-    the ``form_class`` keyword argument. The form class you specify
-    must create and return the new ``User``, and must accept the
-    keyword argument ``profile_callback`` (see below).
+    Following successful registration, redirects to either
+    ``/accounts/register/complete/`` or, if supplied, the URL
+    specified in the keyword argument ``success_url``.
+    
+    By default, ``registration.forms.RegistrationForm`` will be used
+    as the registration form; to change this, pass a different form
+    class as the ``form_class`` keyword argument. The form class you
+    specify must have a method ``save`` which will create and return
+    the new ``User``, and that method must accept the keyword argument
+    ``profile_callback`` (see below).
     
     To enable creation of a site-specific user profile object for the
     new user, pass a function which will create the profile object as
     the keyword argument ``profile_callback``. See
     ``RegistrationManager.create_inactive_user`` in the file
-    ``models.py`` for details on what this function should do.
+    ``models.py`` for details on how to write this function.
     
-    Context::
+    By default, uses the template
+    ``registration/registration_form.html``; to change this, pass the
+    name of a template as the keyword argument ``template_name``.
+    
+    Context:
+    
         form
-            The registration form
+            The registration form.
     
-    Template::
-        registration/registration_form.html
+    Template:
+    
+        registration/registration_form.html or ``template_name``
+        keyword argument.
     
     """
     if request.method == 'POST':
             return HttpResponseRedirect(success_url)
     else:
         form = form_class()
-    return render_to_response('registration/registration_form.html',
+    return render_to_response(template_name,
                               { 'form': form },
                               context_instance=RequestContext(request))
-
 from distutils.core import setup
 
 setup(name='registration',
-      version='0.2',
+      version='0.3',
       description='User-registration application for Django',
       author='James Bennett',
       author_email='james@b-list.org',
       url='http://code.google.com/p/django-registration/',
       packages=['registration'],
-      package_dir={ 'registration': 'registration' },
-      package_data={ 'registration': ['templates/registration/*.*'] },
       classifiers=['Development Status :: 4 - Beta',
                    'Environment :: Web Environment',
                    'Intended Audience :: Developers',