Here again is a recurring theme of this book: at its worst, web development is boring and monotonous.
So far we’ve covered how Django tries to take away some of that monotony at the model and template layers, but web developers also experience this boredom at the view level.
Django’s generic views were to developed to ease that pain. They take certain common idioms and patterns in view development and abstract them so that you can quickly write common views of onto data without having to write too much code.
In fact, nearly every view example in the preceding chapters could be re-written with the help of generic views.
Django contains generic views to do the following:
Taken together, these views provide easy interfaces to perform the most common tasks developers encounter.
All of these views are used by creating configuration dictionaries in your URLconf files and passing those dictionaries as the third member of the URLconf tuple for a given pattern.
For example, here’s the URLconf for the simple weblog app that drives the blog on djangoproject.com:
from django.conf.urls.defaults import *
from django_website.apps.blog.models import Entry
info_dict = {
'queryset': Entry.objects.all(),
'date_field': 'pub_date',
}
urlpatterns = patterns('django.views.generic.date_based',
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'object_detail', dict(info_dict, slug_field='slug')),
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', info_dict),
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', info_dict),
(r'^(?P<year>\d{4})/$', 'archive_year', info_dict),
(r'^/?$', 'archive_index', info_dict),
)
As you can see, this URLconf defines a few options in info_dict. 'queryset' gives the generic view a QuerySet of objects to use (in this case, all of the Entry objects) and tells the generic view which model is being used. The remaining arguments to each generic view are taken from the named captures in the URLconf.
This is really all the “view” code for Django’s weblog! The only thing that’s left is writing a template.
Documentation of each generic view follows, along with a list of all keyword arguments that a generic view expects. Remember that as in the example above, arguments may either come from the URL pattern (as month, day, year, etc. do above) or from the additional-information dictionary (as for queryset, date_field, etc.).
Most generic views require the queryset key, which is a QuerySet instance; see the database API reference in Appendix 3 for more information about QuerySet objects.
Most views also take an optional extra_context dictionary that you can use to pass any auxiliary information you wish to the view. The values in the extra_context dictionary can be either functions (or other callables) or other objects. Functions are evaluated just before they are passed to the template.
The django.views.generic.simple module contains simple views to handle a couple of common cases: rendering a template when no view logic is needed, and issuing a redirect.
The function django.views.generic.simple.direct_to_template renders a given template, passing it a {{ params }} template variable, which is a dictionary of the parameters captured in the URL.
Given the following URL patterns:
urlpatterns = patterns('django.views.generic.simple',
(r'^foo/$', 'direct_to_template', {'template': 'foo_index.html'}),
(r'^foo/(?P<id>\d+)/$', 'direct_to_template', {'template': 'foo_detail.html'}),
)
a request to /foo/ would render the template foo_index.html, and a request to /foo/15/ would render the foo_detail.html with a context variable {{ params.id }} that is set to 15.
django.views.generic.simple.redirect_to redirects to another URL. The given URL may contain dictionary-style string formatting, which will be interpolated against the parameters captured in the URL.
If the given URL is None, Django will return an HTTP 410 (Gone) message.
This example redirects from /foo/<id>/ to /bar/<id>/:
urlpatterns = patterns('django.views.generic.simple',
('^foo/(?p<id>\d+)/$', 'redirect_to', {'url': '/bar/%(id)s/'}),
)
This example returns a 410 HTTP error for requests to /bar/:
urlpatterns = patterns('django.views.generic.simple',
('^bar/$', 'redirect_to', {'url': None}),
)
Although the simple generic views certainly are useful, the real power in Django’s generic views comes from the more complex views that allow you to build common CRUD (Create/Retrieve/Update/Delete) pages with a minimum amount of code.
These views break down into a few different types:
Most of these views take a large number of optional arguments that can control various bits of behavior. Many of these arguments may be given to any of these views, so many of the views below refer back to this list of optional arguments:
The list-detail generic views (in the module django.views.generic.list_detail) handles the common case of displaying a list of items at one view, and individual “detail” views of those items at another.
For the examples in the rest of this chapter, we’ll be working with the simple book/author/publisher objects from chapters 5 and 6:
class Publisher(models.Model):
name = models.CharField(maxlength=30)
address = models.CharField(maxlength=50)
city = models.CharField(maxlength=60)
state_province = models.CharField(maxlength=30)
country = models.CharField(maxlength=50)
website = models.URLField()
class Author(models.Model):
salutation = models.CharField(maxlength=10)
first_name = models.CharField(maxlength=30)
last_name = models.CharField(maxlength=40)
email = models.EmailField()
headshot = models.ImageField()
class Book(models.ModelField):
title = models.CharField(maxlength=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
We’ll also be working with a URL module; if you’re following along, you can start with an skeleton URL config in bookstore.urls:
from django.conf.urls.defaults import *
from django.views.generic import list_detail, date_based, create_update
from bookstore.models import Publisher, Author, Book
urlpatterns = patterns('',
# We'll add URL patterns here.
)
We’ll build this up with generic views as we go.
The view django.views.generic.list_detail.object_list is used to create a page representing a list of objects.
We can use the object_list view to show a simple list of all authors in the bookstore. First, we’ll need to construct a info dictionary for the generic view. Add the following to the top of the bookstore/urls.py file:
author_list_info = {
'queryset' : Author.objects.all(),
'allow_empty': True,
}
Then, we need to register this view at a certain URL. We can do that by adding this URL config piece (inside the patterns directive):
(r'authors/$', list_detail.object_list, author_list_info)
From there, we just need to make a template for this generic view to render. Since we didn’t provide the template_name parameter (see below), Django will guess the name of the template; here it’ll use bookstore/author_list.html. See below for more details on how this “guess” is made.
Additionally, this view may take any of these common arguments described above:
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_list.html by default. Both the app label and the model name are derived from the queryset parameter: the app label is the name of the app that the model is defined in, and the model name is the lower-cased version of the name of the model class.
So, if we passed Author.objects.all() as the queryset, the app label would be bookstore and the model name would be author. This means the default template would be bookstore/author_list.html.
In addition to extra_context, the template’s context will contain:
If the results are paginated, the context will contain these extra variables:
A note on pagination:
If paginate_by is specified, Django will paginate the results. You can specify the page number in the URL in one of two ways:
Use the page parameter in the URLconf. For example, this is what your URLconf might look like:
(r'^objects/page(?P<page>[0-9]+)/$', 'object_list', dict(info_dict))
Pass the page number via the page query-string parameter. For example, a URL would look like this:
/objects/?page=3
In both cases, page is 1-based, not 0-based, so the first page would be represented as page 1.
The django.views.generic.list_detail.object_detail gives a “detail” view of a single object.
Extending the example above, we could make a detail view for a given author. Given an info dict like this:
author_detail_info = {
"queryset" : Author.objects.all(),
"template_object_name" : "author",
}
We could use a urlpattern like:
(r'^authors/(?P<object_id>\d+)/$', list_detail.object_detail, author_detail_info),
to show details about a given book, rendered in the bookstore/author_detail.html template. In that template, the Author object itself would be put into the {{ author }} variable.
Either:
or:
The name of a field on the object whose value is the template name to use. This lets you store template names in your data.
In other words, if your object has a field 'the_template' that contains a string 'foo.html', and you set template_name_field to 'the_template', then the generic view for this object will use the template 'foo.html'.
It’s a bit of a brain-bender, but it’s useful in some cases.
This view may also take these common arguments (documented above):
If template_name and template_name_field aren’t specified, this view will use the template <app_label>/<model_name>_detail.html by default.
In addition to extra_context, the template’s context will be:
Date-based generic views are generally used to provide a set of “archive” pages for dated material. Think year/month/day archives for a newspaper, or a blog like the official Django blog described at the beginning of this chapter.
For the examples, we’ll be using the Book object from above, and build up a way to browse books by year, month, and day published. Notice that for each of these views, we have to tell Django the name of the date field we want to key off of. We have to provide this information since models could contain multiple date or datetime fields.
Into the future…
By default, these views ignore objects with dates in the future.
This means that if you try to visit an archive page in the future, Django will automatically show a 404 (“not found”) error, even if there are objects published that day.
Thus, you can publish post-dated objects that don’t appear publically until after their publication date.
However, for different types of date-based objects this isn’t appropriate (for example, a calendar of upcoming events). For these views, setting the allow_future option to True will make the future objects appear (and allow users to visit “future” archive pages).
The django.views.generic.date_based.archive_index view provides a top-level index page showing the “latest” objects, by date.
A typical publisher probably wants to highlight recently-published books. We can use the archive_index view for this common task. Here’s a info dict:
book_info = {
"queryset" : Book.objects.all(),
"date_field" : "publication_date"
}
And the corresponding urlconf piece (which roots this index at the bottom level of wherever it’s included):
(r'^books/$', date_based.archive_index, book_info),
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_archive.html by default.
In addition to extra_context, the template’s context will be:
A list of datetime.date objects representing all years that have objects available according to queryset. These are ordered in reverse.
For example, if you have blog entries from 2003 through 2006, this list will contain four datetime.date objects: one for each of those years.
The django.views.generic.date_based.archive_year view provides a yearly archive page showing all available months in a given year.
Contuting on with our example, we’ll want to add a way to view all the books published in a given year. We can keep using the book_info dictionary from the above example, but this time we’ll wire it up to the archive_year view:
(r'^books/(?P<year>\d{4})/?$', date_based.archive_year, book_info),
Since there are likely many, many books published each year, we won’t display them on this page, just a list of years in which books are available. Conveniently for us, this is what Django does by default; to change it we could use the make_object_list argument; see below.
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_archive_year.html by default.
In addition to extra_context, the template’s context will be:
If the make_object_list parameter is True, this will be set to a list of objects available for the given year, ordered by the date field. This variable’s name depends on the template_object_name parameter, which is 'object' by default. If template_object_name is 'foo', this variable’s name will be foo_list.
If make_object_list is False, object_list will be passed to the template as an empty list.
The django.views.generic.date_based.archive_month views provides a monthly archive page showing all objects in a given month.
Continuing on with our example, creating month views should look mighty familiar:
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', date_based.archive_month, book_info),
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_archive_month.html by default.
In addition to extra_context, the template’s context will be:
The django.views.generic.date_based.archive_week view shows all objects in a given week.
Note
Django believes that weeks start on Sunday, for the perfectly arbitrary reason that Python does, too.
Are you starting to see a pattern here yet?
(r'^(?P<year>\d{4})/(?P<week>\d{2})/$', date_based.archive_week, book_info),
Optional arguments
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_archive_week.html by default.
In addition to extra_context, the template’s context will be:
The django.views.generic.date_based.archive_day view provides a page showing all objects in a given day.
Keep on keepin’ on:
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{2})/$', date_based.archive_day, book_info),
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_archive_day.html by default.
In addition to extra_context, the template’s context will be:
The django.views.generic.date_based.archive_today view shows all objects for today. This is exactly the same as archive_day, except the year/month/day arguments are not used, and today’s date is used instead.
The django.views.generic.date_based.object_detail view shows a page representing an individual object. This differs from the object_detail page in their respective URLs; the object_detail view uses URLs like /entries/<slug>/, while this one uses URLs like /entries/2006/aug/27/<slug>/.
Note
If you’re using date-based detail pages with slugs in the URLs, you probably also want to use the unique_for_date option on the slug field to validate that slugs aren’t duplicated in a single day. See Appendix 2 for details on unique_for_date.
This one differs (slightly) from all the other examples in that we need to either provide an object ID or a slug so that Django can look up the object in question.
Since the object we’re using doesn’t have a slug field, we’ll use the slightly uglyier ID-based URLs. In practice we’d prefer to use a slug field, but in the interest of simplicity we’ll let it go.
We’ll add the following to the URLconf:
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{2})/(?P<object_id>[\w-]+)/$', date_based.object_detail, book_info),
Either:
or:
The name of a field on the object whose value is the template name to use. This lets you store template names in the data. In other words, if your object has a field 'the_template' that contains a string 'foo.html', and you set template_name_field to 'the_template', then the generic view for this object will use the template 'foo.html'.
It’s a bit of a brain-bender, but it’s useful in some cases.
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_detail.html by default.
In addition to extra_context, the template’s context will be:
Note
These views will change slightly when Django’s revised form architecture (currently under development as django.newforms) is finalized. This section will be updated accordingly.
The django.views.generic.create_update module contains a set of functions for creating, editing and deleting objects.
The django.views.generic.create_update.create_object view displays a form for creating an object, redisplays the form with validation errors (if there are any) and saves the object. This uses the automatic manipulators that come with Django models.
These views all present forms if accessed with a GET and perform the requested action (create/update/delete) if accessed via POST.
Note that these views all have a very rough idea of security. Although they take a login_required attribute which if given will restrict access to logged-in users, that’s as far as it goes. They won’t, for example, check that the user editing an object is the same user that created it, nor will they validate any sort of permissions.
Much of the time, however, those features can be accomplished by writing a small wrapper around the generic view; see “extending generic views”, below, for more about this topic.
If we wanted to allow users to create new books in our database, we could do something like this:
(r'^books/create/$', create_update.create_object, {'model' : Book}),
Note
Notice that this view takes the model to be created, not a QuerySet (as all the list/detail/date-based views above do).
A boolean that designates whether a user must be logged in, in order to see the page and save changes. This hooks into the Django authentication system. By default, this is False.
If this is True, and a non-logged-in user attempts to visit this page or save the form, Django will redirect the request to /accounts/login/.
This view may also take these common arguments (documented above):
If template_name isn’t specified, this view will use the template <app_label>/<model_name>_form.html by default.
In addition to extra_context, the template’s context will be:
A FormWrapper instance representing the form for editing the object. This lets you refer to form fields easily in the template system.
For example, if the model has two fields, name and address:
<form action="" method="post">
<p><label for="id_name">Name:</label> {{ form.name }}</p>
<p><label for="id_address">Address:</label> {{ form.address }}</p>
</form>
See Chapter 7 for more information about working with forms.
The django.views.generic.create_update.update_object view is almost identical to the create-object view above, but this one allows the editing of an existing object instead of the creation of a new one.
Following the above example, we could provide an edit interface for a single book with this URLconf snippet:
(r'^books/edit/(?P<object_id>\d+)/$', create_update.update_object, {'model' : Book}),
Either:
or:
Additionally, this view takes all same optional arguments as the creation view (above), plus the template_object_name common argument.
This view uses the same default template name (<app_label>/<model_name>_form.html) as the creation view.
In addition to extra_context, the template’s context will be:
The django.views.generic.create_update.delete_object view is also very similar to the other two.
If this view is fetched with GET, it will display a confirmation page (i.e. “do you really want to delete this object?”). If the view is submitted with POST, the object will be deleted without confirmation.
All the arguments are the same as for the update object view, as is the context; the template name for this view is <app_label>/<model_name>_confirm_delete.html
There’s no question that using generic views can speed up development substantially. In most projects, however, there comes a moment when the generic views no longer suffice. Indeed, the most common question asked by new Django developers is about how to make generic views handle a wider array of situations.
Luckily, in nearly every one of these cases, there are ways to simply extend generic views to handle a larger array of use cases. These situations usually fall into a couple of patterns:
Often you simply need to present some extra information than that provided by the generic view. For example, think of showing a list of all publishers on a book’s detail page; the object_detail generic view provides the book to the context, but it seems there’s no way to get a list of publishers in that template.
But there is: all generic views take an extra optional parameter extra_context. This is a dictionary of extra objects which will be added to the template’s context. So, to provide the list of publishers in the book detail view, we’d use an info dict like this:
book_info = {
"queryset" : Book.objects.all(),
"date_field" : "publication_date",
"extra_context" : {
"publisher_list" : Publisher.objects.all(),
}
}
This would populate a {{ publisher_list }} variable in the template context. This pattern can be used to pass any information down into the template for the generic view; it’s very handy.
Another common need is to filter down the objects given in a list page by some key in the URL. For example, let’s look at providing an interface to browse books by title. We’d like to provide URLs of the form /books/by-title/a/, /books/by-title/b/, etc. — one list page for each letter of the alphabet.
The problem seems to be that the generic view has no concept of reading variables from the URL; if we wired a URL pattern matching those URLs up to the object_list view, we’d get twenty-six pages displaying all the books. Although we could write twenty-six different info dicts (each with a different queryset argument), that’s just silly. The right technique involves writing a simple “wrapper” function around the generic view.
In our alphabetic-browsing example, we’d start by adding a small bit to the URLconf:
from bookstore.views import browse_alphabetically
urlpatterns = patterns('',
# ...
(r'^books/by-title/([a-z])/$', browse_alphabetically)
)
As you can see, this wires the set of URLs to the browse_alphabetically function, so let’s take a look at how that function could be written:
from bookstore.models. import Book
from django.views.generic import list_detail
def browse_alphabetically(request, letter):
return list_detail.object_list(
request,
queryset = Book.objects.filter(title__istartswith=letter),
template_name = "bookstore/browse_alphabetically.html",
extra_context = {
'letter' : letter,
}
)
That’s it!
This works because there’s really nothing special about generic views — they’re just Python functions. Like any view function, generic views expect a certain set of arguments and return HttpResponse objects. Thus, it’s incredibly easy to wrap a small function around a generic view that does additional work before — or after; see below — handing things off to the generic view.
Note
Notice that in the above example we’ve passed the current letter being display in the extra_context. This is usually a good idea in wrappers of this nature; it lets the template know which letter is currently being browsed.
Also (while we’re on the topic of templates) notice that we’ve passed in a custom template name. Without that, it would try to use the same template as a “vanilla” object_list, which could conflict with other generic views.
The last common pattern we’ll look at involves doing some extra work before or after calling the generic view.
Imagine we had a last_accessed field on our Author object that we were using to keep track of the last time a anybody looked at that author. The generic object_detail view, of course, wouldn’t know anything about this field, but once again we could easily write a custom view to keep that field updated.
First, we’d need to modify the author detail bit in the URLconf to point to a custom view:
from bookstore.views import author_detail
urlpatterns = patterns('',
#...
(r'^authors/(?P<author_id>d+)/$', author_detail),
)
Then we’d write our wrapper function:
import datetime
from bookstore.models import Author
from django.views.generic import list_detail
from django.shortcuts import get_object_or_404
def author_detail(request, author_id):
# Look up the Author (and raise a 404 if she's not found)
author = get_object_or_404(Author, pk=author_id)
# Record the last accessed date
author.last_accessed = datetime.datetime.now()
author.save()
# Show the detail page
return list_detail.object_detail(
request,
queryset = Author.objects.all(),
object_id = author_id,
)
Note
This code won’t actually work unless you add the last_accessed field to your Author model.
We can use a similar idiom to alter the response returned by the generic view. If we wanted to provide a downloadable plain-text version of the list of authors, we could use a view like this:
def author_list_plaintext(request):
response = list_detail.object_list(
queryset = Author.objects.all(),
mimetype = "text/plain",
template_name = "bookstore/author_list.txt"
)
response["Content-Disposition"] = "attachment; filename=authors.txt"
return response
This works because the generic views return simple HttpResponse objects which can be treated like dictionaries to set HTTP headers. This Content-Disposition business, by the way, instructs the browser to download and save the page instead of displaying it in the browser.
Until now, we’ve treated the template engine as a mostly static tool you can use to render your content. It’s true that most of the time you’ll just treat it in that way, but the template engine is actually quite extensible.
In the next chapter we’ll delve deep into the inner workings of Django’s templates, showing all the cool ways it can be extended.
Onward, comrades!
Comments are closed on this chapter.
We're no longer accepting comments on this version of this chapter.
Many thanks to all those who commented.
About this comment system
This site is using a contextual comment system to help us gather targeted feedback about the book. Instead of commenting on an entire chapter, you can leave comments on any indivdual "block" in the chapter. A "block" with comments looks like this:
A "block" is a paragraph, list item, code sample, or other small chunk of content. It'll get highlighted when you select it:
To post a comment on a block, just click in the gutter next to the bit you want to comment on:
As we edit the book, we'll review everyone's comments and roll them into a future version of the book. We'll mark reviewed comments with a little checkmark:
Please make sure to leave a full name (and not a nickname or screenname) if you'd like your contributions acknowledged in print.
Many, many thanks to Jack Slocum; the inspiration and much of the code for the comment system comes from Jack's blog, and this site couldn't have been built without his wonderful
YAHOO.extlibrary. Thanks also to Yahoo for YUI itself.