mirror of
https://github.com/django/django.git
synced 2025-11-02 04:48:33 +00:00
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:
parent
1a10a06b9f
commit
c4c7fbcc0d
36 changed files with 3112 additions and 2016 deletions
432
docs/topics/class-based-views/generic-display.txt
Normal file
432
docs/topics/class-based-views/generic-display.txt
Normal 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`
|
||||
205
docs/topics/class-based-views/generic-editing.txt
Normal file
205
docs/topics/class-based-views/generic-editing.txt
Normal 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()`.
|
||||
233
docs/topics/class-based-views/index.txt
Normal file
233
docs/topics/class-based-views/index.txt
Normal 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.
|
||||
605
docs/topics/class-based-views/mixins.txt
Normal file
605
docs/topics/class-based-views/mixins.txt
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue