19 min read

An overview

Views are at the heart of Django and hold most of your application logic. They are nothing more than Python functions that take an HTTP request as input and return an HTTP response or error.

A mechanism called the dispatcher identifies an incoming URL against a set of URL patterns and their associated view functions. When a match is found, the associated view is called and the request gets handled.

Since many views follow a common strategy of loading an object or list, loading a template, rendering the template, and returning a response, Django offers a way of doing this without writing a view function. These generic views are called from the URL dispatcher and go right to the template.

Creating the application

Before we start looking at views and URLs, let’s create a sample application to experiment with. Since most books and examples use blog models as their demos, let’s keep things fresh by making our demo a press release application for a company website. The press release object will have a title, body, published date, and author name.

Create the data model

In the root directory of your project (in the directory projects/mycompany), create the press application by using the startapp command:

$ python manage.py startapp press

This will create a press folder in your site. Edit the mycompany/press/models.py file:

from django.db import models

class PressRelease(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
pub_date = models.DateTimeField()
author = models.CharField(max_length=100)

def __unicode__(self):
return self.title

Create the admin file

To take advantage of the automatic admin interface that Django gives us, we need to create a file called an admin file. Create a file called admin.py in the mycompany/press directory, adding these lines:

from django.contrib import admin
from mycompany.press.models import PressRelease

admin.site.register(PressRelease)

If you’ve used Django before version 1.0, this step is new. The admin configuration directives were taken out of the model and put into their own files starting in version 1.0.

Add the press and admin applications to your INSTALLED_APPS variable in the settings.py file:

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes',
'djan?go.contrib.sessions',
'django.contrib.sites',
'mycompany.press',
)

In the root directory of your project, run the syncdb command to add the new models to the database:

$ python manage.py syncdb

Because we have Django’s authentication system listed as one of our installed applications, the initial syncdb process will ask us if we want to create a superuser. Go ahead and create a superuser account; you will be using it later to access the admin site.

Configure the URLs

Finally, edit the mycompany/urls.py file:

from django.conf.urls.defaults import *
from django.contrib import admin

admin.autodiscover()

urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
)

Add data in the admin application

By adding django.contrib.admin to our INSTALLED_APPS setting and creating a URL mapping for it, we can access the admin site by browsing to http://localhost:8000/admin/.

Go into the admin app and add two or three press releases so that we have some sample data to work with:

Views, URLs, and Generic Views in Django 1.0

Mapping URLs to views

When Django accepts an incoming request, one of the first things it does is that it looks at the URL and tries to match it against a group of URL patterns. In order to identify patterns, Django uses regular expressions to see if the URLs follow a known format.

Consider these URLs:

http://localhost:8000/press/detail/1/

http://localhost:8000/press/detail/2/

These URLs appear to follow a pattern that they start with press/detail/ and end with a number that represents the ID of a press release. (Recall that we don’t work with the domain name portion of the URL. Django takes care of this automatically for us and just sends us everything that follows the domain name.)

With this pattern, we can add a new line to our mycompany/urls.py file:

from django.conf.urls.defaults import *
from django.contrib import admin

admin.autodiscover()

urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
(r'^press/detail/d+/$', 'mycompany.press.views.detail'),
)

If you’re not familiar with Python’s regular expressions, this new line may look a bit wonky. This is the most important part:

r'^press/detail/d+/$'

It reads like this: “A string that starts with press/detail/ and ends with one or more digits followed by a slash”.

The second segment of the new line is the view function that will get called when an incoming URL matches this pattern. In this case, it will be a function called detail in the mycompany/press/views.py file.

There’s only one problem with this pattern—it recognizes that a number will be at the end of the URL, but doesn’t do anything to pass that number to the view when it’s called.

We can use a Python regular expression group to capture that number:

urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^press/detail/(?P<pid>d+)/$',
'mycompany.press.views.detail'),
)

This grouping syntax looks really funky, but it’s easy to understand once you’ve seen it a few times. (?P ) is the Python syntax for a named group, which allows the regular expression to save the piece that matched, and put a label on it so that we can call it later. The <pid> part is where we assign the label of pid to the ID of the press release that was sent with the URL.

In the case of this URL, the named group pid will be equal to 2:

