User Authenication and Login

  • The aims of this tutorial is to get you familiar with the User Authentications mechanisms provided by Django.

Django provides a number of helpful constructs when dealing with users. The first of these is Django’s User model/table. This model holds pretty standard user fields: username, last_name, first_name, email, password, is_staff, is_active, is_superuser, last_login, date_joined.

See https://docs.djangoproject.com/en/dev/topics/auth/ for more details about the User model.

To extend the User model, you will need to add a UserProfile model/table, which has a one-to-one relationship with Django’s User model/table. In the UserProfile table, you can store additional details about the user beyond what is provided by the User model.

Note: make sure that ‘django.contrib.auth’ and ‘django.contrib.contenttypes’ are listed in your INSTALLED_APPS setting.

You will also need to make sure that you have password hasher functions set up and included in your settings.py file:

PASSWORD_HASHERS = (
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.BCryptPasswordHasher',
        'django.contrib.auth.hashers.SHA1PasswordHasher',
        'django.contrib.auth.hashers.MD5PasswordHasher',
        'django.contrib.auth.hashers.CryptPasswordHasher',
)

Adding in the password hashers is really important. If this is not included then passwords will not be encrypted properly, and will lead to errors when users try to log in.

Creating the User Models/Tables

In Models.py, add in the following lines of code:

from django.contrib.auth.models import User


class UserProfile(models.Model):
        # This field is required.
        user = models.OneToOneField(User)
        # These fields are optional
        website = models.URLField(blank=True)
        picture = models.ImageField(upload_to='imgs', blank=True)

        def __unicode__(self):
                return self.user.username

In the UserProfile class, a one to one field is added to link the User and UserProfile. To add some details about the user, we’ve added a field to store their website URL and an image of the user. Both of these fields can be left empty (i.e. blank), and for the ImageField we need to set an upload_to path (where images uploaded will be saved to.. the path is relative to the MEDIA_ROOT path set in the settings.py). Make sure the MEDIA_ROOT/imgs directory exists.

In rango/admin.py import UserProfile and register the UserProfile model with admin, i.e. admin.site.register(UserProfile)

Now update the database:

python manage.py syncdb

Creating a User Registration View/Template

While off the shelf User Registrations packages for Django exist, such as the Django Registration app: http://code.google.com/p/django-registration/, it is good to familarize yourself with the process of handling user registrations.

To set up the views and templates required you will need to do the following:

  • Create a UserForm and UserProfileForm
  • Create a view to handle the creation of a user
  • Create a template that displays the UserForm and UserProfileForm.
  • Add in a url mapping to the view
  • Add a link in index.html to let users register.

In models.py add:

from django.forms import ModelForm

class UserForm(forms.ModelForm):
        class Meta:
                model = User
                fields = ["username", "email", "password"]

class UserProfileForm(forms.ModelForm):
        class Meta:
                model = UserProfile

In views.py add:

from django.contrib.auth.models import User
from rango.models import UserProfile
from rango.models import UserForm, UserProfileForm
from django.http import HttpResponse
from django.shortcuts import render_to_response
from tango_with_django_project.settings import MEDIA_ROOT

def register(request):
        context = RequestContext(request)
        registered = False
        if request.method == 'POST':
                uform = UserForm(data = request.POST)
                pform = UserProfileForm(data = request.POST)
                if uform.is_valid() and pform.is_valid():
                        user = uform.save()
                        # form brings back a plain text string, not an encrypted password
                        pw = user.password
                        # thus we need to use set password to encrypt the password string
                        user.set_password(pw)
                        user.save()
                        profile = pform.save(commit = False)
                        profile.user = user
                        profile.save()
                        save_file(request.FILES['picture'])
                        registered = True
                else:
                        print uform.errors, pform.errors
        else:
                uform = UserForm()
                pform = UserProfileForm()

        return render_to_response('rango/register.html', {'uform': uform, 'pform': pform, 'registered': registered }, context)


def save_file(file, path=''):
        filename = file._get_name()
        fd = open('%s/%s' % (MEDIA_ROOT, str(path) + str(filename)), 'wb' )
        for chunk in file.chunks():
                fd.write(chunk)
        fd.close()

For the register view you also need to handle the image upload. To do this you will need to add in the helper function save_file. This will download and save the file to your MEDIA_ROOT directory. To access the image later you will will be able to use it via the Development Media Server you set up via the MEDIA_URL.

The body of the template, register.html:

<BODY>
{% if registered %}
        <A href="/rango/">Rango</A> says: thank you for registering.
{% else %}
        rango says: register here!
        <FORM id="user_form" method="post" action="/rango/register/" enctype="multipart/form-data">
                {% csrf_token %}
                {{ uform.as_p }}
                {{ pform.as_p }}
                <INPUT type="submit" name="submit" value="submit" />
        </FORM>
{% endif %}
</BODY>

Note a couple of things here: ModelForms from Django provides a way to produce customized forms, with different widgets, labels, etc. These forms can be rendered as html in the template saving you lots of time messing about with html form code. Also, since the ModelForms are linked to Models, then they can save the data or obtain the data from the Model and save/display it.

You’ll notice that the html forms created include a “user” field. This is because the ModelForm created for UserProfile did not specify which fields to include within the html form, so it created all of them (i.e. user, website, picture). Specify which fields should be shown now, by updating the meta class within the UserProfileForm:

fields = ['website','picture']

This will now only include the website and picture fields on the user profile form. Note that in the register view function, once a user object has been created, it can then be assigned to the UserProfile with profile.user = user. This performs the necessary one to one mapping between the User and UserProfile model.

The ModelForms also provide details of any errors relating to the data entry, and whether the data meets all the Model conditions. In the previous section, you saw that the ModelForms can be customized to clean the data being pass through to handle such errors.

