504 lines
19 KiB
Plaintext
504 lines
19 KiB
Plaintext
.. _topics-generic-views:
|
|
|
|
=============
|
|
Generic views
|
|
=============
|
|
|
|
Writing Web applications can be monotonous, because we repeat certain patterns
|
|
again and again. 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 developed to ease that pain. They take certain
|
|
common idioms and patterns found in view development and abstract them so that
|
|
you can quickly write common views of data without having to write too much
|
|
code.
|
|
|
|
We can recognize certain common tasks, like displaying a list of objects, and
|
|
write code that displays a list of *any* object. Then the model in question can
|
|
be passed as an extra argument to the URLconf.
|
|
|
|
Django ships with generic views to do the following:
|
|
|
|
* Perform common "simple" tasks: redirect to a different page and
|
|
render a given template.
|
|
|
|
* Display list and detail pages for a single object. If we were creating an
|
|
application to manage conferences then a ``talk_list`` view and a
|
|
``registered_user_list`` view would be examples of list views. A single
|
|
talk page is an example of what we call a "detail" view.
|
|
|
|
* Present date-based objects in year/month/day archive pages,
|
|
associated detail, and "latest" pages. The Django Weblog's
|
|
(http://www.djangoproject.com/weblog/) year, month, and
|
|
day archives are built with these, as would be a typical
|
|
newspaper's archives.
|
|
|
|
* Allow users to create, update, and delete objects -- with or
|
|
without authorization.
|
|
|
|
Taken together, these views provide easy interfaces to perform the most common
|
|
tasks developers encounter.
|
|
|
|
Using generic views
|
|
===================
|
|
|
|
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 a simple URLconf you could use to present a static "about"
|
|
page::
|
|
|
|
from django.conf.urls.defaults import *
|
|
from django.views.generic.simple import direct_to_template
|
|
|
|
urlpatterns = patterns('',
|
|
('^about/$', direct_to_template, {
|
|
'template': 'about.html'
|
|
})
|
|
)
|
|
|
|
Though this might seem a bit "magical" at first glance -- look, a view with no
|
|
code! --, actually the ``direct_to_template`` view simply grabs information from
|
|
the extra-parameters dictionary and uses that information when rendering the
|
|
view.
|
|
|
|
Because this generic view -- and all the others -- is a regular view function
|
|
like any other, we can reuse it inside our own views. As an example, let's
|
|
extend our "about" example to map URLs of the form ``/about/<whatever>/`` to
|
|
statically rendered ``about/<whatever>.html``. We'll do this by first modifying
|
|
the URLconf to point to a view function:
|
|
|
|
.. parsed-literal::
|
|
|
|
from django.conf.urls.defaults import *
|
|
from django.views.generic.simple import direct_to_template
|
|
**from mysite.books.views import about_pages**
|
|
|
|
urlpatterns = patterns('',
|
|
('^about/$', direct_to_template, {
|
|
'template': 'about.html'
|
|
}),
|
|
**('^about/(\w+)/$', about_pages),**
|
|
)
|
|
|
|
Next, we'll write the ``about_pages`` view::
|
|
|
|
from django.http import Http404
|
|
from django.template import TemplateDoesNotExist
|
|
from django.views.generic.simple import direct_to_template
|
|
|
|
def about_pages(request, page):
|
|
try:
|
|
return direct_to_template(request, template="about/%s.html" % page)
|
|
except TemplateDoesNotExist:
|
|
raise Http404()
|
|
|
|
Here we're treating ``direct_to_template`` like any other function. Since it
|
|
returns an ``HttpResponse``, we can simply return it as-is. The only slightly
|
|
tricky business here is dealing with missing templates. We don't want a
|
|
nonexistent template to cause a server error, so we catch
|
|
``TemplateDoesNotExist`` exceptions and return 404 errors instead.
|
|
|
|
.. admonition:: Is there a security vulnerability here?
|
|
|
|
Sharp-eyed readers may have noticed a possible security hole: we're
|
|
constructing the template name using interpolated content from the browser
|
|
(``template="about/%s.html" % page``). At first glance, this looks like a
|
|
classic *directory traversal* vulnerability. But is it really?
|
|
|
|
Not exactly. Yes, a maliciously crafted value of ``page`` could cause
|
|
directory traversal, but although ``page`` *is* taken from the request URL,
|
|
not every value will be accepted. The key is in the URLconf: we're using
|
|
the regular expression ``\w+`` to match the ``page`` part of the URL, and
|
|
``\w`` only accepts letters and numbers. Thus, any malicious characters
|
|
(dots and slashes, here) will be rejected by the URL resolver before they
|
|
reach the view itself.
|
|
|
|
Generic views of objects
|
|
========================
|
|
|
|
The ``direct_to_template`` certainly is useful, but Django's generic views
|
|
really shine when it comes to presenting views on your database content. Because
|
|
it's such a common task, Django comes with a handful of built-in generic views
|
|
that make generating list and detail views of objects incredibly easy.
|
|
|
|
Let's take a look at one of these generic views: the "object list" view. We'll
|
|
be using these models::
|
|
|
|
# models.py
|
|
from django.db import models
|
|
|
|
class Publisher(models.Model):
|
|
name = models.CharField(max_length=30)
|
|
address = models.CharField(max_length=50)
|
|
city = models.CharField(max_length=60)
|
|
state_province = models.CharField(max_length=30)
|
|
country = models.CharField(max_length=50)
|
|
website = models.URLField()
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
class Meta:
|
|
ordering = ["-name"]
|
|
|
|
class Book(models.Model):
|
|
title = models.CharField(max_length=100)
|
|
authors = models.ManyToManyField('Author')
|
|
publisher = models.ForeignKey(Publisher)
|
|
publication_date = models.DateField()
|
|
|
|
To build a list page of all publishers, we'd use a URLconf along these lines::
|
|
|
|
from django.conf.urls.defaults import *
|
|
from django.views.generic import list_detail
|
|
from mysite.books.models import Publisher
|
|
|
|
publisher_info = {
|
|
"queryset" : Publisher.objects.all(),
|
|
}
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^publishers/$', list_detail.object_list, publisher_info)
|
|
)
|
|
|
|
That's all the Python code we need to write. We still need to write a template,
|
|
however. We could explicitly tell the ``object_list`` view which template to use
|
|
by including a ``template_name`` key in the extra arguments dictionary, but in
|
|
the absence of an explicit template Django will infer one from the object's
|
|
name. In this case, the inferred template will be
|
|
``"books/publisher_list.html"`` -- the "books" part comes from the name of the
|
|
app that defines the model, while the "publisher" bit is just the lowercased
|
|
version of the model's name.
|
|
|
|
.. highlightlang:: html+django
|
|
|
|
This template will be rendered against a context containing a variable called
|
|
``object_list`` that contains all the publisher objects. A very simple template
|
|
might look like the following::
|
|
|
|
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<h2>Publishers</h2>
|
|
<ul>
|
|
{% for publisher in object_list %}
|
|
<li>{{ publisher.name }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endblock %}
|
|
|
|
That's really all there is to it. All the cool features of generic views come
|
|
from changing the "info" dictionary passed to the generic view. The
|
|
:ref:`generic views reference<ref-generic-views>` documents all the generic
|
|
views and all their options in detail; the rest of this document will consider
|
|
some of the common ways you might customize and extend generic views.
|
|
|
|
Extending generic views
|
|
=======================
|
|
|
|
.. highlightlang:: python
|
|
|
|
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 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 handful of patterns dealt with in the sections that follow.
|
|
|
|
Making "friendly" template contexts
|
|
-----------------------------------
|
|
|
|
You might have noticed that our sample publisher list template stores all the
|
|
books in a variable named ``object_list``. While this works just fine, it isn't
|
|
all that "friendly" to template authors: they have to "just know" that they're
|
|
dealing with publishers here. A better name for that variable would be
|
|
``publisher_list``; that variable's content is pretty obvious.
|
|
|
|
We can change the name of that variable easily with the ``template_object_name``
|
|
argument:
|
|
|
|
.. parsed-literal::
|
|
|
|
publisher_info = {
|
|
"queryset" : Publisher.objects.all(),
|
|
**"template_object_name" : "publisher",**
|
|
}
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^publishers/$', list_detail.object_list, publisher_info)
|
|
)
|
|
|
|
Providing a useful ``template_object_name`` is always a good idea. Your
|
|
coworkers who design templates will thank you.
|
|
|
|
Adding extra context
|
|
--------------------
|
|
|
|
Often you simply need to present some extra information beyond that provided by
|
|
the generic view. For example, think of showing a list of all the books on each
|
|
publisher detail page. The ``object_detail`` generic view provides the
|
|
publisher to the context, but it seems there's no way to get additional
|
|
information in that template.
|
|
|
|
But there is: all generic views take an extra optional parameter,
|
|
``extra_context``. This is a dictionary of extra objects that will be added to
|
|
the template's context. So, to provide the list of all books on the detail
|
|
detail view, we'd use an info dict like this:
|
|
|
|
.. parsed-literal::
|
|
|
|
from mysite.books.models import Publisher, **Book**
|
|
|
|
publisher_info = {
|
|
"queryset" : Publisher.objects.all(),
|
|
"template_object_name" : "publisher",
|
|
**"extra_context" : {"book_list" : Book.objects.all()}**
|
|
}
|
|
|
|
This would populate a ``{{ book_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.
|
|
|
|
However, there's actually a subtle bug here -- can you spot it?
|
|
|
|
The problem has to do with when the queries in ``extra_context`` are evaluated.
|
|
Because this example puts ``Book.objects.all()`` in the URLconf, it will
|
|
be evaluated only once (when the URLconf is first loaded). Once you add or
|
|
remove books, you'll notice that the generic view doesn't reflect those
|
|
changes until you reload the Web server (see :ref:`caching-and-querysets`
|
|
for more information about when QuerySets are cached and evaluated).
|
|
|
|
.. note::
|
|
|
|
This problem doesn't apply to the ``queryset`` generic view argument. Since
|
|
Django knows that particular QuerySet should *never* be cached, the generic
|
|
view takes care of clearing the cache when each view is rendered.
|
|
|
|
The solution is to use a callback in ``extra_context`` instead of a value. Any
|
|
callable (i.e., a function) that's passed to ``extra_context`` will be evaluated
|
|
when the view is rendered (instead of only once). You could do this with an
|
|
explicitly defined function:
|
|
|
|
.. parsed-literal::
|
|
|
|
def get_books():
|
|
return Book.objects.all()
|
|
|
|
publisher_info = {
|
|
"queryset" : Publisher.objects.all(),
|
|
"template_object_name" : "publisher",
|
|
"extra_context" : **{"book_list" : get_books}**
|
|
}
|
|
|
|
or you could use a less obvious but shorter version that relies on the fact that
|
|
``Book.objects.all`` is itself a callable:
|
|
|
|
.. parsed-literal::
|
|
|
|
publisher_info = {
|
|
"queryset" : Publisher.objects.all(),
|
|
"template_object_name" : "publisher",
|
|
"extra_context" : **{"book_list" : Book.objects.all}**
|
|
}
|
|
|
|
Notice the lack of parentheses after ``Book.objects.all``; this references
|
|
the function without actually calling it (which the generic view will do later).
|
|
|
|
Viewing subsets of objects
|
|
--------------------------
|
|
|
|
Now let's take a closer look at this ``queryset`` key we've been using all
|
|
along. Most generic views take one of these ``queryset`` arguments -- it's how
|
|
the view knows which set of objects to display (see :ref:`topics-db-queries` for
|
|
more information about ``QuerySet`` objects, and see the
|
|
:ref:`generic views reference<ref-generic-views>` for the complete details).
|
|
|
|
To pick a simple example, we might want to order a list of books by
|
|
publication date, with the most recent first:
|
|
|
|
.. parsed-literal::
|
|
|
|
book_info = {
|
|
"queryset" : Book.objects.all().order_by("-publication_date"),
|
|
}
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^publishers/$', list_detail.object_list, publisher_info),
|
|
**(r'^books/$', list_detail.object_list, book_info),**
|
|
)
|
|
|
|
|
|
That's a pretty simple example, but it illustrates the idea nicely. Of course,
|
|
you'll usually want to do more than just reorder objects. If you want to
|
|
present a list of books by a particular publisher, you can use the same
|
|
technique:
|
|
|
|
.. parsed-literal::
|
|
|
|
**acme_books = {**
|
|
**"queryset": Book.objects.filter(publisher__name="Acme Publishing"),**
|
|
**"template_name" : "books/acme_list.html"**
|
|
**}**
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^publishers/$', list_detail.object_list, publisher_info),
|
|
**(r'^books/acme/$', list_detail.object_list, acme_books),**
|
|
)
|
|
|
|
Notice that along with a filtered ``queryset``, we're also using a custom
|
|
template name. If we didn't, the generic view would use the same template as the
|
|
"vanilla" object list, which might not be what we want.
|
|
|
|
Also notice that this isn't a very elegant way of doing publisher-specific
|
|
books. If we want to add another publisher page, we'd need another handful of
|
|
lines in the URLconf, and more than a few publishers would get unreasonable.
|
|
We'll deal with this problem in the next section.
|
|
|
|
.. note::
|
|
|
|
If you get a 404 when requesting ``/books/acme/``, check to ensure you
|
|
actually have a Publisher with the name 'ACME Publishing'. Generic
|
|
views have an ``allow_empty`` parameter for this case. See the
|
|
:ref:`generic views reference<ref-generic-views>` for more details.
|
|
|
|
Complex filtering with wrapper functions
|
|
----------------------------------------
|
|
|
|
Another common need is to filter down the objects given in a list page by some
|
|
key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but
|
|
what if we wanted to write a view that displayed all the books by some arbitrary
|
|
publisher? We can "wrap" the ``object_list`` generic view to avoid writing a lot
|
|
of code by hand. As usual, we'll start by writing a URLconf:
|
|
|
|
.. parsed-literal::
|
|
|
|
from mysite.books.views import books_by_publisher
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^publishers/$', list_detail.object_list, publisher_info),
|
|
**(r'^books/(\w+)/$', books_by_publisher),**
|
|
)
|
|
|
|
Next, we'll write the ``books_by_publisher`` view itself::
|
|
|
|
from django.http import Http404
|
|
from django.views.generic import list_detail
|
|
from mysite.books.models import Book, Publisher
|
|
|
|
def books_by_publisher(request, name):
|
|
|
|
# Look up the publisher (and raise a 404 if it can't be found).
|
|
try:
|
|
publisher = Publisher.objects.get(name__iexact=name)
|
|
except Publisher.DoesNotExist:
|
|
raise Http404
|
|
|
|
# Use the object_list view for the heavy lifting.
|
|
return list_detail.object_list(
|
|
request,
|
|
queryset = Book.objects.filter(publisher=publisher),
|
|
template_name = "books/books_by_publisher.html",
|
|
template_object_name = "books",
|
|
extra_context = {"publisher" : publisher}
|
|
)
|
|
|
|
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 the next section) handing things off to the generic view.
|
|
|
|
.. note::
|
|
|
|
Notice that in the preceding example we passed the current publisher being
|
|
displayed in the ``extra_context``. This is usually a good idea in wrappers
|
|
of this nature; it lets the template know which "parent" object is currently
|
|
being browsed.
|
|
|
|
Performing extra work
|
|
---------------------
|
|
|
|
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 anybody looked at that author::
|
|
|
|
# models.py
|
|
|
|
class Author(models.Model):
|
|
salutation = models.CharField(max_length=10)
|
|
first_name = models.CharField(max_length=30)
|
|
last_name = models.CharField(max_length=40)
|
|
email = models.EmailField()
|
|
headshot = models.ImageField(upload_to='/tmp')
|
|
last_accessed = models.DateTimeField()
|
|
|
|
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 add an author detail bit in the URLconf to point to a
|
|
custom view:
|
|
|
|
.. parsed-literal::
|
|
|
|
from mysite.books.views import author_detail
|
|
|
|
urlpatterns = patterns('',
|
|
#...
|
|
**(r'^authors/(?P<author_id>\d+)/$', author_detail),**
|
|
)
|
|
|
|
Then we'd write our wrapper function::
|
|
|
|
import datetime
|
|
from mysite.books.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 create a
|
|
``books/author_detail.html`` template.
|
|
|
|
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(
|
|
request,
|
|
queryset = Author.objects.all(),
|
|
mimetype = "text/plain",
|
|
template_name = "books/author_list.txt"
|
|
)
|
|
response["Content-Disposition"] = "attachment; filename=authors.txt"
|
|
return response
|
|
|
|
This works because the generic views return simple ``HttpResponse`` objects
|
|
that 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.
|