123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- =============
- Generic views
- =============
- .. versionchanged:: 1.3
- .. note::
- From Django 1.3, function-based generic views have been deprecated in favor
- of a class-based approach, described in the class-based views :doc:`topic
- guide </topics/class-based-views>` and :doc:`detailed reference
- </ref/class-based-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 import patterns, url, include
- 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/<whatever>/`` to
- statically rendered ``about/<whatever>.html``. We'll do this by first modifying
- the URLconf to point to a view function:
- .. parsed-literal::
- from django.conf.urls import patterns, url, include
- 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 import patterns, url, include
- 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 %}
- <h2>Publishers</h2>
- <ul>
- {% for publisher in object_list %}
- <li>{{ publisher.name }}</li>
- {% endfor %}
- </ul>
- {% 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</ref/generic-views>` 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</ref/generic-views>` 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</ref/generic-views>` 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<author_id>\\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.
|