http://localhost:8000/press/detail/2/

Any named groups that we get from a URL are passed as arguments to our view function. In this example, our detail function in press/views.py will have a method signature like this:

def detail(request, pid):
p = PressRelease.object.get(id=pid)
..

There are two keyword arguments to the detail function, request and pid. (Django automatically passes the keyword request, which we’ll explore a little later.)

Because we used a named group in the URL configuration to capture the press release ID, it’s passed to our detail function as pid. You can use multiple named groups in your URL patterns to capture multiple pieces of information and pass them to your functions.

Note: URL configurations and patterns are usually referred to as URLConf.

Handling unmatched URL patterns

URLs are matched up with view functions when they match patterns, but what happens when a match isn’t found? This URL wouldn’t match the patterns we created because it doesn’t end in a number:

http://localhost:8000/press/detail/abc/

In this case, the URL dispatcher wouldn’t match against our pattern and would keep trying other patterns until a match is found. If no match is found, a 404 error is raised. If you have debug set to true (DEBUG=True) in your settings file, you’ll see an error message like this:

Splitting up the URL configurations

We created the URL configurations for the press application in the mycompany/urls.py file. While this is perfectly acceptable, sticking all the configurations into the main urls.py file can get unwieldy for large projects with many applications. It also isn’t very modular if we want to share applications with others or use applications that other people distribute.

Instead of writing the press release configuration in our main mycompany/urls.py file, let’s create a new file at mycompany/press/urls.py:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'^detail/(?P<pid>d+)/$', 'press.views.detail'),
)

This looks very similar to what we already have, but note that we’ve dropped press from the beginning of the regular expression. This line will match URLs that start with detail.

Open your mycompany/urls.py file and edit the highlighted line:

from django.conf.urls.defaults import *
from django.contrib import admin

admin.autodiscover()

urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
(r'^press/', include('mycompany.press.urls')),
)

We’ve changed the regular expression portion to match URLs that start with press/. If one is found, Django will hop over to the press/urls.py file to try to match the rest of the URL (without the press/ prefix).

With this setup, we are telling Django that any URLs that start with press will be handled in a separate urls.py file in the press directory.

Creating views

Now that we’re matching a URL to a view and passing it information, we can look at how a view is structured. Views have two rules you must follow:

  1. The view must accept the request object as its first argument.
  2. The view must return an HTTP response or an exception.

Beyond this, just remember that a view is a standard Python function and you can do just about anything in it that you can do in a Python program.

Accepting the request object

Our first rule for views states that a view must accept the request object as its first argument. What is this request object?

Django automatically creates the request object when a page is requested. It contains data about the incoming HTTP request such as the requestor’s IP address, user agent, request method, cookies, GET parameters, POST parameters, and so on. Everything you should need to know about an incoming request will be found in this object.

When you build your view functions, always specify request as the first keyword argument:

def detail(request):
# Python code here

If you forget to add request as the first parameter, you’ll know quickly because your view will fail to load with some kind of error message about the arguments (the exact error depends on what other keyword arguments you might be using).

Responding with an HTTP response

The second rule for views is that a view must return an HTTP response or an exception. Let’s start by talking about what an HTTP response is.

In order for a browser to understand how to render a web page, it looks at some special hidden information called headers, which is sent by the server along with the content or document being requested. These headers tell the browser information such as what kind of web server is sending the response, which version of the HTTP protocol is being used, how big the content is, and what kind of content is being sent.

Luckily, we don’t have to worry about most of this because the web server and Django take care of it for us. All we have to do is make sure we send the response out of our view using the HttpResponse method.

In your mycompany/press/views.py file, add the following lines:

from django.http import HttpResponse

def detail(request, pid):
return HttpResponse('This is just a test.')

Point your browser to http://localhost:8000/press/detail/1/. Here’s what it should look like:

Views, URLs, and Generic Views in Django 1.0

Obviously, our views are going to be more complicated than this one, but it illustrates how simple they can be.

Responding with an exception

The second part of our rule said that the view can respond with an exception instead of an HTTP response. When Django encounters an error during the processing of a view, we usually want to return a friendly error message to the user to let them know something went wrong (as opposed to just sending back a blank screen). Usually, these error messages are in the form of 404 or 500 Error pages.

404 errors are also known as page not found errors. Anyone who has spent time surfing the Web has undoubtedly encountered a 404 Error page when clicking an old link that is no longer valid. In traditional HTML publishing, 404 errors popped up when the user requested a filename that wasn’t found on the server (that’s where the “page” in “page not found” comes from). With Django, we don’t have URLs that represent filenames on the server, but we still return a 404 error when the user is looking for a resource that does not exist.

Django makes it easy to return a 404 page by returning the error using the HttpResponseNotFound function:

from django.http import HttpResponseNotFound

def detail(request, pid):
return HttpResponseNotFound('Page Not Found')

Similarly, requests that cause errors on the server are usually referred to as 500 errors. (500 is the standard HTTP response code for a server error.) Django also makes it easy to serve a 500 error:

from django.http import HttpResponseServerError

def detail(request, pid):
return HttpResponseServerError('An Error Has Occurred.')

Putting the views together

Now that we know how a view works and what it needs to do, let’s write the real view to work with our sample application.

Building the basic view

In your mycompany/press/views.py file, replace any contents with the following lines:

from django.http import HttpResponse
from django.http import HttpResponseNotFound
from mycompany.press.models import PressRelease

def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
try:
p = PressRelease.objects.get(id=pid)
return HttpResponse(p.title)
except PressRelease.DoesNotExist:
return HttpResponseNotFound('Press Release Not Found')

If you’d like to test it out, point your browser to http://localhost:8000/press/detail/1/. You should see the title of your press release. Change the number at the end of the press release to an ID that doesn’t exist (such as 99) and you should get a Page Not Found error.

This view doesn’t return a very pretty output, but it follows the rule that the view must serve an HTTP response or an error/exception. The try/except error handling to make sure the press release exists is kind of ugly. Luckily, Django gives us a more elegant way of handling it.

Cleaning up the error handling

Instead of putting a try/except block around the object lookup, Django has a get_object_or_404 method that will automatically raise an error if the object is not found.

Change the highlighted lines in your mycompany/press/views.py file:

from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from mycompany.press.models import PressRelease

def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
p = get_object_or_404(PressRelease, id=pid)
return HttpResponse(p.title)

That’s a much cleaner way of doing things!

Note: If you’re getting a list instead of an object, Django has a get_list_or_404 method that you can use. We’ll see this in a few pages.

Adding the template files

The last thing we need to do is add a way to load up the response with the output of a rendered template.

We’re going to load a template file, replace placeholders in that file with our data (called “rendering” the template), and then return the contents of the template as a string as an HTTP response.

We create a templates directory at mycompany/templates, and configured the settings.py file to tell Django where to find it:

TEMPLATE_DIRS = (
'/projects/mycompany/templates/',
)

Verify that you have configured your project this way before continuing. With this setting in place, we can load templates relative to this path.

Create a directory under the mycompany/templates directory called press. (It’s common practice to use subdirectories to group template files by the application they are associated with.)

Create a new file at mycompany/templates/press/detail.html and add these lines:

<html>
<head>
<title>{{ press.title }}</title>
</head>
<body>
<h1>{{ press.title }}</h1>
<p>
Author: {{ press.author }}<br/>
Date: {{ press.pub_date }}<br/>
</p>
<p>
{{ press.body }}
</p></body>
</html>

This simple template file has placeholders for our title, author, pub_date, and body fields. When the template is rendered, these placeholders will be replaced with their respective values.

Now that we have a template, we can tell the view to use it.

Adding the template to the view

In our mycompany/press/views.py file, let’s add a few lines to load our template. Replace the contents of your file with these lines:

from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template import loader, Context
from mycompany.press.models import PressRelease

def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
p = get_object_or_404(PressRelease, id=1)
t = loader.get_template('press/detail.html')
c = Context({'press': p})
rendered_template = t.render(c)
return HttpResponse(rendered_template)

In the function, we’re retrieving the press/detail.html template file and creating a special data object called Context. So for now, just understand that it passes data to the template so that it can be rendered. The context object in this example passes our press release object to the template in a variable called press.

Our template gets rendered into a string called rendered_template that is sent back to the browser via HttpResponse the same way we sent back simple lines of text in previous examples.

