| 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 | ``Publisher.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. |