mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/trunk@14409 bcc190cf-cafb-0310-a4f2-bffc1f526a37
		
			
				
	
	
		
			540 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			540 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| =========================
 | |
| Class-based generic views
 | |
| =========================
 | |
| 
 | |
| .. versionadded:: 1.3
 | |
| 
 | |
| .. note::
 | |
|     Prior to Django 1.3, generic views were implemented as functions. The
 | |
|     function-based implementation has been deprecated in favor of the
 | |
|     class-based approach described here.
 | |
| 
 | |
|     For details on the previous generic views implementation,
 | |
|     see the :doc:`topic guide </topics/generic-views>` and
 | |
|     :doc:`detailed reference </ref/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 ``TalkListView`` and a
 | |
|       ``RegisteredUserListView`` 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 <http://www.djangoproject.com/weblog/>`_'s
 | |
|       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.
 | |
| 
 | |
| 
 | |
| Simple usage
 | |
| ============
 | |
| 
 | |
| Class-based generic views (and any class-based views that inherit from
 | |
| the base classes Django provides) can be configured in two
 | |
| ways: subclassing, or passing in arguments directly in the URLconf.
 | |
| 
 | |
| When you subclass a class-based view, you can override attributes
 | |
| (such as the ``template_name``) or methods (such as ``get_context_data``)
 | |
| in your subclass to provide new values or methods. Consider, for example,
 | |
| a view that just displays one template, ``about.html``. Django has a
 | |
| generic view to do this - :class:`~django.views.generic.base.TemplateView` -
 | |
| so we can just subclass it, and override the template name::
 | |
| 
 | |
|     # some_app/views.py
 | |
|     from django.views.generic import TemplateView
 | |
| 
 | |
|     class AboutView(TemplateView):
 | |
|         template_name = "about.html"
 | |
| 
 | |
| Then, we just need to add this new view into our URLconf. As the class-based
 | |
| views themselves are classes, we point the URL to the as_view class method
 | |
| instead, which is the entrypoint for class-based views::
 | |
| 
 | |
|     # urls.py
 | |
|     from django.conf.urls.defaults import *
 | |
|     from some_app.views import AboutView
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         (r'^about/', AboutView.as_view()),
 | |
|     )
 | |
| 
 | |
| Alternatively, if you're only changing a few simple attributes on a
 | |
| class-based view, you can simply pass the new attributes into the as_view
 | |
| method call itself::
 | |
| 
 | |
|     from django.conf.urls.defaults import *
 | |
|     from django.views.generic import TemplateView
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         (r'^about/', TemplateView.as_view(template_name="about.html")),
 | |
|     )
 | |
| 
 | |
| A similar overriding pattern can be used for the ``url`` attribute on
 | |
| :class:`~django.views.generic.base.RedirectView`, another simple
 | |
| generic view.
 | |
| 
 | |
| 
 | |
| Generic views of objects
 | |
| ========================
 | |
| 
 | |
| :class:`~django.views.generic.base.TemplateView` certainly is useful,
 | |
| but Django's generic views really shine when it comes to presenting
 | |
| views of 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 ListView
 | |
|     from books.models import Publisher
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         (r'^publishers/$', ListView.as_view(
 | |
|             model=Publisher,
 | |
|         )),
 | |
|     )
 | |
| 
 | |
| That's all the Python code we need to write. We still need to write a template,
 | |
| however. We could explicitly tell the view which template to use
 | |
