mirror of
https://github.com/django/django.git
synced 2024-12-23 17:46:27 +00:00
940d17409e
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16256 bcc190cf-cafb-0310-a4f2-bffc1f526a37
617 lines
23 KiB
Plaintext
617 lines
23 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 entry point 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()
|
|
|
|
class Meta:
|
|
ordering = ["-name"]
|
|
|
|
def __unicode__(self):
|
|
return self.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.
|
|
|
|
.. note::
|
|
Thus, when (for example) the :class:`django.template.loaders.app_directories.Loader`
|
|
template loader is enabled in :setting:`TEMPLATE_LOADERS`, the template
|
|
location would be::
|
|
|
|
/path/to/project/books/templates/books/publisher_list.html
|
|
|
|
.. 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.
|
|
|
|
Well, if you're dealing with a model object, this is already done for
|
|
you. When you are dealing with an object or queryset, Django is able
|
|
to populate the context using the verbose name (or the plural verbose
|
|
name, in the case of a list of objects) of the object being displayed.
|
|
This is provided in addition to the default ``object_list`` entry, but
|
|
contains exactly the same data.
|
|
|
|
If the verbose name (or plural verbose name) still isn't a good match,
|
|
you can manually set the name of the context variable. The
|
|
``context_object_name`` attribute on a generic view specifies the
|
|
context variable to use. In this example, 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
|
|
``render_to_response()`` implementation will override the versions provided by
|
|
:class:`JSONResponseMixin` and
|
|
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
|
|
|
|
Decorating class-based views
|
|
============================
|
|
|
|
.. highlightlang:: python
|
|
|
|
The extension of class-based views isn't limited to using mixins. You
|
|
can use also use decorators.
|
|
|
|
Decorating in URLconf
|
|
---------------------
|
|
|
|
The simplest way of decorating class-based views is to decorate the
|
|
result of the :meth:`~django.views.generic.base.View.as_view` method.
|
|
The easiest place to do this is in the URLconf where you deploy your
|
|
view::
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.views.generic import TemplateView
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^about/',login_required(TemplateView.as_view(template_name="secret.html"))),
|
|
)
|
|
|
|
This approach applies the decorator on a per-instance basis. If you
|
|
want every instance of a view to be decorated, you need to take a
|
|
different approach.
|
|
|
|
Decorating the class
|
|
--------------------
|
|
|
|
To decorate every instance of a class-based view, you need to decorate
|
|
the class definition itself. To do this you apply the decorator to the
|
|
:meth:`~django.views.generic.base.View.dispatch` method of the class.
|
|
|
|
A method on a class isn't quite the same as a standalone function, so
|
|
you can't just apply a function decorator to the method -- you need to
|
|
transform it into a method decorator first. The ``method_decorator``
|
|
decorator transforms a function decorator into a method decorator so
|
|
that it can be used on an instance method. For example::
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.utils.decorators import method_decorator
|
|
from django.views.generic import TemplateView
|
|
|
|
class ProtectedView(TemplateView):
|
|
template_name = 'secret.html'
|
|
|
|
@method_decorator(login_required)
|
|
def dispatch(self, *args, **kwargs):
|
|
return super(ProtectedView, self).dispatch(*args, **kwargs)
|
|
|
|
In this example, every instance of ``ProtectedView`` will have
|
|
login protection.
|
|
|
|
.. note::
|
|
|
|
``method_decorator`` passes ``*args`` and ``**kwargs``
|
|
as parameters to the decorated method on the class. If your method
|
|
does not accept a compatible set of parameters it will raise a
|
|
``TypeError`` exception.
|