The rendered_template variable was used for clarity. You can omit it and just return the response like this:

def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
p = get_object_or_404(PressRelease, id=1)
t = loader.get_template('press/detail.html')
c = Context({'press': p})
return HttpResponse(t.render(c))

Point your browser to the URL http://localhost:8000/detail/1/. You should see something like this depending on what you entered earlier into the admin site as sample data:

Views, URLs, and Generic Views in Django 1.0

Creating the list view and template

In addition to displaying the detail for a specific press release, we’ll also need a way to display a list of press releases. The steps to add this will be very similar to what we just did to add our detail view.

In your mycompany/press/views.py file, add the highlighted lines:

from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import get_list_or_404
from django.template import loader, Context
from mycompany.press.models import PressRelease

def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
p = get_object_or_404(PressRelease, id=1)
t = loader.get_template('press/detail.html')
c = Context({'press': p})
return HttpResponse(t.render(c))

def press_list(request):
'''
Returns a list of press releases
'''
pl = get_list_or_404(PressRelease)
t = loader.get_template('press/list.html')
c = Context({'press_list': pl})
return HttpResponse(t.render(c))

In your mycompany/press/urls.py file, add the highlighted line:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'detail/(?P<pid>d+)/$','mycompany.press.views.detail'),
(r'list/$', 'mycompany.press.views.press_list'),
)

Any incoming request starting with press/ will be sent to our press/urls.py file. If the remaining part of the URL is list/, it will be handled by the press_list function in our press/views.py file. If the remaining part is detail/<number> (such as detail/1 or detail/2), it will be handled by the detail function.

Finally, create a new file at mycompany/templates/press/list.html:

<html>
<head>
<title>Press Releases</title>
</head>
<body>
<h1>Press Releases</h1>
<ul>
{% for press in press_list %}
<li>
<a href="/press/detail/{{ press.id }}/">
{{ press.title }}</a>
</li>
{% endfor %}
</ul>
</body>
</html>

Point your browser to the URL http://localhost:8000/press/list/. You should see something like this, depending on what you entered earlier into the admin site:

Views, URLs, and Generic Views in Django 1.0

Using generic views to shorten development time

What we’ve done so far in this article is pretty standard for web application development:

  • We created a view to load an object by its ID.
  • We created a view to load a list of objects.
  • We retrieved our object using the data sent in from the URL or retrieved a list of objects.
  • We loaded a template file.
  • We rendered the template.
  • We returned an HTTP response.

Because these actions are so common, Django has a way to cut out the whole step of writing a view, called generic views. Generic views are called from the URL configuration file, which allows you to go right from the URL pattern to your template.

Generic views come in a few types:

  • Simple
  • List/detail
  • Date-based
  • Create/update/delete

We won’t be covering the date-based or create/update/delete generic views. But after reading this article, you’ll be well-prepared to read about them in the online documentation.

Simple generic views

The two simple generic views that handle loading of a template don’t require any data lookup (going directly to a template) and redirecting from one URL to another.

Loading a template directly

If you just need to load and render a template when a URL is requested, you can use the direct_to_template generic view.

For example, let’s build a robots exclusion file (aka a robots.txt file) that search engine spiders will request at http://localhost:8000/robots.txt. (Search engines wouldn’t index pages on a localhost domain, but pretend for this example that they would.)

Since the file is rarely changed after being created, you may not want the overhead of a database lookup to serve it, so you just want to render a template when the URL is requested.

Create a new file at mycompany/templates/robots.txt and add these lines:

User-agent: *
Disallow: /admin

This very simple example will prevent spiders from trying to index your admin path (visit robotstxt.org for more info on how exclusion files work).

In your mycompany/urls.py file, add the highlighted lines:

from django.conf.urls.defaults import *
from django.contrib import admin

admin.autodiscover()

urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
(r'^press/', include('mycompany.press.urls')),
(r'^robots.txt$',
'django.views.generic.simple.direct_to_template',
'template': 'robots.txt'}),
)

Point your browser to the URL http://localhost:8000/robots.txt/. You’ll get a response that looks like this:

Views, URLs, and Generic Views in Django 1.0

LEAVE A REPLY

Please enter your comment!
Please enter your name here