| by including a ``template_name`` key in the arguments to as_view, 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/class-based-views>` documents all the generic
 | |
| views and 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.
 | |
| 
 | |
| This is one of the reasons generic views were redesigned for the 1.3 release -
 | |
| previously, they were just view functions with a bewildering array of options;
 | |
| now, rather than passing in a large amount of configuration in the URLconf,
 | |
| the recommended way to extend generic views is to subclass them, and override
 | |
| their attributes or methods.
 | |
| 
 | |
| 
 | |
| Making "friendly" template contexts
 | |
| -----------------------------------
 | |
| 
 | |
| You might have noticed that our sample publisher list template stores all the
 | |
| publishers 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 more obvious name for that variable
 | |
| would be ``publisher_list``.
 | |
| 
 | |
| We can change the name of that variable easily with the ``context_object_name``
 | |
| attribute - here, we'll override it in the URLconf, since it's a simple change:
 | |
| 
 | |
| .. parsed-literal::
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         (r'^publishers/$', ListView.as_view(
 | |
|             model=Publisher,
 | |
|             **context_object_name="publisher_list",**
 | |
|         )),
 | |
|     )
 | |
| 
 | |
| Providing a useful ``context_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
 | |
| :class:`~django.views.generic.detail.DetailView` generic view provides
 | |
| the publisher to the context, but it seems there's no way to get
 | |
| additional information in that template.
 | |
| 
 | |
| However, there is; you can subclass
 | |
| :class:`~django.views.generic.detail.DetailView` and provide your own
 | |
| implementation of the ``get_context_data`` method. The default
 | |
| implementation of this that comes with
 | |
| :class:`~django.views.generic.detail.DetailView` simply adds in the
 | |
| object being displayed to the template, but you can override it to show
 | |
| more::
 | |
| 
 | |
|     from django.views.generic import DetailView
 | |
|     from books.models import Publisher, Book
 | |
| 
 | |
|     class PublisherDetailView(DetailView):
 | |
| 
 | |
|         context_object_name = "publisher"
 | |
|         model = Publisher
 | |
| 
 | |
|         def get_context_data(self, **kwargs):
 | |
|             # Call the base implementation first to get a context
 | |
|             context = super(PublisherDetailView, self).get_context_data(**kwargs)
 | |
|             # Add in a QuerySet of all the books
 | |
|             context['book_list'] = Book.objects.all()
 | |
|             return context
 | |
| 
 | |
| 
 | |
| Viewing subsets of objects
 | |
| --------------------------
 | |
| 
 | |
| Now let's take a closer look at the ``model`` argument we've been
 | |
| using all along. The ``model`` argument, which specifies the database
 | |
| model that the view will operate upon, is available on all the
 | |
| generic views that operate on a single object or a collection of
 | |
| objects. However, the ``model`` argument is not the only way to
 | |
| specify the objects that the view will operate upon -- you can also
 | |
| specify the list of objects using the ``queryset`` argument::
 | |
| 
 | |
|     from django.views.generic import DetailView
 | |
|     from books.models import Publisher, Book
 | |
| 
 | |
|     class PublisherDetailView(DetailView):
 | |
| 
 | |
|         context_object_name = "publisher"
 | |
|         queryset = Publisher.objects.all()
 | |
| 
 | |
| Specifying ``model = Publisher`` is really just shorthand for saying
 | |
| ``queryset = Publisher.objects.all()``. However, by using ``queryset``
 | |
| to define a filtered list of objects you can be more specific about the
 | |
| objects that will be visible in the view (see :doc:`/topics/db/queries`
 | |
| for more information about :class:`QuerySet` objects, and see the
 | |
| :doc:`class-based views reference </ref/class-based-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::
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         (r'^publishers/$', ListView.as_view(
 | |
|             queryset=Publisher.objects.all(),
 | |
|             context_object_name="publisher_list",
 | |
|         )),
 | |
|         (r'^books/$', ListView.as_view(
 | |
|             queryset=Book.objects.order_by("-publication_date"),
 | |
|             context_object_name="book_list",
 | |
|         )),
 | |
|     )
 | |
| 
 | |
| 
 | |
| 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 (here, illustrated using subclassing rather than by passing arguments
 | |
| in the URLconf)::
 | |
| 
 | |
|     from django.views.generic import ListView
 | |
|     from books.models import Book
 | |
| 
 | |
|     class AcmeBookListView(ListView):
 | |
| 
 | |
|         context_object_name = "book_list"
 | |
