An Introduction to Django - Part 3


Nathan Osman's Gravatar

Nathan Osman
published Sept. 3, 2013, 11:13 a.m.


Although it has been nearly three months since the last two articles in my Django series, I am pleased to present part three. We continue to develop Track Me, a simple web application that allows users to post short status updates about what they're doing.

Forms

Anyone who has worked with server-side scripting languages knows just how tedious and error-prone working with forms can be. Field enumeration, input validation, and control rendering are all essential tasks that must be done with security and reusability in mind. As is often the case, Django provides an easy way to work with forms - and even takes care of validating the input for you. In fact, if you need a form for entering or editing data in a model, Django can even generate the form for you using the information in the model.

Our web application requires the following forms:

  • user registration form
  • login form
  • new status update form

(Once this series is complete and you are looking for an extra challenge, I encourage you to add a fourth form - one for editing existing status updates.)

Model Forms

As mentioned in the previous section, Django can do a lot of work for us by generating a form using the information in an existing model. Django will choose the appropriate form field based on the type of each model field, although there are some options for customization.

Forms are stored in a file named forms.py, so begin by creating updates/forms.py with the following contents:

from django import forms
from models import Update

class NewUpdateForm(forms.ModelForm):
    class Meta:
        model = Update

Most of the code above should be self-explanatory. The class we have created, NewUpdateForm, provides everything we need to render an HTML form, perform input validation, and store the newly created update. One important point worth mentioning - future Django versions will require that you include the names of fields you want included in the form. For now, Django will assume you want all fields included.

Using Forms

So now we have a form but how do we go about using it in the view? Begin by importing the form at the top of updates/views.py:

from forms import NewUpdateForm

You will also need to import the redirect function from Django, in addition to render:

from django.shortcuts import redirect, render

Next, modify the new function:

def new(request):
    if request.method == 'POST':
        form = NewUpdateForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('updates.views.index')
    else:
        form = NewUpdateForm()
    return render(request, 'updates/new.html', {
        'form': form,
    })

Quite a bit happens in this function, so we'll take a close look. First, we perform two different actions depending on whether the current HTTP request was a GET or POST request. In the former case, we simply create an empty form instance, which will result in a blank form - none of the fields will be filled in. If a POST request was received, an instance of the form is still created, but it is initialized with the POST variables supplied with the request.

If the is_valid form method returns True, then the input was successfully validated and the form can be processed. The form's save method will create a new instance of our model using the information supplied. The user is then redirected back to the home page. In the event that there was an error in the input (perhaps a field was left blank), the form is rendered with the existing information and an error message can be displayed to the user.

The Form Template

The view we just created references a template named new.html but we haven't created it yet. Because we have two other forms to create later, it makes sense to create a base form that can be extended as needed. But before we can extend the form, we must first create it. Here is the base form template in its entirety (updates/templates/form.html):

{% extends "base.html" %}

{% block contents %}
    <h2>{% block form_title %}{% endblock %}</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Continue</button>
    </form>
{% endblock %}

This template consists of a block for the title, a special token that protects the form against CSRF attacks, and the controls of the form (rendered inside <p> HTML elements).

Now we need to create the template for our first form. Create a file named updates/templates/updates/new.html with the following contents:

{% extends "form.html" %}

{% block title %}{{ block.super }} - New Update{% endblock %}
{% block form_title %}New Update{% endblock %}

Now try it out! If everything worked well, you should be able to select a user, enter an update, and see it displayed at the top of the home page. Now obviously you don't want random visitors to be able to choose an account to post an update under. We need to implement user registration.

Authentication

Django ships with a password-based authentication system which you are free to use in your own web applications. In fact, you have already used it - remember the administration interface from the first part of this series? First we need to create a brand new app for the views:

./manage.py startapp accounts

As you will recall, we must add this app to trackme/settings.py in order to use it:

INSTALLED_APPS = (
    ...
    'accounts',
    ...
)

We must also add the URLs for our new app to trackme/urls.py:

urlpatterns = patterns('',
    ...
    url(r'^accounts/', include('accounts.urls')),
    ...
)

Now because Django provides forms for user registration and login, we don't need to create those. In fact, Django even provides a view for logging in and out - so we only need to create a single view for user registration. Let's specify the URLs we will use (accounts/urls.py):

from django.conf.urls import patterns, url
from django.contrib.auth.views import login, logout
from django.core.urlresolvers import reverse_lazy

urlpatterns = patterns('accounts.views',
    url(r'^register/$', 'register'),
    url(r'^login/$',    login,
            {'template_name': 'accounts/login.html',}),
    url(r'^logout/$',   logout,
            {'next_page': reverse_lazy('updates.views.index'),}),
)

Whoa... what are we doing here? We import the login and logout views, which we directly assign to URLs further down. We also import reverse_lazy - what does that do? Well, urlpatterns maps URLs to views. The reverse_lazy function attempts to determine the URL of the provided view. In this case, we need to lookup the URL of the home page. We also provide the login view with the name of the template to use for rendering the login form.

Here is what the login form template looks like:

{% extends "form.html" %}

{% block title %}{{ block.super }} - Login{% endblock %}
{% block form_title %}Login{% endblock %}

User Registration

Now we are left with a single view to implement - register. This is relatively easy to implement - here are the contents of accounts/views.py:

from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import redirect, render

def register(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('django.contrib.auth.views.login')
    else:
        form = UserCreationForm()
    return render(request, 'accounts/register.html', {
        'form': form,
    })

This looks pretty similar to the new view in the updates app. We simply use the UserCreationForm instead of the NewUpdateForm and specify a different page to redirect to after registration. The template accounts/templates/accounts/register.html is pretty simple:

{% extends "form.html" %}

{% block title %}{{ block.super }} - Register{% endblock %}
{% block form_title %}Register{% endblock %}

At this point, you should be able to register new users and log them in. Unfortunately, you will quickly discover one small problem: after logging in, users are redirected to a page that does not exist. We can fix that by opening up trackme/settings.py and adding:

LOGIN_REDIRECT_URL = 'updates.views.index'

This indicates to Django where the user should end up after successfully logging in.

Linking to Views

You've probably noticed by now that there is no way for the user to actually visit these pages unless they know the exact URL of the page. We need to include links to these pages at the top of our base template. This can easily be done by adding the following to updates/templates/base.html:

<style>
  ...
  #nav {
    float: right;
    padding-right: 8px;
  }
</style>
...
<p id="nav">
  <a href="{% url "django.contrib.auth.views.login" %}">Login</a>
  |
  <a href="{% url "accounts.views.register" %}">Register</a>
</p>
<h2>...

We're Not Done Yet

Well, it's time to wrap up another article in this series. Our web application is not quite finished yet, so you will have to wait until the fourth and final article is published. However, the app is really starting to take shape and is quite usable in its current form.

Currently, we don't have a link in the navigation bar for creating new updates and users can still select any user when creating an update. We'll fix both of those and take a look at some other features in the next article.


NextAn Introduction to Django - Part 4