Fixed #18451 -- Vastly improved class based view documentation.

Many thanks to Daniel Greenfeld, James Aylett, Marc Tamlyn, Simon Williams, Danilo Bargen and Luke Plant for their work on this.
This commit is contained in:
Jannis Leidel 2012-06-11 10:34:00 +02:00
parent 1a10a06b9f
commit c4c7fbcc0d
36 changed files with 3112 additions and 2016 deletions

View file

@ -0,0 +1,432 @@
.. _Generic views:
=========================
Class-based generic views
=========================
.. note::
Prior to Django 1.3, generic views were implemented as functions. The
function-based implementation has been removed in favor of the
class-based approach described here.
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:
* 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.
* 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.
Extending generic views
=======================
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.
That said, generic views will have a limit. If you find you're struggling to
implement your view as a subclass of a generic view, then you may find it more
effective to write just the code you need, using your own class-based or
functional views.
More examples of generic views are available in some third party applications,
or you could write your own as needed.
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 start by looking at some examples of showing a list of objects or an
individual object.
.. comment: link here to the other topic pages (form handling, date based, mixins)
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()
Now we need to define a view::
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
Finally hook that view into your urls::
# urls.py
from django.conf.urls import patterns, url, include
from books.views import PublisherList
urlpatterns = patterns('',
url(r'^publishers/$', PublisherList.as_view()),
)
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 adding a
``template_name`` attribute to the 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`, a template location could 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 attributes set on the generic view. The
:doc:`generic views reference</ref/class-based-views/index>` 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.
Making "friendly" template contexts
-----------------------------------
.. highlightlang:: python
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 lower cased version of the model class' name. This is
provided in addition to the default ``object_list`` entry, but contains exactly
the same data, i.e. ``publisher_list``.
If the this 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::
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favourite_publishers'
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 how do we 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 send
more::
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetail(DetailView):
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
.. note::
Generally, get_context_data will merge the context data of all parent
classes with those of the current class. To preserve this behavior in your
own classes where you want to alter the context, you should be sure to call
get_context_data on the super class. When no two classes try to define the
same key, this will give the expected results. However if any class
attempts to override a key after parent classes have set it (after the call
to super), any children of that class will also need to explictly set it
after super if they want to be sure to override all parents. If you're
having trouble, review the method resolution order of your view.
.. _generic-views-list-subsets:
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 PublisherDetail(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/index>` 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::
from django.views.generic import ListView
from books.models import Book
class BookList(ListView):
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::
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/index>` 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::
# urls.py
from books.views import PublisherBookList
urlpatterns = patterns('',
(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
)
Next, we'll write the ``PublisherBookList`` view itself::
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookList(ListView):
template_name = 'books/books_by_publisher.html'
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.args[0])
return Book.objects.filter(publisher=self.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::
# ...
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
.. _generic-views-extra-work:
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)
name = models.CharField(max_length=200)
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::
from books.views import AuthorDetailView
urlpatterns = patterns('',
#...
url(r'^authors/(?P<pk>\\d+)/$', AuthorDetailView.as_view(), name='author-detail'),
)
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::
from django.views.generic import DetailView
from django.shortcuts import get_object_or_404
from django.utils import timezone
from books.models import Author
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 = timezone.now()
object.save()
# Return the object
return object
.. 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 call the group something else, you can set ``pk_url_kwarg``
on the view. More details can be found in the reference for
:class:`~django.views.generic.detail.DetailView`

View file

@ -0,0 +1,205 @@
Form handling with class-based views
====================================
Form processing generally has 3 paths:
* Initial GET (blank or prepopulated form)
* POST with invalid data (typically redisplay form with errors)
* POST with valid data (process the data and typically redirect)
Implementing this yourself often results in a lot of repeated
boilerplate code (see :ref:`Using a form in a
view<using-a-form-in-a-view>`). To help avoid this, Django provides a
collection of generic class-based views for form processing.
Basic Forms
-----------
Given a simple contact form::
# forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
The view can be constructed using a FormView::
# views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super(ContactView, self).form_valid(form)
Notes:
* FormView inherits
:class:`~django.views.generic.base.TemplateResponseMixin` so
:attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
can be used here
* The default implementation for
:meth:`~django.views.generic.edit.FormView.form_valid` simply
redirects to the :attr:`success_url`
Model Forms
-----------
Generic views really shine when working with models. These generic
views will automatically create a :class:`ModelForm`, so long as they
can work out which model class to use:
* If the :attr:`model` attribute is given, that model class will be used
* If :meth:`get_object()` returns an object, the class of that object
will be used
* If a :attr:`queryset` is given, the model for that queryset will be used
Model form views provide a :meth:`form_valid()` implementation that
saves the model automatically. You can override this if you have any
special requirements; see below for examples.
You don't even need to provide a attr:`success_url` for
:class:`~django.views.generic.edit.CreateView` or
:class:`~django.views.generic.edit.UpdateView` - they will use
:meth:`get_absolute_url()` on the model object if available.
If you want to use a custom :class:`ModelForm` (for instance to add
extra validation) simply set
:attr:`~django.views.generic.edit.FormMixin.form_class` on your view.
.. note::
When specifying a custom form class, you must still specify the model,
even though the :attr:`form_class` may be a :class:`ModelForm`.
First we need to add :meth:`get_absolute_url()` to our :class:`Author`
class:
.. code-block:: python
# models.py
from django import models
from django.core.urlresolvers import reverse
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
Then we can use :class:`CreateView` and friends to do the actual
work. Notice how we're just configuring the generic class-based views
here; we don't have to write any logic ourselves::
# views.py
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
class AuthorUpdate(UpdateView):
model = Author
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
.. note::
We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not
just ``reverse`` as the urls are not loaded when the file is imported.
Finally, we hook these new views into the URLconf::
# urls.py
from django.conf.urls import patterns, url
from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
urlpatterns = patterns('',
# ...
url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
url(r'author/(?P<pk>\d+)/$', AuthorUpdate.as_view(), name='author_update'),
url(r'author/(?P<pk>\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
)
.. note::
These views inherit :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
which uses :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_prefix`
to construct the
:attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
based on the model.
In this example:
* :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html``
* :class:`DeleteView` uses ``myapp/author_confirm_delete.html``
If you wish to have separate templates for :class:`CreateView` and
:class:1UpdateView`, you can set either :attr:`template_name` or
:attr:`template_name_suffix` on your view class.
Models and request.user
-----------------------
To track the user that created an object using a :class:`CreateView`,
you can use a custom :class:`ModelForm` to do this. First, add the
foreign key relation to the model::
# models.py
from django import models
from django.contrib.auth import User
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User)
# ...
Create a custom :class:`ModelForm` in order to exclude the
``created_by`` field and prevent the user from editing it:
.. code-block:: python
# forms.py
from django import forms
from myapp.models import Author
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
exclude = ('created_by',)
In the view, use the custom :attr:`form_class` and override
:meth:`form_valid()` to add the user::
# views.py
from django.views.generic.edit import CreateView
from myapp.models import Author
from myapp.forms import AuthorForm
class AuthorCreate(CreateView):
form_class = AuthorForm
model = Author
def form_valid(self, form):
form.instance.created_by = self.request.user
return super(AuthorCreate, self).form_valid(form)
Note that you'll need to :ref:`decorate this
view<decorating-class-based-views>` using
:func:`~django.contrib.auth.decorators.login_required`, or
alternatively handle unauthorised users in the :meth:`form_valid()`.

View file

@ -0,0 +1,233 @@
=================
Class-based views
=================
.. versionadded:: 1.3
A view is a callable which takes a request and returns a
response. This can be more than just a function, and Django provides
an example of some classes which can be used as views. These allow you
to structure your views and reuse code by harnessing inheritance and
mixins. There are also some generic views for simple tasks which we'll
get to later, but you may want to design your own structure of
reusable views which suits your use case. For full details, see the
:doc:`class-based views reference
documentation</ref/class-based-views/index>`.
.. toctree::
:maxdepth: 1
generic-display
generic-editing
mixins
Basic examples
==============
Django provides base view classes which will suit a wide range of applications.
All views inherit from the :class:`~django.views.generic.base.View` class, which
handles linking the view in to the URLs, HTTP method dispatching and other
simple features. :class:`~django.views.generic.base.RedirectView` is for a simple HTTP
redirect, and :class:`~django.views.generic.base.TemplateView` extends the base class
to make it also render a template.
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 import patterns, url, include
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 import patterns, url, include
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`.
.. _jsonresponsemixin-example:
More than just HTML
-------------------
Where class based views shine is when you want to do the same thing many times.
Suppose you're writing an API, and every view should return JSON instead of
rendered HTML.
We can use create a mixin class to use in all of our views, handling the
conversion to JSON once.
For example, a simple JSON mixin might look something like this::
import json
from django import http
class JSONResponseMixin(object):
"""
A mixin that can be used to render a JSON response.
"""
reponse_class = HTTPResponse
def render_to_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
response_kwargs['content_type'] = 'application/json'
return self.response_class(
self.convert_context_to_json(context),
**response_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)
Now we mix this into the base view::
from django.views.generic import View
class JSONView(JSONResponseMixin, View):
pass
Equally we could use our mixin with one of the generic views. We can make our
own version of :class:`~django.views.generic.detail.DetailView` by mixing
: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`.
For more information on how to use the built in generic views, consult the next
topic on :doc:`generic class based views</topics/class-based-views/generic-display>`.
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, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = patterns('',
(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))),
(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
)
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-class-based-views:
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.

View file

@ -0,0 +1,605 @@
===================================
Using mixins with class-based views
===================================
.. versionadded:: 1.3
.. caution::
This is an advanced topic. A working knowledge of :doc:`Django's
class-based views<index>` is advised before exploring these
techniques.
Django's built-in class-based views provide a lot of functionality,
but some of it you may want to use separately. For instance, you may
want to write a view that renders a template to make the HTTP
response, but you can't use
:class:`~django.views.generic.base.TemplateView`; perhaps you need to
render a template only on `POST`, with `GET` doing something else
entirely. While you could use
:class:`~django.template.response.TemplateResponse` directly, this
will likely result in duplicate code.
For this reason, Django also provides a number of mixins that provide
more discrete functionality. Template rendering, for instance, is
encapsulated in the
:class:`~django.views.generic.base.TemplateResponseMixin`. The Django
reference documentation contains :doc:`full documentation of all the
mixins</ref/class-based-views/mixins>`.
Context and template responses
==============================
Two central mixins are provided that help in providing a consistent
interface to working with templates in class-based views.
:class:`~django.views.generic.base.TemplateResponseMixin`
Every built in view which returns a
:class:`~django.template.response.TemplateResponse` will call the
:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
method that :class:`TemplateResponseMixin` provides. Most of the time this
will be called for you (for instance, it is called by the ``get()`` method
implemented by both :class:`~django.views.generic.base.TemplateView` and
:class:`~django.views.generic.base.DetailView`); similarly, it's unlikely
that you'll need to override it, although if you want your response to
return something not rendered via a Django template then you'll want to do
it. For an example of this, see the :ref:`JSONResponseMixin example
<jsonresponsemixin-example>`.
``render_to_response`` itself calls
:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`,
which by default will just look up
:attr:`~django.views.generic.base.TemplateResponseMixin.template_name` on
the class-based view; two other mixins
(:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
and
:class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`)
override this to provide more flexible defaults when dealing with actual
objects.
.. versionadded:: 1.5
:class:`~django.views.generic.base.ContextMixin`
Every built in view which needs context data, such as for rendering a
template (including :class:`TemplateResponseMixin` above), should call
:meth:`~django.views.generic.base.ContextMixin.get_context_data` passing
any data they want to ensure is in there as keyword arguments.
``get_context_data`` returns a dictionary; in :class:`ContextMixin` it
simply returns its keyword arguments, but it is common to override this to
add more members to the dictionary.
Building up Django's generic class-based views
===============================================
Let's look at how two of Django's generic class-based views are built
out of mixins providing discrete functionality. We'll consider
:class:`~django.views.generic.detail.DetailView`, which renders a
"detail" view of an object, and
:class:`~django.views.generic.list.ListView`, which will render a list
of objects, typically from a queryset, and optionally paginate
them. This will introduce us to four mixins which between them provide
useful functionality when working with either a single Django object,
or multiple objects.
There are also mixins involved in the generic edit views
(:class:`~django.views.generic.edit.FormView`, and the model-specific
views :class:`~django.views.generic.edit.CreateView`,
:class:`~django.views.generic.edit.UpdateView` and
:class:`~django.views.generic.edit.DeleteView`), and in the
date-based generic views. These are
covered in the :doc:`mixin reference
documentation</ref/class-based-views/mixins>`.
DetailView: working with a single Django object
-----------------------------------------------
To show the detail of an object, we basically need to do two things:
we need to look up the object and then we need to make a
:class:`TemplateResponse` with a suitable template, and that object as
context.
To get the object, :class:`~django.views.generic.detail.DetailView`
relies on :class:`~django.views.generic.detail.SingleObjectMixin`,
which provides a
:meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
method that figures out the object based on the URL of the request (it
looks for ``pk`` and ``slug`` keyword arguments as declared in the
URLConf, and looks the object up either from the
:attr:`~django.views.generic.detail.SingleObjectMixin.model` attribute
on the view, or the
:attr:`~django.views.generic.detail.SingleObjectMixin.queryset`
attribute if that's provided). :class:`SingleObjectMixin` also overrides
:meth:`~django.views.generic.base.ContextMixin.get_context_data`,
which is used across all Django's built in class-based views to supply
context data for template renders.
To then make a :class:`TemplateResponse`, :class:`DetailView` uses
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
which extends
:class:`~django.views.generic.base.TemplateResponseMixin`, overriding
:meth:`get_template_names()` as discussed above. It actually provides
a fairly sophisticated set of options, but the main one that most
people are going to use is
``<app_label>/<object_name>_detail.html``. The ``_detail`` part can be
changed by setting
:attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
on a subclass to something else. (For instance, the :doc:`generic edit
views<generic-editing>` use ``_form`` for create and update views, and
``_confirm_delete`` for delete views.)
ListView: working with many Django objects
------------------------------------------
Lists of objects follow roughly the same pattern: we need a (possibly
paginated) list of objects, typically a :class:`QuerySet`, and then we need
to make a :class:`TemplateResponse` with a suitable template using
that list of objects.
To get the objects, :class:`~django.views.generic.list.ListView` uses
:class:`~django.views.generic.list.MultipleObjectMixin`, which
provides both
:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`
and
:meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`. Unlike
with :class:`SingleObjectMixin`, there's no need to key off parts of
the URL to figure out the queryset to work with, so the default just
uses the
:attr:`~django.views.generic.list.MultipleObjectMixin.queryset` or
:attr:`~django.views.generic.list.MultipleObjectMixin.model` attribute
on the view class. A common reason to override
:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`
here would be to dynamically vary the objects, such as depending on
the current user or to exclude posts in the future for a blog.
:class:`MultipleObjectMixin` also overrides
:meth:`~django.views.generic.base.ContextMixin.get_context_data` to
include appropriate context variables for pagination (providing
dummies if pagination is disabled). It relies on ``object_list`` being
passed in as a keyword argument, which :class:`ListView` arranges for
it.
To make a :class:`TemplateResponse`, :class:`ListView` then uses
:class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`;
as with :class:`SingleObjectTemplateResponseMixin` above, this
overrides :meth:`get_template_names()` to provide :meth:`a range of
options
<~django.views.generic.list.MultipleObjectTempalteResponseMixin>`,
with the most commonly-used being
``<app_label>/<object_name>_list.html``, with the ``_list`` part again
being taken from the
:attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
attribute. (The date based generic views use suffixes such as ``_archive``,
``_archive_year`` and so on to use different templates for the various
specialised date-based list views.)
Using Django's class-based view mixins
======================================
Now we've seen how Django's generic class-based views use the provided
mixins, let's look at other ways we can combine them. Of course we're
still going to be combining them with either built-in class-based
views, or other generic class-based views, but there are a range of
rarer problems you can solve than are provided for by Django out of
the box.
.. warning::
Not all mixins can be used together, and not all generic class
based views can be used with all other mixins. Here we present a
few examples that do work; if you want to bring together other
functionality then you'll have to consider interactions between
attributes and methods that overlap between the different classes
you're using, and how `method resolution order`_ will affect which
versions of the methods will be called in what order.
The reference documentation for Django's :doc:`class-based
views</ref/class-based-views/index>` and :doc:`class-based view
mixins</ref/class-based-views/mixins>` will help you in
understanding which attributes and methods are likely to cause
conflict between different classes and mixins.
If in doubt, it's often better to back off and base your work on
:class:`View` or :class:`TemplateView`, perhaps with
:class:`SimpleObjectMixin` and
:class:`MultipleObjectMixin`. Although you will probably end up
writing more code, it is more likely to be clearly understandable
to someone else coming to it later, and with fewer interactions to
worry about you will save yourself some thinking. (Of course, you
can always dip into Django's implementation of the generic class
based views for inspiration on how to tackle problems.)
.. _method resolution order: http://www.python.org/download/releases/2.3/mro/
Using SingleObjectMixin with View
---------------------------------
If we want to write a simple class-based view that responds only to
``POST``, we'll subclass :class:`~django.views.generic.base.View` and
write a ``post()`` method in the subclass. However if we want our
processing to work on a particular object, identified from the URL,
we'll want the functionality provided by
:class:`~django.views.generic.detail.SingleObjectMixin`.
We'll demonstrate this with the publisher modelling we used in the
:doc:`generic class-based views
introduction<generic-display>`.
.. code-block:: python
# views.py
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
class RecordInterest(View, SingleObjectMixin):
"""Records the current user's interest in an author."""
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseForbidden()
# Look up the author we're interested in.
self.object = self.get_object()
# Actually record interest somehow here!
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
In practice you'd probably want to record the interest in a key-value
store rather than in a relational database, so we've left that bit
out. The only bit of the view that needs to worry about using
:class:`SingleObjectMixin` is where we want to look up the author
we're interested in, which it just does with a simple call to
``self.get_object()``. Everything else is taken care of for us by the
mixin.
We can hook this into our URLs easily enough:
.. code-block:: python
# urls.py
from books.views import RecordInterest
urlpatterns = patterns('',
#...
url(r'^author/(?P<pk>\d+)/interest/$', RecordInterest.as_view(), name='author-interest'),
)
Note the ``pk`` named group, which
:meth:`~django.views.generic.detail.SingleObjectMixin.get_object` uses
to look up the :class:`Author` instance. You could also use a slug, or
any of the other features of :class:`SingleObjectMixin`.
Using SingleObjectMixin with ListView
-------------------------------------
:class:`~django.views.generic.list.ListView` provides built-in
pagination, but you might want to paginate a list of objects that are
all linked (by a foreign key) to another object. In our publishing
example, you might want to paginate through all the books by a
particular publisher.
One way to do this is to combine :class:`ListView` with
:class:`SingleObjectMixin`, so that the queryset for the paginated
list of books can hang off the publisher found as the single
object. In order to do this, we need to have two different querysets:
**Publisher queryset for use in get_object**
We'll set that up directly when we call :meth:`get_object()`.
**Book queryset for use by ListView**
We'll figure that out ourselves in :meth:`get_queryset()` so we
can take into account the Publisher we're looking at.
.. highlightlang:: python
.. note::
We have to think carefully about :meth:`get_context_data()`.
Since both :class:`SingleObjectMixin` and :class:`ListView` will
put things in the context data under the value of
:attr:`context_object_name` if it's set, we'll instead explictly
ensure the Publisher is in the context data. :class:`ListView`
will add in the suitable ``page_obj`` and ``paginator`` for us
providing we remember to call ``super()``.
Now we can write a new :class:`PublisherDetail`::
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
class PublisherDetail(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
def get_context_data(self, **kwargs):
kwargs['publisher'] = self.object
return super(PublisherDetail, self).get_context_data(**kwargs)
def get_queryset(self):
self.object = self.get_object(Publisher.objects.all())
return self.object.book_set.all()
Notice how we set ``self.object`` within :meth:`get_queryset` so we
can use it again later in :meth:`get_context_data`. If you don't set
:attr:`template_name`, the template will default to the normal
:class:`ListView` choice, which in this case would be
``"books/book_list.html"`` because it's a list of books;
:class:`ListView` knows nothing about :class:`SingleObjectMixin`, so
it doesn't have any clue this view is anything to do with a Publisher.
.. highlightlang:: html+django
The ``paginate_by`` is deliberately small in the example so you don't
have to create lots of books to see the pagination working! Here's the
template you'd want to use::
{% extends "base.html" %}
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
Avoid anything more complex
===========================
Generally you can use
:class:`~django.views.generic.base.TemplateResponseMixin` and
:class:`~django.views.generic.detail.SingleObjectMixin` when you need
their functionality. As shown above, with a bit of care you can even
combine :class:`SingleObjectMixin` with
:class:`~django.views.generic.list.ListView`. However things get
increasingly complex as you try to do so, and a good rule of thumb is:
.. hint::
Each of your views should use only mixins or views from one of the
groups of generic class-based views: :doc:`detail,
list<generic-display>`, :doc:`editing<generic-editing>` and
date. For example it's fine to combine
:class:`TemplateView` (built in view) with
:class:`MultipleObjectMixin` (generic list), but you're likely to
have problems combining :class:`SingleObjectMixin` (generic
detail) with :class:`MultipleObjectMixin` (generic list).
To show what happens when you try to get more sophisticated, we show
an example that sacrifices readability and maintainability when there
is a simpler solution. First, let's look at a naive attempt to combine
:class:`~django.views.generic.detail.DetailView` with
:class:`~django.views.generic.edit.FormMixin` to enable use to
``POST`` a Django :class:`Form` to the same URL as we're displaying an
object using :class:`DetailView`.
Using FormMixin with DetailView
-------------------------------
Think back to our earlier example of using :class:`View` and
:class:`SingleObjectMixin` together. We were recording a user's
interest in a particular author; say now that we want to let them
leave a message saying why they like them. Again, let's assume we're
not going to store this in a relational database but instead in
something more esoteric that we won't worry about here.
At this point it's natural to reach for a :class:`Form` to encapsulate
the information sent from the user's browser to Django. Say also that
we're heavily invested in `REST`_, so we want to use the same URL for
displaying the author as for capturing the message from the
user. Let's rewrite our :class:`AuthorDetailView` to do that.
.. _REST: http://en.wikipedia.org/wiki/Representational_state_transfer
We'll keep the ``GET`` handling from :class:`DetailView`, although
we'll have to add a :class:`Form` into the context data so we can
render it in the template. We'll also want to pull in form processing
from :class:`~django.views.generic.edit.FormMixin`, and write a bit of
code so that on ``POST`` the form gets called appropriately.
.. note::
We use :class:`FormMixin` and implement :meth:`post()` ourselves
rather than try to mix :class:`DetailView` with :class:`FormView`
(which provides a suitable :meth:`post()` already) because both of
the views implement :meth:`get()`, and things would get much more
confusing.
Our new :class:`AuthorDetail` looks like this:
.. code-block:: python
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.
from django import forms
from django.http import HttpResponseForbidden
from django.core.urlresolvers import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetail(DetailView, FormMixin):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
return reverse(
'author-detail',
kwargs = {'pk': self.object.pk},
)
def get_context_data(self, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
context = {
'form': form
}
context.update(kwargs)
return super(AuthorDetail, self).get_context_data(**context)
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
if not self.request.user.is_authenticated():
return HttpResponseForbidden()
self.object = self.get_object()
# record the interest using the message in form.cleaned_data
return super(AuthorDetail, self).form_valid(form)
:meth:`get_success_url()` is just providing somewhere to redirect to,
which gets used in the default implementation of
:meth:`form_valid()`. We have to provide our own :meth:`post()` as
noted earlier, and override :meth:`get_context_data()` to make the
:class:`Form` available in the context data.
A better solution
-----------------
It should be obvious that the number of subtle interactions between
:class:`FormMixin` and :class:`DetailView` is already testing our
ability to manage things. It's unlikely you'd want to write this kind
of class yourself.
In this case, it would be fairly easy to just write the :meth:`post()`
method yourself, keeping :class:`DetailView` as the only generic
functionality, although writing :class:`Form` handling code involves a
lot of duplication.
Alternatively, it would still be easier than the above approach to
have a separate view for processing the form, which could use
:class:`~django.views.generic.edit.FormView` distinct from
:class:`DetailView` without concerns.
An alternative better solution
------------------------------
What we're really trying to do here is to use two different class
based views from the same URL. So why not do just that? We have a very
clear division here: ``GET`` requests should get the
:class:`DetailView` (with the :class:`Form` added to the context
data), and ``POST`` requests should get the :class:`FormView`. Let's
set up those views first.
The :class:`AuthorDisplay` view is almost the same as :ref:`when we
first introduced AuthorDetail<generic-views-extra-work>`; we have to
write our own :meth:`get_context_data()` to make the
:class:`AuthorInterestForm` available to the template. We'll skip the
:meth:`get_object()` override from before for clarity.
.. code-block:: python
from django.views.generic import DetailView
from django import forms
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDisplay(DetailView):
queryset = Author.objects.all()
def get_context_data(self, **kwargs):
context = {
'form': AuthorInterestForm(),
}
context.update(kwargs)
return super(AuthorDisplay, self).get_context_data(**context)
Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
have to bring in :class:`SingleObjectMixin` so we can find the author
we're talking about, and we have to remember to set
:attr:`template_name` to ensure that form errors will render the same
template as :class:`AuthorDisplay` is using on ``GET``.
.. code-block:: python
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
class AuthorInterest(FormView, SingleObjectMixin):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
model = Author
def get_context_data(self, **kwargs):
context = {
'object': self.get_object(),
}
return super(AuthorInterest, self).get_context_data(**context)
def get_success_url(self):
return reverse(
'author-detail',
kwargs = {'pk': self.object.pk},
)
def form_valid(self, form):
if not self.request.user.is_authenticated():
return HttpResponseForbidden()
self.object = self.get_object()
# record the interest using the message in form.cleaned_data
return super(AuthorInterest, self).form_valid(form)
Finally we bring this together in a new :class:`AuthorDetail` view. We
already know that calling :meth:`as_view()` on a class-based view
gives us something that behaves exactly like a function based view, so
we can do that at the point we choose between the two subviews.
You can of course pass through keyword arguments to :meth:`as_view()`
in the same way you would in your URLconf, such as if you wanted the
:class:`AuthorInterest` behaviour to also appear at another URL but
using a different template.
.. code-block:: python
from django.views.generic import View
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = AuthorDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = AuthorInterest.as_view()
return view(request, *args, **kwargs)
This approach can also be used with any other generic class-based
views or your own class-based views inheriting directly from
:class:`View` or :class:`TemplateView`, as it keeps the different
views as separate as possible.