~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to docs/topics/generic-views.txt

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2009-07-29 11:26:28 UTC
  • mfrom: (1.1.8 upstream) (4.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20090729112628-pg09ino8sz0sj21t
Tags: 1.1-1
* New upstream release.
* Merge from experimental:
  - Ship FastCGI initscript and /etc/default file in python-django's examples
    directory (Closes: #538863)
  - Drop "05_10539-sphinx06-compatibility.diff"; it has been applied
    upstream.
  - Bump Standards-Version to 3.8.2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
.. _topics-generic-views:
 
2
 
 
3
=============
 
4
Generic views
 
5
=============
 
6
 
 
7
Writing Web applications can be monotonous, because we repeat certain patterns
 
8
again and again. Django tries to take away some of that monotony at the model
 
9
and template layers, but Web developers also experience this boredom at the view
 
10
level.
 
11
 
 
12
Django's *generic views* were developed to ease that pain. They take certain
 
13
common idioms and patterns found in view development and abstract them so that
 
14
you can quickly write common views of data without having to write too much
 
15
code.
 
16
 
 
17
We can recognize certain common tasks, like displaying a list of objects, and
 
18
write code that displays a list of *any* object. Then the model in question can
 
19
be passed as an extra argument to the URLconf.
 
20
 
 
21
Django ships with generic views to do the following:
 
22
 
 
23
    * Perform common "simple" tasks: redirect to a different page and
 
24
      render a given template.
 
25
 
 
26
    * Display list and detail pages for a single object. If we were creating an
 
27
      application to manage conferences then a ``talk_list`` view and a
 
28
      ``registered_user_list`` view would be examples of list views. A single
 
29
      talk page is an example of what we call a "detail" view.
 
30
 
 
31
    * Present date-based objects in year/month/day archive pages,
 
32
      associated detail, and "latest" pages. The Django Weblog's
 
33
      (http://www.djangoproject.com/weblog/) year, month, and
 
34
      day archives are built with these, as would be a typical
 
35
      newspaper's archives.
 
36
 
 
37
    * Allow users to create, update, and delete objects -- with or
 
38
      without authorization.
 
39
 
 
40
Taken together, these views provide easy interfaces to perform the most common
 
41
tasks developers encounter.
 
42
 
 
43
Using generic views
 
44
===================
 
45
 
 
46
All of these views are used by creating configuration dictionaries in
 
47
your URLconf files and passing those dictionaries as the third member of the
 
48
URLconf tuple for a given pattern.
 
49
 
 
50
For example, here's a simple URLconf you could use to present a static "about"
 
51
page::
 
52
 
 
53
    from django.conf.urls.defaults import *
 
54
    from django.views.generic.simple import direct_to_template
 
55
 
 
56
    urlpatterns = patterns('',
 
57
        ('^about/$', direct_to_template, {
 
58
            'template': 'about.html'
 
59
        })
 
60
    )
 
61
 
 
62
Though this might seem a bit "magical" at first glance  -- look, a view with no
 
63
code! --, actually the ``direct_to_template`` view simply grabs information from
 
64
the extra-parameters dictionary and uses that information when rendering the
 
65
view.
 
66
 
 
67
Because this generic view -- and all the others -- is a regular view functions
 
68
like any other, we can reuse it inside our own views. As an example, let's
 
69
extend our "about" example to map URLs of the form ``/about/<whatever>/`` to
 
70
statically rendered ``about/<whatever>.html``. We'll do this by first modifying
 
71
the URLconf to point to a view function:
 
72
 
 
73
.. parsed-literal::
 
74
 
 
75
    from django.conf.urls.defaults import *
 
76
    from django.views.generic.simple import direct_to_template
 
77
    **from mysite.books.views import about_pages**
 
78
 
 
79
    urlpatterns = patterns('',
 
80
        ('^about/$', direct_to_template, {
 
81
            'template': 'about.html'
 
82
        }),
 
83
        **('^about/(\w+)/$', about_pages),**
 
84
    )
 
85
 
 
86
Next, we'll write the ``about_pages`` view::
 
87
 
 
88
    from django.http import Http404
 
89
    from django.template import TemplateDoesNotExist
 
90
    from django.views.generic.simple import direct_to_template
 
91
 
 
92
    def about_pages(request, page):
 
93
        try:
 
94
            return direct_to_template(request, template="about/%s.html" % page)
 
95
        except TemplateDoesNotExist:
 
96
            raise Http404()
 
97
 
 
98
Here we're treating ``direct_to_template`` like any other function. Since it
 
99
returns an ``HttpResponse``, we can simply return it as-is. The only slightly
 
100
tricky business here is dealing with missing templates. We don't want a
 
101
nonexistent template to cause a server error, so we catch
 
102
``TemplateDoesNotExist`` exceptions and return 404 errors instead.
 
103
 
 
104
.. admonition:: Is there a security vulnerability here?
 
105
 
 
106
    Sharp-eyed readers may have noticed a possible security hole: we're
 
107
    constructing the template name using interpolated content from the browser
 
108
    (``template="about/%s.html" % page``). At first glance, this looks like a
 
109
    classic *directory traversal* vulnerability. But is it really?
 
110
 
 
111
    Not exactly. Yes, a maliciously crafted value of ``page`` could cause
 
112
    directory traversal, but although ``page`` *is* taken from the request URL,
 
113
    not every value will be accepted. The key is in the URLconf: we're using
 
114
    the regular expression ``\w+`` to match the ``page`` part of the URL, and
 
115
    ``\w`` only accepts letters and numbers. Thus, any malicious characters
 
116
    (dots and slashes, here) will be rejected by the URL resolver before they
 
117
    reach the view itself.
 
118
 
 
119
Generic views of objects
 
120
========================
 
121
 
 
122
The ``direct_to_template`` certainly is useful, but Django's generic views
 
123
really shine when it comes to presenting views on your database content. Because
 
124
it's such a common task, Django comes with a handful of built-in generic views
 
125
that make generating list and detail views of objects incredibly easy.
 
126
 
 
127
Let's take a look at one of these generic views: the "object list" view. We'll
 
128
be using these models::
 
129
 
 
130
    # models.py
 
131
    from django.db import models
 
132
 
 
133
    class Publisher(models.Model):
 
134
        name = models.CharField(max_length=30)
 
135
        address = models.CharField(max_length=50)
 
136
        city = models.CharField(max_length=60)
 
137
        state_province = models.CharField(max_length=30)
 
138
        country = models.CharField(max_length=50)
 
139
        website = models.URLField()
 
140
 
 
141
        def __unicode__(self):
 
142
            return self.name
 
143
 
 
144
        class Meta:
 
145
            ordering = ["-name"]
 
146
 
 
147
    class Book(models.Model):
 
148
        title = models.CharField(max_length=100)
 
149
        authors = models.ManyToManyField('Author')
 
150
        publisher = models.ForeignKey(Publisher)
 
151
        publication_date = models.DateField()
 
152
 
 
153
To build a list page of all books, we'd use a URLconf along these lines::
 
154
 
 
155
    from django.conf.urls.defaults import *
 
156
    from django.views.generic import list_detail
 
157
    from mysite.books.models import Publisher
 
158
 
 
159
    publisher_info = {
 
160
        "queryset" : Publisher.objects.all(),
 
161
    }
 
162
 
 
163
    urlpatterns = patterns('',
 
164
        (r'^publishers/$', list_detail.object_list, publisher_info)
 
165
    )
 
166
 
 
167
That's all the Python code we need to write. We still need to write a template,
 
168
however. We could explicitly tell the ``object_list`` view which template to use
 
169
by including a ``template_name`` key in the extra arguments dictionary, but in
 
170
the absence of an explicit template Django will infer one from the object's
 
171
name. In this case, the inferred template will be
 
172
``"books/publisher_list.html"`` -- the "books" part comes from the name of the
 
173
app that defines the model, while the "publisher" bit is just the lowercased
 
174
version of the model's name.
 
175
 
 
176
.. highlightlang:: html+django
 
177
 
 
178
This template will be rendered against a context containing a variable called
 
179
``object_list`` that contains all the book objects. A very simple template
 
180
might look like the following::
 
181
 
 
182
    {% extends "base.html" %}
 
183
 
 
184
    {% block content %}
 
185
        <h2>Publishers</h2>
 
186
        <ul>
 
187
            {% for publisher in object_list %}
 
188
                <li>{{ publisher.name }}</li>
 
189
            {% endfor %}
 
190
        </ul>
 
191
    {% endblock %}
 
192
 
 
193
That's really all there is to it. All the cool features of generic views come
 
194
from changing the "info" dictionary passed to the generic view. The
 
195
:ref:`generic views reference<ref-generic-views>` documents all the generic
 
196
views and all their options in detail; the rest of this document will consider
 
197
some of the common ways you might customize and extend generic views.
 
198
 
 
199
Extending generic views
 
200
=======================
 
201
 
 
202
.. highlightlang:: python
 
203
 
 
204
There's no question that using generic views can speed up development
 
205
substantially. In most projects, however, there comes a moment when the
 
206
generic views no longer suffice. Indeed, the most common question asked by new
 
207
Django developers is how to make generic views handle a wider array of
 
208
situations.
 
209
 
 
210
Luckily, in nearly every one of these cases, there are ways to simply extend
 
211
generic views to handle a larger array of use cases. These situations usually
 
212
fall into a handful of patterns dealt with in the sections that follow.
 
213
 
 
214
Making "friendly" template contexts
 
215
-----------------------------------
 
216
 
 
217
You might have noticed that our sample publisher list template stores all the
 
218
books in a variable named ``object_list``. While this works just fine, it isn't
 
219
all that "friendly" to template authors: they have to "just know" that they're
 
220
dealing with books here. A better name for that variable would be
 
221
``publisher_list``; that variable's content is pretty obvious.
 
222
 
 
223
We can change the name of that variable easily with the ``template_object_name``
 
224
argument:
 
225
 
 
226
.. parsed-literal::
 
227
 
 
228
    publisher_info = {
 
229
        "queryset" : Publisher.objects.all(),
 
230
        **"template_object_name" : "publisher",**
 
231
    }
 
232
 
 
233
    urlpatterns = patterns('',
 
234
        (r'^publishers/$', list_detail.object_list, publisher_info)
 
235
    )
 
236
 
 
237
Providing a useful ``template_object_name`` is always a good idea. Your
 
238
coworkers who design templates will thank you.
 
239
 
 
240
Adding extra context
 
241
--------------------
 
242
 
 
243
Often you simply need to present some extra information beyond that provided by
 
244
the generic view. For example, think of showing a list of all the other
 
245
publishers on each publisher detail page. The ``object_detail`` generic view
 
246
provides the publisher to the context, but it seems there's no way to get a list
 
247
of *all* publishers in that template.
 
248
 
 
249
But there is: all generic views take an extra optional parameter,
 
250
``extra_context``. This is a dictionary of extra objects that will be added to
 
251
the template's context. So, to provide the list of all publishers on the detail
 
252
detail view, we'd use an info dict like this:
 
253
 
 
254
.. parsed-literal::
 
255
 
 
256
    from mysite.books.models import Publisher, **Book**
 
257
 
 
258
    publisher_info = {
 
259
        "queryset" : Publisher.objects.all(),
 
260
        "template_object_name" : "publisher",
 
261
        **"extra_context" : {"book_list" : Book.objects.all()}**
 
262
    }
 
263
 
 
264
This would populate a ``{{ book_list }}`` variable in the template context.
 
265
This pattern can be used to pass any information down into the template for the
 
266
generic view. It's very handy.
 
267
 
 
268
However, there's actually a subtle bug here -- can you spot it?
 
269
 
 
270
The problem has to do with when the queries in ``extra_context`` are evaluated.
 
271
Because this example puts ``Publisher.objects.all()`` in the URLconf, it will
 
272
be evaluated only once (when the URLconf is first loaded). Once you add or
 
273
remove publishers, you'll notice that the generic view doesn't reflect those
 
274
changes until you reload the Web server (see :ref:`caching-and-querysets`
 
275
for more information about when QuerySets are cached and evaluated).
 
276
 
 
277
.. note::
 
278
 
 
279
    This problem doesn't apply to the ``queryset`` generic view argument. Since
 
280
    Django knows that particular QuerySet should *never* be cached, the generic
 
281
    view takes care of clearing the cache when each view is rendered.
 
282
 
 
283
The solution is to use a callback in ``extra_context`` instead of a value. Any
 
284
callable (i.e., a function) that's passed to ``extra_context`` will be evaluated
 
285
when the view is rendered (instead of only once). You could do this with an
 
286
explicitly defined function:
 
287
 
 
288
.. parsed-literal::
 
289
 
 
290
    def get_books():
 
291
        return Book.objects.all()
 
292
 
 
293
    publisher_info = {
 
294
        "queryset" : Publisher.objects.all(),
 
295
        "template_object_name" : "publisher",
 
296
        "extra_context" : **{"book_list" : get_books}**
 
297
    }
 
298
 
 
299
or you could use a less obvious but shorter version that relies on the fact that
 
300
``Book.objects.all`` is itself a callable:
 
301
 
 
302
.. parsed-literal::
 
303
 
 
304
    publisher_info = {
 
305
        "queryset" : Publisher.objects.all(),
 
306
        "template_object_name" : "publisher",
 
307
        "extra_context" : **{"book_list" : Book.objects.all}**
 
308
    }
 
309
 
 
310
Notice the lack of parentheses after ``Book.objects.all``; this references
 
311
the function without actually calling it (which the generic view will do later).
 
312
 
 
313
Viewing subsets of objects
 
314
--------------------------
 
315
 
 
316
Now let's take a closer look at this ``queryset`` key we've been using all
 
317
along. Most generic views take one of these ``queryset`` arguments -- it's how
 
318
the view knows which set of objects to display (see :ref:`topics-db-queries` for
 
319
more information about ``QuerySet`` objects, and see the
 
320
:ref:`generic views reference<ref-generic-views>` for the complete details).
 
321
 
 
322
To pick a simple example, we might want to order a list of books by
 
323
publication date, with the most recent first:
 
324
 
 
325
.. parsed-literal::
 
326
 
 
327
    book_info = {
 
328
        "queryset" : Book.objects.all().order_by("-publication_date"),
 
329
    }
 
330
 
 
331
    urlpatterns = patterns('',
 
332
        (r'^publishers/$', list_detail.object_list, publisher_info),
 
333
        **(r'^books/$', list_detail.object_list, book_info),**
 
334
    )
 
335
 
 
336
 
 
337
That's a pretty simple example, but it illustrates the idea nicely. Of course,
 
338
you'll usually want to do more than just reorder objects. If you want to
 
339
present a list of books by a particular publisher, you can use the same
 
340
technique:
 
341
 
 
342
.. parsed-literal::
 
343
 
 
344
    **acme_books = {**
 
345
        **"queryset": Book.objects.filter(publisher__name="Acme Publishing"),**
 
346
        **"template_name" : "books/acme_list.html"**
 
347
    **}**
 
348
 
 
349
    urlpatterns = patterns('',
 
350
        (r'^publishers/$', list_detail.object_list, publisher_info),
 
351
        **(r'^books/acme/$', list_detail.object_list, acme_books),**
 
352
    )
 
353
 
 
354
Notice that along with a filtered ``queryset``, we're also using a custom
 
355
template name. If we didn't, the generic view would use the same template as the
 
356
"vanilla" object list, which might not be what we want.
 
357
 
 
358
Also notice that this isn't a very elegant way of doing publisher-specific
 
359
books. If we want to add another publisher page, we'd need another handful of
 
360
lines in the URLconf, and more than a few publishers would get unreasonable.
 
361
We'll deal with this problem in the next section.
 
362
 
 
363
.. note::
 
364
 
 
365
    If you get a 404 when requesting ``/books/acme/``, check to ensure you
 
366
    actually have a Publisher with the name 'ACME Publishing'.  Generic
 
367
    views have an ``allow_empty`` parameter for this case.  See the
 
368
    :ref:`generic views reference<ref-generic-views>` for more details.
 
369
 
 
370
Complex filtering with wrapper functions
 
371
----------------------------------------
 
372
 
 
373
Another common need is to filter down the objects given in a list page by some
 
374
key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but
 
375
what if we wanted to write a view that displayed all the books by some arbitrary
 
376
publisher? We can "wrap" the ``object_list`` generic view to avoid writing a lot
 
377
of code by hand. As usual, we'll start by writing a URLconf:
 
378
 
 
379
.. parsed-literal::
 
380
 
 
381
    from mysite.books.views import books_by_publisher
 
382
 
 
383
    urlpatterns = patterns('',
 
384
        (r'^publishers/$', list_detail.object_list, publisher_info),
 
385
        **(r'^books/(\w+)/$', books_by_publisher),**
 
386
    )
 
387
 
 
388
Next, we'll write the ``books_by_publisher`` view itself::
 
389
 
 
390
    from django.http import Http404
 
391
    from django.views.generic import list_detail
 
392
    from mysite.books.models import Book, Publisher
 
393
 
 
394
    def books_by_publisher(request, name):
 
395
 
 
396
        # Look up the publisher (and raise a 404 if it can't be found).
 
397
        try:
 
398
            publisher = Publisher.objects.get(name__iexact=name)
 
399
        except Publisher.DoesNotExist:
 
400
            raise Http404
 
401
 
 
402
        # Use the object_list view for the heavy lifting.
 
403
        return list_detail.object_list(
 
404
            request,
 
405
            queryset = Book.objects.filter(publisher=publisher),
 
406
            template_name = "books/books_by_publisher.html",
 
407
            template_object_name = "books",
 
408
            extra_context = {"publisher" : publisher}
 
409
        )
 
410
 
 
411
This works because there's really nothing special about generic views -- they're
 
412
just Python functions. Like any view function, generic views expect a certain
 
413
set of arguments and return ``HttpResponse`` objects. Thus, it's incredibly easy
 
414
to wrap a small function around a generic view that does additional work before
 
415
(or after; see the next section) handing things off to the generic view.
 
416
 
 
417
.. note::
 
418
 
 
419
    Notice that in the preceding example we passed the current publisher being
 
420
    displayed in the ``extra_context``. This is usually a good idea in wrappers
 
421
    of this nature; it lets the template know which "parent" object is currently
 
422
    being browsed.
 
423
 
 
424
Performing extra work
 
425
---------------------
 
426
 
 
427
The last common pattern we'll look at involves doing some extra work before
 
428
or after calling the generic view.
 
429
 
 
430
Imagine we had a ``last_accessed`` field on our ``Author`` object that we were
 
431
using to keep track of the last time anybody looked at that author::
 
432
 
 
433
    # models.py
 
434
 
 
435
    class Author(models.Model):
 
436
        salutation = models.CharField(max_length=10)
 
437
        first_name = models.CharField(max_length=30)
 
438
        last_name = models.CharField(max_length=40)
 
439
        email = models.EmailField()
 
440
        headshot = models.ImageField(upload_to='/tmp')
 
441
        last_accessed = models.DateTimeField()
 
442
 
 
443
The generic ``object_detail`` view, of course, wouldn't know anything about this
 
444
field, but once again we could easily write a custom view to keep that field
 
445
updated.
 
446
 
 
447
First, we'd need to add an author detail bit in the URLconf to point to a
 
448
custom view:
 
449
 
 
450
.. parsed-literal::
 
451
 
 
452
    from mysite.books.views import author_detail
 
453
 
 
454
    urlpatterns = patterns('',
 
455
        #...
 
456
        **(r'^authors/(?P<author_id>\d+)/$', author_detail),**
 
457
    )
 
458
 
 
459
Then we'd write our wrapper function::
 
460
 
 
461
    import datetime
 
462
    from mysite.books.models import Author
 
463
    from django.views.generic import list_detail
 
464
    from django.shortcuts import get_object_or_404
 
465
 
 
466
    def author_detail(request, author_id):
 
467
        # Look up the Author (and raise a 404 if she's not found)
 
468
        author = get_object_or_404(Author, pk=author_id)
 
469
 
 
470
        # Record the last accessed date
 
471
        author.last_accessed = datetime.datetime.now()
 
472
        author.save()
 
473
 
 
474
        # Show the detail page
 
475
        return list_detail.object_detail(
 
476
            request,
 
477
            queryset = Author.objects.all(),
 
478
            object_id = author_id,
 
479
        )
 
480
 
 
481
.. note::
 
482
 
 
483
    This code won't actually work unless you create a
 
484
    ``books/author_detail.html`` template.
 
485
 
 
486
We can use a similar idiom to alter the response returned by the generic view.
 
487
If we wanted to provide a downloadable plain-text version of the list of
 
488
authors, we could use a view like this::
 
489
 
 
490
    def author_list_plaintext(request):
 
491
        response = list_detail.object_list(
 
492
            request,
 
493
            queryset = Author.objects.all(),
 
494
            mimetype = "text/plain",
 
495
            template_name = "books/author_list.txt"
 
496
        )
 
497
        response["Content-Disposition"] = "attachment; filename=authors.txt"
 
498
        return response
 
499
 
 
500
This works because the generic views return simple ``HttpResponse`` objects
 
501
that can be treated like dictionaries to set HTTP headers. This
 
502
``Content-Disposition`` business, by the way, instructs the browser to
 
503
download and save the page instead of displaying it in the browser.