Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Update to [11025]. This time, actually include the new generic views …

…documentation.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11026 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ec1baddbb789f21ecaa9139765b52365d21a5d6b 1 parent 6c81952
Russell Keith-Magee authored June 17, 2009

Showing 1 changed file with 503 additions and 0 deletions. Show diff stats Hide diff stats

  1. 503  docs/topics/generic-views.txt
503  docs/topics/generic-views.txt
... ...
@@ -0,0 +1,503 @@
  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.

0 notes on commit ec1badd

Please sign in to comment.
Something went wrong with that request. Please try again.