PageRenderTime 13ms CodeModel.GetById 2ms app.highlight 2ms RepoModel.GetById 1ms app.codeStats 0ms

/docs/topics/generic-views.txt

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