|         queryset = Book.objects.filter(publisher__name="Acme Publishing")
 | |
|         template_name = "books/acme_list.html"
 | |
| 
 | |
| 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:`class-based-views reference</ref/class-based-views>` for more details.
 | |
| 
 | |
| 
 | |
| Dynamic filtering
 | |
| -----------------
 | |
| 
 | |
| 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?
 | |
| 
 | |
| Handily, the ListView has a
 | |
| :meth:`~django.views.generic.detail.ListView.get_queryset` method we can
 | |
| override. Previously, it has just been returning the value of the ``queryset``
 | |
| attribute, but now we can add more logic.
 | |
| 
 | |
| The key part to making this work is that when class-based views are called,
 | |
| various useful things are stored on ``self``; as well as the request
 | |
| (``self.request``) this includes the positional (``self.args``) and name-based
 | |
| (``self.kwargs``) arguments captured according to the URLconf.
 | |
| 
 | |
| Here, we have a URLconf with a single captured group::
 | |
| 
 | |
|     from books.views import PublisherBookListView
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         (r'^books/(\w+)/$', PublisherBookListView.as_view()),
 | |
|     )
 | |
| 
 | |
| Next, we'll write the ``PublisherBookListView`` view itself::
 | |
| 
 | |
|     from django.shortcuts import get_object_or_404
 | |
|     from django.views.generic import ListView
 | |
|     from books.models import Book, Publisher
 | |
| 
 | |
|     class PublisherBookListView(ListView):
 | |
| 
 | |
|         context_object_name = "book_list"
 | |
|         template_name = "books/books_by_publisher.html",
 | |
| 
 | |
|         def get_queryset(self):
 | |
|             publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
 | |
|             return Book.objects.filter(publisher=publisher)
 | |
| 
 | |
| As you can see, it's quite easy to add more logic to the queryset selection;
 | |
| if we wanted, we could use ``self.request.user`` to filter using the current
 | |
| user, or other more complex logic.
 | |
| 
 | |
| We can also add the publisher into the context at the same time, so we can
 | |
| use it in the template::
 | |
| 
 | |
|     class PublisherBookListView(ListView):
 | |
| 
 | |
|         context_object_name = "book_list"
 | |
|         template_name = "books/books_by_publisher.html",
 | |
| 
 | |
|         def get_queryset(self):
 | |
|             self.publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
 | |
|             return Book.objects.filter(publisher=self.publisher)
 | |
| 
 | |
|         def get_context_data(self, **kwargs):
 | |
|             # Call the base implementation first to get a context
 | |
|             context = super(PublisherBookListView, self).get_context_data(**kwargs)
 | |
|             # Add in the publisher
 | |
|             context['publisher'] = self.publisher
 | |
|             return context
 | |
| 
 | |
| 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 ``DetailView`` class, 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 AuthorDetailView
 | |
| 
 | |
|     urlpatterns = patterns('',
 | |
|         #...
 | |
|         **(r'^authors/(?P<pk>\\d+)/$', AuthorDetailView.as_view()),**
 | |
|     )
 | |
| 
 | |
| Then we'd write our new view - ``get_object`` is the method that retrieves the
 | |
| object, so we simply override it and wrap the call::
 | |
| 
 | |
|     import datetime
 | |
|     from books.models import Author
 | |
|     from django.views.generic import DetailView
 | |
|     from django.shortcuts import get_object_or_404
 | |
| 
 | |
|     class AuthorDetailView(DetailView):
 | |
| 
 | |
|         queryset = Author.objects.all()
 | |
| 
 | |
|         def get_object(self):
 | |
|             # Call the superclass
 | |
|             object = super(AuthorDetailView, self).get_object()
 | |
|             # Record the last accessed date
 | |
|             object.last_accessed = datetime.datetime.now()
 | |
|             object.save()
 | |
|             # Return the object
 | |
|             return object
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     This code won't actually work unless you create a
 | |
|     ``books/author_detail.html`` template.
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     The URLconf here uses the named group ``pk`` - this name is the default
 | |
