.. _login-label:
User Authentication
===================
The aim of this next part of the tutorial is to get you familiar with the user authentication mechanisms provided by Django. We'll by using the ``auth`` application provided as part of a standard Django installation in package ``django.contrib.auth``. According to `Django's official documentation on Authentication `_, the application consists of the following aspects.
- *Users.*
- *Permissions:* a series of binary flags (e.g. yes/no) determining what a user may or may not do.
- *Groups:* a method of applying permissions to more than one user.
- A configurable *password hashing system:* a must for ensuring data security.
- *Forms and view tools for logging in users,* or restricting content.
- A *pluggable backend system,* allowing you to provide your own authentication-related functionality.
There's lots that Django can do for you in the area of user authentication. We'll be covering the basics to get you started. This'll help you build your confidence with the available tools and their underlying concepts.
Setting up Authentication
-------------------------
Before you can begin to play around with Django's authentication offering, you'll need to make sure that the relevant settings are present in your Rango project's ``settings.py`` file.
Within the ``settings.py`` file find the ``INSTALLED_APPS`` tuple and check that ``django.contrib.auth`` and ``django.contrib.contenttypes`` are listed, so that it looks like the code below:
.. code-block:: python
INSTALLED_APPS = (
'django.contrib.auth', # THIS LINE SHOULD BE PRESENT AND UNCOMMENTED
'django.contrib.contenttypes', # THIS LINE SHOULD BE PRESENT AND UNCOMMENTED
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'rango',
)
While ``django.contrib.auth`` provides Django with access to the authentication system, ``django.contrib.contenttypes`` is used by the authentication application to track models installed in your database. Check out the `Official Django documentation for more details `_ on what ``django.contrib.contenttypes`` is and does to make your life easier.
.. note:: Remember, if you had to add either one of the ``auth`` or ``contenttypes`` applications to your ``INSTALLED_APPS`` tuple, you will need to resynchronise your database with the ``$ python manage.py syncdb`` command.
Passwords are stored by default in Django using the `PBKDF2 algorithm `_, providing a good level of security for your user's data. You can read more about this as part of the `official Django documentation on how django stores passwords `_. The documentation also provides an explanation of how to use different password hashers if you require a greater level of security.
The ``User`` Model
------------------
The core of Django's authentication system is the ``User`` object, located at ``django.contrib.auth.models.User``. A ``User`` object represents each of the people interacting with a Django application. The `Django documentation on User objects `_ states that they are used to allow aspects of the authentication system like access restriction, registration of new user profiles and the association of creators with site content.
The ``User`` model comes complete with five primary attributes. They are:
- the username for the user account;
- the account's password;
- the user's email address;
- the user's first name; and
- the user's surname.
The model also comes with other attributes such as ``is_active`` (which determines whether a particular account is active or not). Check the `official Django documentation on the user model `_ for a full list of attributes provided by the base ``User`` model.
Additional User Attributes
--------------------------
However, what if all the provided attributes that the ``User`` model provides isn't enough? For our Rango application, we want to include two more additional attributes for each user account. Specifically, we wish to include:
- a ``URLField``, allowing a user of Rango to specify their own website; and
- a ``ImageField``, which allows users to specify a picture for their user profile.
Fortunately, this is a relatively easy task to accomplish. This is achieved through the creation of an additional model in Rango's ``models.py`` file. Let's add the new model - add the following code.
.. code-block:: python
class UserProfile(models.Model):
# This line is required. Links UserProfile to a User model instance.
user = models.OneToOneField(User)
# The additional attributes we wish to include.
website = models.URLField(blank=True)
picture = models.ImageField(upload_to='profile_images', blank=True)
# Override the __unicode__() method to return out something meaningful!
def __unicode__(self):
return self.user.username
As we also reference the ``User`` model, we'll need to include the model into the ``models.py`` namespace. Add it with the following import statement at the top of the file.
.. code-block:: python
from django.contrib.auth.models import User
So, how do we accomplish our goal of adding additional user profile fields? This isn't achieved through inheritance, instead the ``UserProfile`` model inherits from Django's ``Model`` class and is linked to the base ``User`` class through a one-to-one relationship via attribute ``user``. This is because various applications may all want to use the User model and extend upon it in different ways.
For Rango, we've added two fields to complete our user profile, and provided a ``__unicode__()`` method to return a meaningful value when a unicode representation of a ``UserProfile`` model instance is requested.
For the two fields ``website`` and ``picture``, we have set ``blank=True`` for both. This allows each of the fields to be blank if necessary, meaning that users need not supply values for the attributes if they do not wish to.
Note that the ``ImageField`` field has an ``upload_to`` attribute. The value of this attribute is conjoined with the project's ``MEDIA_ROOT`` setting to provide a path with which uploaded profile images will be stored. For example, a ``MEDIA_ROOT`` of ``/tango_with_django_project/media/`` and ``upload_to`` attribute of ``profile_images`` will result in all profile images being stored in the directory ``/tango_with_django_project/media/profile_images/``.
.. warning:: The Django ``ImageField`` field makes use of the *Python Imaging Library (PIL).* Back in Chapter :ref:`requirements-label`, we discussed installing PIL along with Django to your setup. If you haven't got PIL installed, you'll need to install it now. If you don't, you'll be greeted with exceptions stating that the module ``pil`` cannot be found!
With our ``UserProfile`` model defined, we now edit Rango's ``admin.py`` file to include the new ``UserProfile`` model in the Django administration web interface. In the ``admin.py`` file, add the following line.
.. code-block:: python
admin.site.register(UserProfile)
You also need to import the ``UserProfile`` model by adding one of the following lines at the top of the ``admin.py`` file. Choose which one you like - the first imports ``UserProfile`` with a separate import statement, while the second combines the import of ``UserProfile`` with Rango models that we have used previously in ``admin.py``.
.. code-block:: python
# Import the UserProfile model individually.
from rango.models import UserProfile
# Import the UserProfile model with Category and Page.
# If you choose this option, you'll want to modify the import statement you've already got to include UserProfile.
from rango.models import Category, Page, UserProfile
.. note:: Remember that your database must be synchronised with the creation of a new model. Run ``$ python manage.py syncdb`` from your terminal to synchronise the new ``UserProfile`` model. This process involves Django creating one or more underlying database tables for the given model. Forgetting to synchronise your changes will result in errors explaining that the required database tables cannot be found.
Creating a *User Registration* View and Template
------------------------------------------------
With our authentication infrastructure laid out, we can now begin to build onto it by providing users of our application with the opportunity to create new user accounts. We will achieve this via the creation of a new view and template combination.
.. note:: We feel it's important to note that there are several off-the-shelf user registration packages available for you to download and use in your Django projects. Examples include the `Django Registration application `_, and you can also check out the table on `this webpage `_ which lists other registration packages. While these exist, we'll be showing you how to set up everything from scratch. While this is at odds with the DRY principle, it is also important to get a feeling for the user authentication package and feature. It will also re-enforce your understanding of working with forms, how to extend upon the user model, and how to upload media.
To set everything the user registration functionality will we go through the following steps:
#. Create a ``UserForm`` and ``UserProfileForm``.
#. Add a view to handle the creation of a new user.
#. Create a template that displays the ``UserForm`` and ``UserProfileForm``.
#. Map a URL to the view created.
#. Link the index page to the register page
.. _login-formclasses-label:
Creating the ``UserForm`` and ``UserProfileForm``
.................................................
In ``rango/forms.py``, we now need to create two classes inheriting from ``forms.ModelForm``. We'll be creating one for the base ``User`` class, as well as one for the new ``UserProfile`` model that we just created. The two ``ModelForm`` inheriting classes allow us to display a HTML form displaying the necessary form fields for a particular model, taking away a significant amount of work for us. Neat!
In ``rango/forms.py``, let's create our two classes which inherit from ``forms.ModelForm``. Add the following code to the module.
.. code-block:: python
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username', 'email', 'password')
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('website', 'picture')
You'll notice that within both classes we create, we add a `nested `_ ``Meta`` class. As `the name of the nested class may suggest `_, anything within a nested ``Meta`` class describes additional properties about the particular ``ModelForm`` class it belongs to. Each ``Meta`` class must at a bare minimum supply a ``model`` field, which references back to the model the ``ModelForm`` inheriting class should relate to. Our ``UserForm`` class is therefore associated with the ``User`` model, for example. By default, Django then renders a HTML form for *all* fields within the associated model.
However, there may be scenarios where we would not want a user to provide information for *all* fields within the associated model. For example, certain form fields may need to be filled in automatically by your code - such as in the ``UserProfileForm``. Recall that the ``UserProfile`` model contains a ``user`` attribute, providing a one-to-one relationship to the ``User`` model. We don't want users to see this abstraction - we want Rango to handle it for them! With the ``fields`` attribute, we can fine tune what fields the user sees in a rendered form. ``UserProfileForm`` will therefore display entries for the ``website`` and ``picture`` fields, but will not provide anything for the ``user`` field.
You'll also notice that ``UserForm`` includes a definition of the ``password`` attribute. While a ``User`` model instance contains a ``password`` attribute by default, the rendered HTML form element is of the incorrect type. If a user types a password, the password will be visible. By updating the ``password`` attribute definition, we can then specify that the ``CharField`` instance should hide a user's input from prying eyes through use of the ``PasswordInput()`` widget.
You shouldn't forget to include the required classes at the top of the ``forms.py`` module!
.. code-block:: python
from rango.models import UserProfile
from django.contrib.auth.models import User
Creating the ``register()`` View
................................
Next we need to handle both the rendering of the form, and the processing of form input data. Within Rango's ``views.py`` file, add the following view function:
.. code-block:: python
from rango.forms import UserForm, UserProfileForm
def register(request):
# Like before, get the request's context.
context = RequestContext(request)
# A boolean value for telling the template whether the registration was successful.
# Set to False initially. Code changes value to True when registration succeeds.
registered = False
# If it's a HTTP POST, we're interested in processing form data.
if request.method == 'POST':
# Attempt to grab information from the raw form information.
# Note that we make use of both UserForm and UserProfileForm.
user_form = UserForm(data=request.POST)
profile_form = UserProfileForm(data=request.POST)
# If the two forms are valid...
if user_form.is_valid() and profile_form.is_valid():
# Save the user's form data to the database.
user = user_form.save()
# Now we hash the password with the set_password method.
# Once hashed, we can update the user object.
user.set_password(user.password)
user.save()
# Now sort out the UserProfile instance.
# Since we need to set the user attribute ourselves, we set commit=False.
# This delays saving the model until we're ready to avoid integrity problems.
profile = profile_form.save(commit=False)
profile.user = user
# Did the user provide a profile picture?
# If so, we need to get it from the input form and put it in the UserProfile model.
if 'picture' in request.FILES:
profile.picture = request.FILES['picture']
# Now we save the UserProfile model instance.
profile.save()
# Update our variable to tell the template registration was successful.
registered = True
# Invalid form or forms - mistakes or something else?
# Print problems to the terminal.
# They'll also be shown to the user.
else:
print user_form.errors, profile_form.errors
# Not a HTTP POST, so we render our form using two ModelForm instances.
# These forms will be blank, ready for user input.
else:
user_form = UserForm()
profile_form = UserProfileForm()
# Render the template depending on the context.
return render_to_response(
'rango/register.html',
{'user_form': user_form, 'profile_form': profile_form, 'registered': registered},
context)
Is the view a lot more complex? It might look so at first, but it isn't really. The only added complexity from our previous ``add_category()`` view is the need to handle two distinct ``ModelForm`` instances - one for the ``User`` model, and one for the ``UserProfile`` model. We also need to handle a user's profile image, if he or she chooses to upload one.
We also establish a link between the two model instances that we create. After creating a new ``User`` model instance, we reference it in the ``UserProfile`` instance with the line ``profile.user = user``. This is where we populate the ``user`` attribute of the ``UserProfileForm`` form, which we hid from users in Section :ref:`login-formclasses-label`.
Creating the *Registration* Template
....................................
Now create a new template file, ``rango/register.html`` and add the following code:
.. code-block:: html
Rango
Register with Rango
{% if registered %}
Rango says: thank you for registering!
Return to the homepage.
{% else %}
Rango says: register here!
{% endif %}
This HTML template makes use of the ``register`` variable we used in our view indicating whether registration was successful or not. Note that ``registered`` must be ``False`` in order for the template to display the registration form - otherwise, apart from the title, only a success message is displayed.
.. warning::
You should be aware of the ``enctype`` attribute for the ``