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