diff -r 5ff1fc726848 -r c6bca38c1cbf parts/django/docs/topics/generic-views.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parts/django/docs/topics/generic-views.txt Sat Jan 08 11:20:57 2011 +0530 @@ -0,0 +1,501 @@ +============= +Generic views +============= + +Writing Web applications can be monotonous, because we repeat certain patterns +again and again. Django tries to take away some of that monotony at the model +and template layers, but Web developers also experience this boredom at the view +level. + +Django's *generic views* were developed to ease that pain. They take certain +common idioms and patterns found in view development and abstract them so that +you can quickly write common views of data without having to write too much +code. + +We can recognize certain common tasks, like displaying a list of objects, and +write code that displays a list of *any* object. Then the model in question can +be passed as an extra argument to the URLconf. + +Django ships with generic views to do the following: + + * Perform common "simple" tasks: redirect to a different page and + render a given template. + + * Display list and detail pages for a single object. If we were creating an + application to manage conferences then a ``talk_list`` view and a + ``registered_user_list`` view would be examples of list views. A single + talk page is an example of what we call a "detail" view. + + * Present date-based objects in year/month/day archive pages, + associated detail, and "latest" pages. The Django Weblog's + (http://www.djangoproject.com/weblog/) year, month, and + day archives are built with these, as would be a typical + newspaper's archives. + + * Allow users to create, update, and delete objects -- with or + without authorization. + +Taken together, these views provide easy interfaces to perform the most common +tasks developers encounter. + +Using generic views +=================== + +All of these views are used by creating configuration dictionaries in +your URLconf files and passing those dictionaries as the third member of the +URLconf tuple for a given pattern. + +For example, here's a simple URLconf you could use to present a static "about" +page:: + + from django.conf.urls.defaults import * + from django.views.generic.simple import direct_to_template + + urlpatterns = patterns('', + ('^about/$', direct_to_template, { + 'template': 'about.html' + }) + ) + +Though this might seem a bit "magical" at first glance -- look, a view with no +code! --, actually the ``direct_to_template`` view simply grabs information from +the extra-parameters dictionary and uses that information when rendering the +view. + +Because this generic view -- and all the others -- is a regular view function +like any other, we can reuse it inside our own views. As an example, let's +extend our "about" example to map URLs of the form ``/about//`` to +statically rendered ``about/.html``. We'll do this by first modifying +the URLconf to point to a view function: + +.. parsed-literal:: + + from django.conf.urls.defaults import * + from django.views.generic.simple import direct_to_template + **from books.views import about_pages** + + urlpatterns = patterns('', + ('^about/$', direct_to_template, { + 'template': 'about.html' + }), + **('^about/(\\w+)/$', about_pages),** + ) + +Next, we'll write the ``about_pages`` view:: + + from django.http import Http404 + from django.template import TemplateDoesNotExist + from django.views.generic.simple import direct_to_template + + def about_pages(request, page): + try: + return direct_to_template(request, template="about/%s.html" % page) + except TemplateDoesNotExist: + raise Http404() + +Here we're treating ``direct_to_template`` like any other function. Since it +returns an ``HttpResponse``, we can simply return it as-is. The only slightly +tricky business here is dealing with missing templates. We don't want a +nonexistent template to cause a server error, so we catch +``TemplateDoesNotExist`` exceptions and return 404 errors instead. + +.. admonition:: Is there a security vulnerability here? + + Sharp-eyed readers may have noticed a possible security hole: we're + constructing the template name using interpolated content from the browser + (``template="about/%s.html" % page``). At first glance, this looks like a + classic *directory traversal* vulnerability. But is it really? + + Not exactly. Yes, a maliciously crafted value of ``page`` could cause + directory traversal, but although ``page`` *is* taken from the request URL, + not every value will be accepted. The key is in the URLconf: we're using + the regular expression ``\w+`` to match the ``page`` part of the URL, and + ``\w`` only accepts letters and numbers. Thus, any malicious characters + (dots and slashes, here) will be rejected by the URL resolver before they + reach the view itself. + +Generic views of objects +======================== + +The ``direct_to_template`` certainly is useful, but Django's generic views +really shine when it comes to presenting views on your database content. Because +it's such a common task, Django comes with a handful of built-in generic views +that make generating list and detail views of objects incredibly easy. + +Let's take a look at one of these generic views: the "object list" view. We'll +be using these models:: + + # models.py + from django.db import models + + class Publisher(models.Model): + name = models.CharField(max_length=30) + address = models.CharField(max_length=50) + city = models.CharField(max_length=60) + state_province = models.CharField(max_length=30) + country = models.CharField(max_length=50) + website = models.URLField() + + def __unicode__(self): + return self.name + + class Meta: + ordering = ["-name"] + + class Book(models.Model): + title = models.CharField(max_length=100) + authors = models.ManyToManyField('Author') + publisher = models.ForeignKey(Publisher) + publication_date = models.DateField() + +To build a list page of all publishers, we'd use a URLconf along these lines:: + + from django.conf.urls.defaults import * + from django.views.generic import list_detail + from books.models import Publisher + + publisher_info = { + "queryset" : Publisher.objects.all(), + } + + urlpatterns = patterns('', + (r'^publishers/$', list_detail.object_list, publisher_info) + ) + +That's all the Python code we need to write. We still need to write a template, +however. We could explicitly tell the ``object_list`` view which template to use +by including a ``template_name`` key in the extra arguments dictionary, but in +the absence of an explicit template Django will infer one from the object's +name. In this case, the inferred template will be +``"books/publisher_list.html"`` -- the "books" part comes from the name of the +app that defines the model, while the "publisher" bit is just the lowercased +version of the model's name. + +.. highlightlang:: html+django + +This template will be rendered against a context containing a variable called +``object_list`` that contains all the publisher objects. A very simple template +might look like the following:: + + {% extends "base.html" %} + + {% block content %} +

Publishers

+ + {% endblock %} + +That's really all there is to it. All the cool features of generic views come +from changing the "info" dictionary passed to the generic view. The +:doc:`generic views reference` documents all the generic +views and all their options in detail; the rest of this document will consider +some of the common ways you might customize and extend generic views. + +Extending generic views +======================= + +.. highlightlang:: python + +There's no question that using generic views can speed up development +substantially. In most projects, however, there comes a moment when the +generic views no longer suffice. Indeed, the most common question asked by new +Django developers is how to make generic views handle a wider array of +situations. + +Luckily, in nearly every one of these cases, there are ways to simply extend +generic views to handle a larger array of use cases. These situations usually +fall into a handful of patterns dealt with in the sections that follow. + +Making "friendly" template contexts +----------------------------------- + +You might have noticed that our sample publisher list template stores all the +books in a variable named ``object_list``. While this works just fine, it isn't +all that "friendly" to template authors: they have to "just know" that they're +dealing with publishers here. A better name for that variable would be +``publisher_list``; that variable's content is pretty obvious. + +We can change the name of that variable easily with the ``template_object_name`` +argument: + +.. parsed-literal:: + + publisher_info = { + "queryset" : Publisher.objects.all(), + **"template_object_name" : "publisher",** + } + + urlpatterns = patterns('', + (r'^publishers/$', list_detail.object_list, publisher_info) + ) + +Providing a useful ``template_object_name`` is always a good idea. Your +coworkers who design templates will thank you. + +Adding extra context +-------------------- + +Often you simply need to present some extra information beyond that provided by +the generic view. For example, think of showing a list of all the books on each +publisher detail page. The ``object_detail`` generic view provides the +publisher to the context, but it seems there's no way to get additional +information in that template. + +But there is: all generic views take an extra optional parameter, +``extra_context``. This is a dictionary of extra objects that will be added to +the template's context. So, to provide the list of all books on the detail +detail view, we'd use an info dict like this: + +.. parsed-literal:: + + from books.models import Publisher, **Book** + + publisher_info = { + "queryset" : Publisher.objects.all(), + "template_object_name" : "publisher", + **"extra_context" : {"book_list" : Book.objects.all()}** + } + +This would populate a ``{{ book_list }}`` variable in the template context. +This pattern can be used to pass any information down into the template for the +generic view. It's very handy. + +However, there's actually a subtle bug here -- can you spot it? + +The problem has to do with when the queries in ``extra_context`` are evaluated. +Because this example puts ``Book.objects.all()`` in the URLconf, it will +be evaluated only once (when the URLconf is first loaded). Once you add or +remove books, you'll notice that the generic view doesn't reflect those +changes until you reload the Web server (see :ref:`caching-and-querysets` +for more information about when QuerySets are cached and evaluated). + +.. note:: + + This problem doesn't apply to the ``queryset`` generic view argument. Since + Django knows that particular QuerySet should *never* be cached, the generic + view takes care of clearing the cache when each view is rendered. + +The solution is to use a callback in ``extra_context`` instead of a value. Any +callable (i.e., a function) that's passed to ``extra_context`` will be evaluated +when the view is rendered (instead of only once). You could do this with an +explicitly defined function: + +.. parsed-literal:: + + def get_books(): + return Book.objects.all() + + publisher_info = { + "queryset" : Publisher.objects.all(), + "template_object_name" : "publisher", + "extra_context" : **{"book_list" : get_books}** + } + +or you could use a less obvious but shorter version that relies on the fact that +``Book.objects.all`` is itself a callable: + +.. parsed-literal:: + + publisher_info = { + "queryset" : Publisher.objects.all(), + "template_object_name" : "publisher", + "extra_context" : **{"book_list" : Book.objects.all}** + } + +Notice the lack of parentheses after ``Book.objects.all``; this references +the function without actually calling it (which the generic view will do later). + +Viewing subsets of objects +-------------------------- + +Now let's take a closer look at this ``queryset`` key we've been using all +along. Most generic views take one of these ``queryset`` arguments -- it's how +the view knows which set of objects to display (see :doc:`/topics/db/queries` for +more information about ``QuerySet`` objects, and see the +:doc:`generic views reference` for the complete details). + +To pick a simple example, we might want to order a list of books by +publication date, with the most recent first: + +.. parsed-literal:: + + book_info = { + "queryset" : Book.objects.all().order_by("-publication_date"), + } + + urlpatterns = patterns('', + (r'^publishers/$', list_detail.object_list, publisher_info), + **(r'^books/$', list_detail.object_list, book_info),** + ) + + +That's a pretty simple example, but it illustrates the idea nicely. Of course, +you'll usually want to do more than just reorder objects. If you want to +present a list of books by a particular publisher, you can use the same +technique: + +.. parsed-literal:: + + **acme_books = {** + **"queryset": Book.objects.filter(publisher__name="Acme Publishing"),** + **"template_name" : "books/acme_list.html"** + **}** + + urlpatterns = patterns('', + (r'^publishers/$', list_detail.object_list, publisher_info), + **(r'^books/acme/$', list_detail.object_list, acme_books),** + ) + +Notice that along with a filtered ``queryset``, we're also using a custom +template name. If we didn't, the generic view would use the same template as the +"vanilla" object list, which might not be what we want. + +Also notice that this isn't a very elegant way of doing publisher-specific +books. If we want to add another publisher page, we'd need another handful of +lines in the URLconf, and more than a few publishers would get unreasonable. +We'll deal with this problem in the next section. + +.. note:: + + If you get a 404 when requesting ``/books/acme/``, check to ensure you + actually have a Publisher with the name 'ACME Publishing'. Generic + views have an ``allow_empty`` parameter for this case. See the + :doc:`generic views reference` for more details. + +Complex filtering with wrapper functions +---------------------------------------- + +Another common need is to filter down the objects given in a list page by some +key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but +what if we wanted to write a view that displayed all the books by some arbitrary +publisher? We can "wrap" the ``object_list`` generic view to avoid writing a lot +of code by hand. As usual, we'll start by writing a URLconf: + +.. parsed-literal:: + + from books.views import books_by_publisher + + urlpatterns = patterns('', + (r'^publishers/$', list_detail.object_list, publisher_info), + **(r'^books/(\\w+)/$', books_by_publisher),** + ) + +Next, we'll write the ``books_by_publisher`` view itself:: + + from django.http import Http404 + from django.views.generic import list_detail + from books.models import Book, Publisher + + def books_by_publisher(request, name): + + # Look up the publisher (and raise a 404 if it can't be found). + try: + publisher = Publisher.objects.get(name__iexact=name) + except Publisher.DoesNotExist: + raise Http404 + + # Use the object_list view for the heavy lifting. + return list_detail.object_list( + request, + queryset = Book.objects.filter(publisher=publisher), + template_name = "books/books_by_publisher.html", + template_object_name = "books", + extra_context = {"publisher" : publisher} + ) + +This works because there's really nothing special about generic views -- they're +just Python functions. Like any view function, generic views expect a certain +set of arguments and return ``HttpResponse`` objects. Thus, it's incredibly easy +to wrap a small function around a generic view that does additional work before +(or after; see the next section) handing things off to the generic view. + +.. note:: + + Notice that in the preceding example we passed the current publisher being + displayed in the ``extra_context``. This is usually a good idea in wrappers + of this nature; it lets the template know which "parent" object is currently + being browsed. + +Performing extra work +--------------------- + +The last common pattern we'll look at involves doing some extra work before +or after calling the generic view. + +Imagine we had a ``last_accessed`` field on our ``Author`` object that we were +using to keep track of the last time anybody looked at that author:: + + # models.py + + class Author(models.Model): + salutation = models.CharField(max_length=10) + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=40) + email = models.EmailField() + headshot = models.ImageField(upload_to='/tmp') + last_accessed = models.DateTimeField() + +The generic ``object_detail`` view, of course, wouldn't know anything about this +field, but once again we could easily write a custom view to keep that field +updated. + +First, we'd need to add an author detail bit in the URLconf to point to a +custom view: + +.. parsed-literal:: + + from books.views import author_detail + + urlpatterns = patterns('', + #... + **(r'^authors/(?P\\d+)/$', author_detail),** + ) + +Then we'd write our wrapper function:: + + import datetime + from books.models import Author + from django.views.generic import list_detail + from django.shortcuts import get_object_or_404 + + def author_detail(request, author_id): + # Look up the Author (and raise a 404 if she's not found) + author = get_object_or_404(Author, pk=author_id) + + # Record the last accessed date + author.last_accessed = datetime.datetime.now() + author.save() + + # Show the detail page + return list_detail.object_detail( + request, + queryset = Author.objects.all(), + object_id = author_id, + ) + +.. note:: + + This code won't actually work unless you create a + ``books/author_detail.html`` template. + +We can use a similar idiom to alter the response returned by the generic view. +If we wanted to provide a downloadable plain-text version of the list of +authors, we could use a view like this:: + + def author_list_plaintext(request): + response = list_detail.object_list( + request, + queryset = Author.objects.all(), + mimetype = "text/plain", + template_name = "books/author_list.txt" + ) + response["Content-Disposition"] = "attachment; filename=authors.txt" + return response + +This works because the generic views return simple ``HttpResponse`` objects +that can be treated like dictionaries to set HTTP headers. This +``Content-Disposition`` business, by the way, instructs the browser to +download and save the page instead of displaying it in the browser.