parts/django/docs/topics/generic-views.txt
changeset 307 c6bca38c1cbf
equal deleted inserted replaced
306:5ff1fc726848 307:c6bca38c1cbf
       
     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.