In the register view, the code handles a GET request by showing empty User and the UserProfile forms. The code also handles the POST request which is triggered when the user presses the submit button on the registration form. If there are errors with the data, then on POST, these will be detected, and the forms will be shown again, with data in them, along with error messages. Error messages are also printed to the console.

You should also note that you have added in an attribute called enctype into the <FORM ..> declaration and this attribute has been set to: “multipart/form-data”. This is important to include as it signals that the form will be composed of a number of parts and potentially contain files, images, videos, and other binaries.

In the template, an if statement is used to display the form if the user has not registered, otherwise to display the form. This is a handy way to include or exclude pieces of functionality.

Now that you have created the forms, view and template for registering, add the following urlpattern in rango/urls.py:

url(r'^register/$', views.register, name='register'),

And in the template index.html, add a link to register:

<a href="/rango/register/">Register Here</a>

Try out this extra functionality and register a user, then check it out in the admin interface. Hopefully, all the data will have been persisted to your the database, so now you’ll need to create the login functionality for your application.

Adding User Login Functionality

Create a login view, by adding the following code to rango/views.py:

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect

def user_login(request):
    context = RequestContext(request)
    if request.method == 'POST':
          username = request.POST['username']
          password = request.POST['password']
          user = authenticate(username=username, password=password)
          if user is not None:
              if user.is_active:
                  login(request, user)
                  # Redirect to index page.
                  return HttpResponseRedirect("rango/")
              else:
                  # Return a 'disabled account' error message
                  return HttpResponse("You're account is disabled.")
          else:
              # Return an 'invalid login' error message.
              print  "invalid login details " + username + " " + password
              return render_to_response('login.html', {}, context)
    else:
        # the login is a  GET request, so just show the user the login form.
        return render_to_response('login.html', {}, context)

Basically, the user_login view, tests to see if a POST request has been made, if so, it extracts the username and password from the POST, and then tries to authenticate the user, using Django’s authenticate function the user model.

If a User is returned, and is active then, using Django’s login function, the login event is handled (i.e. the user login is recorded, cookies are generated and sent to the client, which can be extract from subsequent requests.) Django also has a logout function which will securely log the user out (i.e. expire cookies, etc).

Next Django’s HttpResponseRedirect function is called to redirect the user to the login in page. This is a pretty useful, which lets you use pre-existing views. The rest of the login method handles the other cases (invalid login, disabled account or logging in)

Create a login.html template that will capture the username and password of the user:

<HTML>
<HEADER>
    <TITLE>Rango</TITLE>
</HEADER>
<BODY>
        <FORM id="login_form" method="post" action="/rango/login/">
        {% csrf_token %}
                Username:
                <input type="text" name="username" value="" id="username" size="50" />
            <br />
                Password:
                <input type="password" name="password" value="" id="password" size="50" />
                <br />
        <INPUT type="submit" name="submit" value="submit" />
        </FORM>
</BODY>
</HTML>

Make sure you add in the url mapping for the login view with:

rl(r'^login/$', views.user_login, name='login'),

Now, in index.html, add in register and login links:

<P>
<A href="/rango/register/">Register</A> |
<A href="/rango/login/">Login</A>
</P>

Restricting Access

To restrict access to views, there are two ways:

  • directly examining the request, or
  • using a convenience decorator function.

The direct way checks whether the request.user.is_authenticated() as follows:

def some_view(request):
        if not request.user.is_authenticated():
                return HttpResponseRedirect('/rango/login/')
        else:
                ...

Otherwise, you can use the decorator function @login_required. By adding the @login_required decorator function, before your view is executed Django will check and see if an authenticated user is currently logged in. If the user is logged in then they will be able to view the page, otherwise they will notified that they do not have access to that particular page.

To try out this functionality, create a view called, restricted in views.py and decorate it as follows:

from django.contrib.auth.decorators import login_required



@login_required
def restricted(request):
    return HttpResponse('Rango says: since you are an authenticated user you can view this restricted page.')

In rango/urls.py, map a url to this view, by adding the following code to urlpatterns:

url(r'restricted', views.restricted, name='restricted' )

To handle the case when a user has not logged in, you will need to set, LOGIN_URL in settings.py:

LOGIN_URL = '/rango/login'

The @login_required decorator will then redirect users not logged in to this url.

Logging out

To ensure that users can log out securely use Django’s logout function.

@login_required
def user_logout(request):
    context = RequestContext(request)
    logout(request)
    # Redirect back to index page.
    return HttpResponseRedirect('/rango/')

Now add in the url mapping: url(r’^logout/$’, views.user_logout, name=’logout’), and update the index.html template as follows:

<P>
        {% if user.is_authenticated %}
                Welcome {{ user.username }}  |
                <A href="/rango/restricted/">Restricted Page</A> |
                <A href="/rango/logout/">Logout</A>
        {% else %}
                <A href="/rango/register/">Register</A> |
                <A href="/rango/login/">Login</A>
        {% endif %}
</P>

Recall that in the index view the context is passed through to the template, part of this context includes a reference to the the user model. This means that you are able to access the data associated with the current user in the template, by referring to the user object. Here, the index template tests to see if the user is authenticated, and if they are displays their username, and provides a link to the restricted content: otherwise register and login options are presented.

In this part of the tutorial, only some of the User Authentication functions have been used to demonstrate how to register a user and how to log users in and out. However, it is worth learning more about User Authentication in Django at: https://docs.djangoproject.com/en/1.4/topics/auth/ as Django provides many other helpful functions and helper templates and views to handle common aspects of user authentication and registration.

Exercises

  • Customize the application so that only registered users can add/edit, while non-registered can only view/use the categories/pages
  • Handle lost passwords by adding a form that emails the password to the user