|     name that DetailView uses to find the value of the primary key used to
 | |
|     filter the queryset.
 | |
| 
 | |
|     If you want to change it, you'll need to do your own ``get()`` call
 | |
|     on ``self.queryset`` using the new named parameter from ``self.kwargs``.
 | |
| 
 | |
| More than just HTML
 | |
| -------------------
 | |
| 
 | |
| So far, we've been focusing on rendering templates to generate
 | |
| responses. However, that's not all generic views can do.
 | |
| 
 | |
| Each generic view is composed out of a series of mixins, and each
 | |
| mixin contributes a little piece of the entire view. Some of these
 | |
| mixins -- such as
 | |
| :class:`~django.views.generic.base.TemplateResponseMixin` -- are
 | |
| specifically designed for rendering content to an HTML response using a
 | |
| template. However, you can write your own mixins that perform
 | |
| different rendering behavior.
 | |
| 
 | |
| For example, a simple JSON mixin might look something like this::
 | |
| 
 | |
|     from django import http
 | |
|     from django.utils import simplejson as json
 | |
| 
 | |
|     class JSONResponseMixin(object):
 | |
|         def render_to_response(self, context):
 | |
|             "Returns a JSON response containing 'context' as payload"
 | |
|             return self.get_json_response(self.convert_context_to_json(context))
 | |
| 
 | |
|         def get_json_response(self, content, **httpresponse_kwargs):
 | |
|             "Construct an `HttpResponse` object."
 | |
|             return http.HttpResponse(content,
 | |
|                                      content_type='application/json',
 | |
|                                      **httpresponse_kwargs)
 | |
| 
 | |
|         def convert_context_to_json(self, context):
 | |
|             "Convert the context dictionary into a JSON object"
 | |
|             # Note: This is *EXTREMELY* naive; in reality, you'll need
 | |
|             # to do much more complex handling to ensure that arbitrary
 | |
|             # objects -- such as Django model instances or querysets
 | |
|             # -- can be serialized as JSON.
 | |
|             return json.dumps(context)
 | |
| 
 | |
| Then, you could build a JSON-returning
 | |
| :class:`~django.views.generic.detail.DetailView` by mixing your
 | |
| :class:`JSONResponseMixin` with the
 | |
| :class:`~django.views.generic.detail.BaseDetailView` -- (the
 | |
| :class:`~django.views.generic.detail.DetailView` before template
 | |
| rendering behavior has been mixed in)::
 | |
| 
 | |
|     class JSONDetailView(JSONResponseMixin, BaseDetailView):
 | |
|         pass
 | |
| 
 | |
| This view can then be deployed in the same way as any other
 | |
| :class:`~django.views.generic.detail.DetailView`, with exactly the
 | |
| same behavior -- except for the format of the response.
 | |
| 
 | |
| If you want to be really adventurous, you could even mix a
 | |
| :class:`~django.views.generic.detail.DetailView` subclass that is able
 | |
| to return *both* HTML and JSON content, depending on some property of
 | |
| the HTTP request, such as a query argument or a HTTP header. Just mix
 | |
| in both the :class:`JSONResponseMixin` and a
 | |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
 | |
| and override the implementation of :func:`render_to_response()` to defer
 | |
| to the appropriate subclass depending on the type of response that the user
 | |
| requested::
 | |
| 
 | |
|     class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
 | |
|         def render_to_response(self, context):
 | |
|             # Look for a 'format=json' GET argument
 | |
|             if self.request.GET.get('format','html') == 'json':
 | |
|                 return JSONResponseMixin.render_to_response(self, context)
 | |
|             else:
 | |
|                 return SingleObjectTemplateResponseMixin.render_to_response(self, context)
 | |
| 
 | |
| Because of the way that Python resolves method overloading, the local
 | |
| :func:`render_to_response()` implementation will override the
 | |
| versions provided by :class:`JSONResponseMixin` and
 | |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
 |