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

@ -51,6 +51,7 @@ answer newbie questions, and generally made Django that much better:
Antoni Aloy Antoni Aloy
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com> Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
AgarFu <heaven@croasanaso.sytes.net> AgarFu <heaven@croasanaso.sytes.net>
James Aylett
Dagur Páll Ammendrup <dagurp@gmail.com> Dagur Páll Ammendrup <dagurp@gmail.com>
Collin Anderson <cmawebsite@gmail.com> Collin Anderson <cmawebsite@gmail.com>
Jeff Anderson <jefferya@programmerq.net> Jeff Anderson <jefferya@programmerq.net>
@ -85,6 +86,7 @@ answer newbie questions, and generally made Django that much better:
Esdras Beleza <linux@esdrasbeleza.com> Esdras Beleza <linux@esdrasbeleza.com>
Chris Bennett <chrisrbennett@yahoo.com> Chris Bennett <chrisrbennett@yahoo.com>
James Bennett James Bennett
Danilo Bargen
Shai Berger <shai@platonix.com> Shai Berger <shai@platonix.com>
Julian Bez Julian Bez
Arvis Bickovskis <viestards.lists@gmail.com> Arvis Bickovskis <viestards.lists@gmail.com>
@ -222,6 +224,7 @@ answer newbie questions, and generally made Django that much better:
pradeep.gowda@gmail.com pradeep.gowda@gmail.com
Collin Grady <collin@collingrady.com> Collin Grady <collin@collingrady.com>
Gabriel Grant <g@briel.ca> Gabriel Grant <g@briel.ca>
Daniel Greenfeld
Simon Greenhill <dev@simon.net.nz> Simon Greenhill <dev@simon.net.nz>
Owen Griffiths Owen Griffiths
Espen Grindhaug <http://grindhaug.org/> Espen Grindhaug <http://grindhaug.org/>
@ -507,6 +510,7 @@ answer newbie questions, and generally made Django that much better:
Aaron Swartz <http://www.aaronsw.com/> Aaron Swartz <http://www.aaronsw.com/>
Ville Säävuori <http://www.unessa.net/> Ville Säävuori <http://www.unessa.net/>
Mart Sõmermaa <http://mrts.pri.ee/> Mart Sõmermaa <http://mrts.pri.ee/>
Marc Tamlyn
Christian Tanzer <tanzer@swing.co.at> Christian Tanzer <tanzer@swing.co.at>
Tyler Tarabula <tyler.tarabula@gmail.com> Tyler Tarabula <tyler.tarabula@gmail.com>
Tyson Tate <tyson@fallingbullets.com> Tyson Tate <tyson@fallingbullets.com>
@ -555,6 +559,7 @@ answer newbie questions, and generally made Django that much better:
Mike Wiacek <mjwiacek@google.com> Mike Wiacek <mjwiacek@google.com>
Frank Wierzbicki Frank Wierzbicki
charly.wilhelm@gmail.com charly.wilhelm@gmail.com
Simon Williams
Derek Willis <http://blog.thescoop.org/> Derek Willis <http://blog.thescoop.org/>
Rachel Willmer <http://www.willmer.com/kb/> Rachel Willmer <http://www.willmer.com/kb/>
Jakub Wilk <ubanus@users.sf.net> Jakub Wilk <ubanus@users.sf.net>

View File

@ -90,6 +90,9 @@ class View(object):
return http.HttpResponseNotAllowed(self._allowed_methods()) return http.HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs): def options(self, request, *args, **kwargs):
"""
Handles responding to requests for the OPTIONS HTTP verb.
"""
response = http.HttpResponse() response = http.HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods()) response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = 0 response['Content-Length'] = 0
@ -108,7 +111,11 @@ class TemplateResponseMixin(object):
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
""" """
Returns a response with a template rendered with the given context. Returns a response, using the `response_class` for this
view, with a template rendered with the given context.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
""" """
return self.response_class( return self.response_class(
request = self.request, request = self.request,

View File

@ -14,6 +14,9 @@ from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResp
from django.views.generic.list import MultipleObjectMixin, MultipleObjectTemplateResponseMixin from django.views.generic.list import MultipleObjectMixin, MultipleObjectTemplateResponseMixin
class YearMixin(object): class YearMixin(object):
"""
Mixin for views manipulating year-based data.
"""
year_format = '%Y' year_format = '%Y'
year = None year = None
@ -67,6 +70,9 @@ class YearMixin(object):
class MonthMixin(object): class MonthMixin(object):
"""
Mixin for views manipulating month-based data.
"""
month_format = '%b' month_format = '%b'
month = None month = None
@ -123,6 +129,9 @@ class MonthMixin(object):
class DayMixin(object): class DayMixin(object):
"""
Mixin for views manipulating day-based data.
"""
day_format = '%d' day_format = '%d'
day = None day = None
@ -176,6 +185,9 @@ class DayMixin(object):
class WeekMixin(object): class WeekMixin(object):
"""
Mixin for views manipulating week-based data.
"""
week_format = '%U' week_format = '%U'
week = None week = None
@ -312,7 +324,7 @@ class DateMixin(object):
class BaseDateListView(MultipleObjectMixin, DateMixin, View): class BaseDateListView(MultipleObjectMixin, DateMixin, View):
""" """
Abstract base class for date-based views display a list of objects. Abstract base class for date-based views displaying a list of objects.
""" """
allow_empty = False allow_empty = False

View File

@ -48,6 +48,7 @@ class SingleObjectMixin(ContextMixin):
% self.__class__.__name__) % self.__class__.__name__)
try: try:
# Get the single item from the filtered queryset
obj = queryset.get() obj = queryset.get()
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") % raise Http404(_("No %(verbose_name)s found matching the query") %
@ -88,6 +89,9 @@ class SingleObjectMixin(ContextMixin):
return None return None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""
Insert the single object into the context dict.
"""
context = {} context = {}
context_object_name = self.get_context_object_name(self.object) context_object_name = self.get_context_object_name(self.object)
if context_object_name: if context_object_name:
@ -97,6 +101,9 @@ class SingleObjectMixin(ContextMixin):
class BaseDetailView(SingleObjectMixin, View): class BaseDetailView(SingleObjectMixin, View):
"""
A base view for displaying a single object
"""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
context = self.get_context_data(object=self.object) context = self.get_context_data(object=self.object)
@ -109,8 +116,13 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
def get_template_names(self): def get_template_names(self):
""" """
Return a list of template names to be used for the request. Must return Return a list of template names to be used for the request. May not be
a list. May not be called if render_to_response is overridden. called if render_to_response is overridden. Returns the following list:
* the value of ``template_name`` on the view (if provided)
* the contents of the ``template_name_field`` field on the
object instance that the view is operating upon (if available)
* ``<app_label>/<object_name><template_name_suffix>.html``
""" """
try: try:
names = super(SingleObjectTemplateResponseMixin, self).get_template_names() names = super(SingleObjectTemplateResponseMixin, self).get_template_names()

View File

@ -46,6 +46,9 @@ class FormMixin(ContextMixin):
return kwargs return kwargs
def get_success_url(self): def get_success_url(self):
"""
Returns the supplied success URL.
"""
if self.success_url: if self.success_url:
url = self.success_url url = self.success_url
else: else:
@ -54,9 +57,16 @@ class FormMixin(ContextMixin):
return url return url
def form_valid(self, form): def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL.
"""
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form): def form_invalid(self, form):
"""
If the form is invalid, re-render the context data with the
data-filled form and errors.
"""
return self.render_to_response(self.get_context_data(form=form)) return self.render_to_response(self.get_context_data(form=form))
@ -67,7 +77,7 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
def get_form_class(self): def get_form_class(self):
""" """
Returns the form class to use in this view Returns the form class to use in this view.
""" """
if self.form_class: if self.form_class:
return self.form_class return self.form_class
@ -94,6 +104,9 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
return kwargs return kwargs
def get_success_url(self): def get_success_url(self):
"""
Returns the supplied URL.
"""
if self.success_url: if self.success_url:
url = self.success_url % self.object.__dict__ url = self.success_url % self.object.__dict__
else: else:
@ -106,10 +119,17 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
return url return url
def form_valid(self, form): def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
self.object = form.save() self.object = form.save()
return super(ModelFormMixin, self).form_valid(form) return super(ModelFormMixin, self).form_valid(form)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""
If an object has been supplied, inject it into the context with the
supplied context_object_name name.
"""
context = {} context = {}
if self.object: if self.object:
context['object'] = self.object context['object'] = self.object
@ -122,14 +142,21 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
class ProcessFormView(View): class ProcessFormView(View):
""" """
A mixin that processes a form on POST. A mixin that renders a form on GET and processes it on POST.
""" """
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
form_class = self.get_form_class() form_class = self.get_form_class()
form = self.get_form(form_class) form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form)) return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class() form_class = self.get_form_class()
form = self.get_form(form_class) form = self.get_form(form_class)
if form.is_valid(): if form.is_valid():
@ -172,7 +199,7 @@ class BaseCreateView(ModelFormMixin, ProcessFormView):
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
""" """
View for creating an new object instance, View for creating a new object instance,
with a response rendered by template. with a response rendered by template.
""" """
template_name_suffix = '_form' template_name_suffix = '_form'
@ -196,7 +223,7 @@ class BaseUpdateView(ModelFormMixin, ProcessFormView):
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView): class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
""" """
View for updating an object, View for updating an object,
with a response rendered by template.. with a response rendered by template.
""" """
template_name_suffix = '_form' template_name_suffix = '_form'
@ -208,6 +235,10 @@ class DeletionMixin(object):
success_url = None success_url = None
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
"""
Calls the delete() method on the fetched object and then
redirects to the success URL.
"""
self.object = self.get_object() self.object = self.get_object()
self.object.delete() self.object.delete()
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())

View File

@ -8,6 +8,9 @@ from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
class MultipleObjectMixin(ContextMixin): class MultipleObjectMixin(ContextMixin):
"""
A mixin for views manipulating multiple objects.
"""
allow_empty = True allow_empty = True
queryset = None queryset = None
model = None model = None
@ -111,6 +114,9 @@ class MultipleObjectMixin(ContextMixin):
class BaseListView(MultipleObjectMixin, View): class BaseListView(MultipleObjectMixin, View):
"""
A base view for displaying a list of objects.
"""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset() self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty() allow_empty = self.get_allow_empty()
@ -132,6 +138,9 @@ class BaseListView(MultipleObjectMixin, View):
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin): class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
"""
Mixin for responding with a template and list of objects.
"""
template_name_suffix = '_list' template_name_suffix = '_list'
def get_template_names(self): def get_template_names(self):

View File

@ -16,7 +16,7 @@ Glossary
A higher-order :term:`view` function that provides an abstract/generic A higher-order :term:`view` function that provides an abstract/generic
implementation of a common idiom or pattern found in view development. implementation of a common idiom or pattern found in view development.
See :doc:`/ref/class-based-views`. See :doc:`/topics/class-based-views/index`.
model model
Models store your application's data. Models store your application's data.

View File

@ -175,7 +175,7 @@ using :class:`~django.template.RequestContext` when rendering the template.
As a brief refresher, context processors add variables into the contexts of As a brief refresher, context processors add variables into the contexts of
every template. However, context processors require that you use every template. However, context processors require that you use
:class:`~django.template.RequestContext` when rendering templates. This happens :class:`~django.template.RequestContext` when rendering templates. This happens
automatically if you're using a :doc:`generic view </ref/class-based-views>`, automatically if you're using a :doc:`generic view </ref/class-based-views/index>`,
but in views written by hand you'll need to explicitly use ``RequestContext`` but in views written by hand you'll need to explicitly use ``RequestContext``
To see how that works, and to read more details, check out To see how that works, and to read more details, check out
:ref:`subclassing-context-requestcontext`. :ref:`subclassing-context-requestcontext`.

View File

@ -105,9 +105,10 @@ The view layer
:doc:`Managing files <topics/files>` | :doc:`Managing files <topics/files>` |
:doc:`Custom storage <howto/custom-file-storage>` :doc:`Custom storage <howto/custom-file-storage>`
* **Generic views:** * **Class-based views:**
:doc:`Overview<topics/class-based-views>` | :doc:`Overview<topics/class-based-views/index>` |
:doc:`Built-in generic views<ref/class-based-views>` :doc:`Built-in class-based views<ref/class-based-views/index>` |
:doc:`Built-in view mixins<ref/class-based-views/mixins>`
* **Advanced:** * **Advanced:**
:doc:`Generating CSV <howto/outputting-csv>` | :doc:`Generating CSV <howto/outputting-csv>` |

View File

@ -134,7 +134,7 @@ these changes.
* The function-based generic view modules will be removed in favor of their * The function-based generic view modules will be removed in favor of their
class-based equivalents, outlined :doc:`here class-based equivalents, outlined :doc:`here
</topics/class-based-views>`: </topics/class-based-views/index>`:
* The :class:`~django.core.servers.basehttp.AdminMediaHandler` will be * The :class:`~django.core.servers.basehttp.AdminMediaHandler` will be
removed. In its place use removed. In its place use
@ -221,7 +221,7 @@ these changes.
the 1.4 release. They will be removed. the 1.4 release. They will be removed.
* The :doc:`form wizard </ref/contrib/formtools/form-wizard>` has been * The :doc:`form wizard </ref/contrib/formtools/form-wizard>` has been
refactored to use class based views with pluggable backends in 1.4. refactored to use class-based views with pluggable backends in 1.4.
The previous implementation will be removed. The previous implementation will be removed.
* Legacy ways of calling * Legacy ways of calling

View File

@ -320,7 +320,7 @@ function anymore -- generic views can be (and are) used multiple times
Run the server, and use your new polling app based on generic views. Run the server, and use your new polling app based on generic views.
For full details on generic views, see the :doc:`generic views documentation For full details on generic views, see the :doc:`generic views documentation
</topics/class-based-views>`. </topics/class-based-views/index>`.
Coming soon Coming soon
=========== ===========

View File

@ -54,7 +54,7 @@ of 1.0. This includes these APIs:
- :doc:`HTTP request/response handling </topics/http/index>`, including file - :doc:`HTTP request/response handling </topics/http/index>`, including file
uploads, middleware, sessions, URL resolution, view, and shortcut APIs. uploads, middleware, sessions, URL resolution, view, and shortcut APIs.
- :doc:`Generic views </topics/class-based-views>`. - :doc:`Generic views </topics/class-based-views/index>`.
- :doc:`Internationalization </topics/i18n/index>`. - :doc:`Internationalization </topics/i18n/index>`.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,224 @@
==========
Base views
==========
The following three classes provide much of the functionality needed to create
Django views. You may think of them as *parent* views, which can be used by
themselves or inherited from. They may not provide all the capabilities
required for projects, in which case there are Mixins and Generic class-based
views.
.. class:: django.views.generic.base.View
The master class-based base view. All other class-based views inherit from
this base class.
**Method Flowchart**
1. :meth:`dispatch()`
2. :meth:`http_method_not_allowed()`
**Example views.py**::
from django.http import HttpResponse
from django.views.generic import View
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!')
**Example urls.py**::
from django.conf.urls import patterns, url
from myapp.views import MyView
urlpatterns = patterns('',
url(r'^mine/$', MyView.as_view(), name='my-view'),
)
**Methods**
.. method:: dispatch(request, *args, **kwargs)
The ``view`` part of the view -- the method that accepts a ``request``
argument plus arguments, and returns a HTTP response.
The default implementation will inspect the HTTP method and attempt to
delegate to a method that matches the HTTP method; a ``GET`` will be
delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`,
and so on.
The default implementation also sets ``request``, ``args`` and
``kwargs`` as instance variables, so any method on the view can know
the full details of the request that was made to invoke the view.
.. method:: http_method_not_allowed(request, *args, **kwargs)
If the view was called with a HTTP method it doesn't support, this
method is called instead.
The default implementation returns ``HttpResponseNotAllowed`` with list
of allowed methods in plain text.
.. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.
.. class:: django.views.generic.base.TemplateView
Renders a given template, passing it a ``{{ params }}`` template variable,
which is a dictionary of the parameters captured in the URL.
**Ancestors (MRO)**
* :class:`django.views.generic.base.TemplateView`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.base.View`
**Method Flowchart**
1. :meth:`dispatch()`
2. :meth:`http_method_not_allowed()`
3. :meth:`get_context_data()`
**Example views.py**::
from django.views.generic.base import TemplateView
from articles.models import Article
class HomePageView(TemplateView):
template_name = "home.html"
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['latest_articles'] = Article.objects.all()[:5]
return context
**Example urls.py**::
from django.conf.urls import patterns, url
from myapp.views import HomePageView
urlpatterns = patterns('',
url(r'^$', HomePageView.as_view(), name='home'),
)
**Methods and Attributes**
.. attribute:: template_name
The full name of a template to use.
.. method:: get_context_data(**kwargs)
Return a context data dictionary consisting of the contents of
``kwargs`` stored in the context variable ``params``.
**Context**
* ``params``: The dictionary of keyword arguments captured from the URL
pattern that served the view.
.. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.
.. class:: django.views.generic.base.RedirectView
Redirects to a given URL.
The given URL may contain dictionary-style string formatting, which will be
interpolated against the parameters captured in the URL. Because keyword
interpolation is *always* done (even if no arguments are passed in), any
``"%"`` characters in the URL must be written as ``"%%"`` so that Python
will convert them to a single percent sign on output.
If the given URL is ``None``, Django will return an ``HttpResponseGone``
(410).
**Ancestors (MRO)**
* :class:`django.views.generic.base.View`
**Method Flowchart**
1. :meth:`dispatch()`
2. :meth:`http_method_not_allowed()`
3. :meth:`get_redirect_url()`
**Example views.py**::
from django.shortcuts import get_object_or_404
from django.views.generic.base import RedirectView
from articles.models import Article
class ArticleCounterRedirectView(RedirectView):
permanent = False
query_string = True
def get_redirect_url(self, pk):
article = get_object_or_404(Article, pk=pk)
article.update_counter()
return reverse('product_detail', args=(pk,))
**Example urls.py**::
from django.conf.urls import patterns, url
from django.views.generic.base import RedirectView
from article.views import ArticleCounterRedirectView
urlpatterns = patterns('',
url(r'r^(?P<pk>\d+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'),
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
)
**Methods and Attributes**
.. attribute:: url
The URL to redirect to, as a string. Or ``None`` to raise a 410 (Gone)
HTTP error.
.. attribute:: permanent
Whether the redirect should be permanent. The only difference here is
the HTTP status code returned. If ``True``, then the redirect will use
status code 301. If ``False``, then the redirect will use status code
302. By default, ``permanent`` is ``True``.
.. attribute:: query_string
Whether to pass along the GET query string to the new location. If
``True``, then the query string is appended to the URL. If ``False``,
then the query string is discarded. By default, ``query_string`` is
``False``.
.. method:: get_redirect_url(**kwargs)
Constructs the target URL for redirection.
The default implementation uses :attr:`~RedirectView.url` as a starting
string, performs expansion of ``%`` parameters in that string, as well
as the appending of query string if requested by
:attr:`~RedirectView.query_string`. Subclasses may implement any
behavior they wish, as long as the method returns a redirect-ready URL
string.
.. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.

View File

@ -0,0 +1,273 @@
==================
Generic date views
==================
Date-based generic views (in the module :mod:`django.views.generic.dates`)
are views for displaying drilldown pages for date-based data.
.. class:: django.views.generic.dates.ArchiveIndexView
A top-level index page showing the "latest" objects, by date. Objects with
a date in the *future* are not included unless you set ``allow_future`` to
``True``.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.ArchiveIndexView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseArchiveIndexView`
* :class:`django.views.generic.dates.BaseDateListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
**Notes**
* Uses a default ``context_object_name`` of ``latest``.
* Uses a default ``template_name_suffix`` of ``_archive``.
.. class:: django.views.generic.dates.YearArchiveView
A yearly archive page showing all available months in a given year. Objects
with a date in the *future* are not displayed unless you set
``allow_future`` to ``True``.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.YearArchiveView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseYearArchiveView`
* :class:`django.views.generic.dates.YearMixin`
* :class:`django.views.generic.dates.BaseDateListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
.. attribute:: make_object_list
A boolean specifying whether to retrieve the full list of objects for
this year and pass those to the template. If ``True``, the list of
objects will be made available to the context. By default, this is
``False``.
.. method:: get_make_object_list()
Determine if an object list will be returned as part of the context. If
``False``, the ``None`` queryset will be used as the object list.
**Context**
In addition to the context provided by
:class:`django.views.generic.list.MultipleObjectMixin` (via
:class:`django.views.generic.dates.BaseDateListView`), the template's
context will be:
* ``date_list``: A
:meth:`DateQuerySet<django.db.models.query.QuerySet.dates>` object object
containing all months that have objects available according to
``queryset``, represented as
:class:`datetime.datetime<python:datetime.datetime>` objects, in
ascending order.
* ``year``: A :class:`datetime.date<python:datetime.date>` object
representing the given year.
* ``next_year``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next year. If the next year is in the
future, this will be ``None``.
* ``previous_year``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous year. Unlike ``next_year``,
this will never be ``None``.
**Notes**
* Uses a default ``template_name_suffix`` of ``_archive_year``.
.. class:: django.views.generic.dates.MonthArchiveView
A monthly archive page showing all objects in a given month. Objects with a
date in the *future* are not displayed unless you set ``allow_future`` to
``True``.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.MonthArchiveView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseMonthArchiveView`
* :class:`django.views.generic.dates.YearMixin`
* :class:`django.views.generic.dates.MonthMixin`
* :class:`django.views.generic.dates.BaseDateListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
**Context**
In addition to the context provided by
:class:`~django.views.generic.list.MultipleObjectMixin` (via
:class:`~django.views.generic.dates.BaseDateListView`), the template's
context will be:
* ``date_list``: A
:meth:`DateQuerySet<django.db.models.query.QuerySet.dates>` object
containing all days that have objects available in the given month,
according to ``queryset``, represented as
:class:`datetime.datetime<python:datetime.datetime>` objects, in
ascending order.
* ``month``: A :class:`datetime.date<python:datetime.date>` object
representing the given month.
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next month. If the next month is in the
future, this will be ``None``.
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous month. Unlike ``next_month``,
this will never be ``None``.
**Notes**
* Uses a default ``template_name_suffix`` of ``_archive_month``.
.. class:: django.views.generic.dates.WeekArchiveView
A weekly archive page showing all objects in a given week. Objects with a
date in the *future* are not displayed unless you set ``allow_future`` to
``True``.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.WeekArchiveView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseWeekArchiveView`
* :class:`django.views.generic.dates.YearMixin`
* :class:`django.views.generic.dates.WeekMixin`
* :class:`django.views.generic.dates.BaseDateListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
**Context**
In addition to the context provided by
:class:`~django.views.generic.list.MultipleObjectMixin` (via
:class:`~django.views.generic.dates.BaseDateListView`), the template's
context will be:
* ``week``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the given week.
* ``next_week``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next week. If the next week is in the
future, this will be ``None``.
* ``previous_week``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous week. Unlike ``next_week``,
this will never be ``None``.
**Notes**
* Uses a default ``template_name_suffix`` of ``_archive_week``.
.. class:: django.views.generic.dates.DayArchiveView
A day archive page showing all objects in a given day. Days in the future
throw a 404 error, regardless of whether any objects exist for future days,
unless you set ``allow_future`` to ``True``.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.DayArchiveView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseDayArchiveView`
* :class:`django.views.generic.dates.YearMixin`
* :class:`django.views.generic.dates.MonthMixin`
* :class:`django.views.generic.dates.DayMixin`
* :class:`django.views.generic.dates.BaseDateListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
**Context**
In addition to the context provided by
:class:`~django.views.generic.list.MultipleObjectMixin` (via
:class:`~django.views.generic.dates.BaseDateListView`), the template's
context will be:
* ``day``: A :class:`datetime.date<python:datetime.date>` object
representing the given day.
* ``next_day``: A :class:`datetime.date<python:datetime.date>` object
representing the next day. If the next day is in the future, this will be
``None``.
* ``previous_day``: A :class:`datetime.date<python:datetime.date>` object
representing the previous day. Unlike ``next_day``, this will never be
``None``.
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next month. If the next month is in the
future, this will be ``None``.
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous month. Unlike ``next_month``,
this will never be ``None``.
**Notes**
* Uses a default ``template_name_suffix`` of ``_archive_day``.
.. class:: django.views.generic.dates.TodayArchiveView
A day archive page showing all objects for *today*. This is exactly the
same as :class:`django.views.generic.dates.DayArchiveView`, except today's
date is used instead of the ``year``/``month``/``day`` arguments.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.TodayArchiveView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseTodayArchiveView`
* :class:`django.views.generic.dates.BaseDayArchiveView`
* :class:`django.views.generic.dates.YearMixin`
* :class:`django.views.generic.dates.MonthMixin`
* :class:`django.views.generic.dates.DayMixin`
* :class:`django.views.generic.dates.BaseDateListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
.. class:: django.views.generic.dates.DateDetailView
A page representing an individual object. If the object has a date value in
the future, the view will throw a 404 error by default, unless you set
``allow_future`` to ``True``.
**Ancestors (MRO)**
* :class:`django.views.generic.dates.DateDetailView`
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.dates.BaseDateDetailView`
* :class:`django.views.generic.dates.YearMixin`
* :class:`django.views.generic.dates.MonthMixin`
* :class:`django.views.generic.dates.DayMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.detail.BaseDetailView`
* :class:`django.views.generic.detail.SingleObjectMixin`
* :class:`django.views.generic.base.View`
.. note::
All of the generic views listed above have matching Base* views that only
differ in that the they do not include the
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.

View File

@ -0,0 +1,86 @@
=====================
Generic display views
=====================
The two following generic class-based views are designed to display data. On
many projects they are typically the most commonly used views.
.. class:: django.views.generic.detail.DetailView
While this view is executing, ``self.object`` will contain the object that
the view is operating upon.
**Ancestors (MRO)**
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.detail.BaseDetailView`
* :class:`django.views.generic.detail.SingleObjectMixin`
* :class:`django.views.generic.base.View`
**Method Flowchart**
1. :meth:`dispatch()`
2. :meth:`http_method_not_allowed()`
3. :meth:`get_template_names()`
4. :meth:`get_slug_field()`
5. :meth:`get_queryset()`
6. :meth:`get_object()`
7. :meth:`get_context_object_name()`
8. :meth:`get_context_data()`
9. :meth:`get()`
10. :meth:`render_to_response()`
**Example views.py**::
from django.views.generic.detail import DetailView
from django.utils import timezone
from articles.models import Article
class ArticleDetailView(DetailView):
model = Article
def get_context_data(self, **kwargs):
context = super(ArticleDetailView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context
**Example urls.py**::
from django.conf.urls import patterns, url
from article.views import ArticleDetailView
urlpatterns = patterns('',
url(r'^(?P<slug>[-_\w]+)/$', ArticleDetailView.as_view(), 'article-detail'),
)
.. class:: django.views.generic.list.ListView
A page representing a list of objects.
While this view is executing, ``self.object_list`` will contain the list of
objects (usually, but not necessarily a queryset) that the view is
operating upon.
**Mixins**
* :class:`django.views.generic.list.ListView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.list.BaseListView`
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.base.View`
**Method Flowchart**
1. :meth:`dispatch():`
2. :meth:`http_method_not_allowed():`
3. :meth:`get_template_names():`
4. :meth:`get_queryset():`
5. :meth:`get_objects():`
6. :meth:`get_context_data():`
7. :meth:`get():`
8. :meth:`render_to_response():`

View File

@ -0,0 +1,78 @@
=====================
Generic editing views
=====================
The views described here provide a foundation for editing content.
.. class:: django.views.generic.edit.FormView
A view that displays a form. On error, redisplays the form with validation
errors; on success, redirects to a new URL.
**Ancestors (MRO)**
* :class:`django.views.generic.edit.FormView`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.edit.BaseFormView`
* :class:`django.views.generic.edit.FormMixin`
* :class:`django.views.generic.edit.ProcessFormView`
* :class:`django.views.generic.base.View`
.. class:: django.views.generic.edit.CreateView
A view that displays a form for creating an object, redisplaying the form
with validation errors (if there are any) and saving the object.
**Ancestors (MRO)**
* :class:`django.views.generic.edit.CreateView`
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.edit.BaseCreateView`
* :class:`django.views.generic.edit.ModelFormMixin`
* :class:`django.views.generic.edit.FormMixin`
* :class:`django.views.generic.detail.SingleObjectMixin`
* :class:`django.views.generic.edit.ProcessFormView`
* :class:`django.views.generic.base.View`
.. class:: django.views.generic.edit.UpdateView
A view that displays a form for editing an existing object, redisplaying
the form with validation errors (if there are any) and saving changes to
the object. This uses a form automatically generated from the object's
model class (unless a form class is manually specified).
**Ancestors (MRO)**
* :class:`django.views.generic.edit.UpdateView`
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.edit.BaseUpdateView`
* :class:`django.views.generic.edit.ModelFormMixin`
* :class:`django.views.generic.edit.FormMixin`
* :class:`django.views.generic.detail.SingleObjectMixin`
* :class:`django.views.generic.edit.ProcessFormView`
* :class:`django.views.generic.base.View`
.. class:: django.views.generic.edit.DeleteView
A view that displays a confirmation page and deletes an existing object.
The given object will only be deleted if the request method is ``POST``. If
this view is fetched via ``GET``, it will display a confirmation page that
should contain a form that POSTs to the same URL.
**Ancestors (MRO)**
* :class:`django.views.generic.edit.DeleteView`
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.edit.BaseDeleteView`
* :class:`django.views.generic.edit.DeletionMixin`
* :class:`django.views.generic.detail.BaseDetailView`
* :class:`django.views.generic.detail.SingleObjectMixin`
* :class:`django.views.generic.base.View`
**Notes**
* The delete confirmation page displayed to a GET request uses a
``template_name_suffix`` of ``'_confirm_delete'``.

View File

@ -0,0 +1,59 @@
=================
Class-based views
=================
Class-based views API reference. For introductory material, see
:doc:`/topics/class-based-views/index`.
.. toctree::
:maxdepth: 1
base
generic-display
generic-editing
generic-date-based
mixins
Specification
-------------
Each request served by a class-based view has an independent state; therefore,
it is safe to store state variables on the instance (i.e., ``self.foo = 3`` is
a thread-safe operation).
A class-based view is deployed into a URL pattern using the
:meth:`~View.as_view()` classmethod::
urlpatterns = patterns('',
(r'^view/$', MyView.as_view(size=42)),
)
.. admonition:: Thread safety with view arguments
Arguments passed to a view are shared between every instance of a view.
This means that you shoudn't use a list, dictionary, or any other
variable object as an argument to a view. If you did, the actions of
one user visiting your view could have an effect on subsequent users
visiting the same view.
Any argument passed into :meth:`~View.as_view()` will be assigned onto the
instance that is used to service a request. Using the previous example,
this means that every request on ``MyView`` is able to use ``self.size``.
Base vs Generic views
---------------------
Base class-based views can be thought of as *parent* views, which can be
used by themselves or inherited from. They may not provide all the
capabilities required for projects, in which case there are Mixins which
extend what base views can do.
Djangos generic views are built off of those base views, and were developed
as a shortcut for common usage patterns such as displaying the details of an
object. 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 repeat yourself.
Most generic views require the ``queryset`` key, which is a ``QuerySet``
instance; see :doc:`/topics/db/queries` for more information about ``QuerySet``
objects.

View File

@ -0,0 +1,256 @@
=================
Date-based mixins
=================
.. class:: django.views.generic.dates.YearMixin
A mixin that can be used to retrieve and provide parsing information for a
year component of a date.
**Methods and Attributes**
.. attribute:: year_format
The :func:`~time.strftime` format to use when parsing the year.
By default, this is ``'%Y'``.
.. attribute:: year
**Optional** The value for the year (as a string). By default, set to
``None``, which means the year will be determined using other means.
.. method:: get_year_format()
Returns the :func:`~time.strftime` format to use when parsing the year. Returns
:attr:`YearMixin.year_format` by default.
.. method:: get_year()
Returns the year for which this view will display data. Tries the
following sources, in order:
* The value of the :attr:`YearMixin.year` attribute.
* The value of the `year` argument captured in the URL pattern
* The value of the `year` GET query argument.
Raises a 404 if no valid year specification can be found.
.. class:: django.views.generic.dates.MonthMixin
A mixin that can be used to retrieve and provide parsing information for a
month component of a date.
**Methods and Attributes**
.. attribute:: month_format
The :func:`~time.strftime` format to use when parsing the month. By default, this is
``'%b'``.
.. attribute:: month
**Optional** The value for the month (as a string). By default, set to
``None``, which means the month will be determined using other means.
.. method:: get_month_format()
Returns the :func:`~time.strftime` format to use when parsing the month. Returns
:attr:`MonthMixin.month_format` by default.
.. method:: get_month()
Returns the month for which this view will display data. Tries the
following sources, in order:
* The value of the :attr:`MonthMixin.month` attribute.
* The value of the `month` argument captured in the URL pattern
* The value of the `month` GET query argument.
Raises a 404 if no valid month specification can be found.
.. method:: get_next_month(date)
Returns a date object containing the first day of the month after the
date provided. Returns ``None`` if mixed with a view that sets
``allow_future = False``, and the next month is in the future. If
``allow_empty = False``, returns the next month that contains data.
.. method:: get_prev_month(date)
Returns a date object containing the first day of the month before the
date provided. If ``allow_empty = False``, returns the previous month
that contained data.
.. class:: django.views.generic.dates.DayMixin
A mixin that can be used to retrieve and provide parsing information for a
day component of a date.
**Methods and Attributes**
.. attribute:: day_format
The :func:`~time.strftime` format to use when parsing the day. By default, this is
``'%d'``.
.. attribute:: day
**Optional** The value for the day (as a string). By default, set to
``None``, which means the day will be determined using other means.
.. method:: get_day_format()
Returns the :func:`~time.strftime` format to use when parsing the day. Returns
:attr:`DayMixin.day_format` by default.
.. method:: get_day()
Returns the day for which this view will display data. Tries the
following sources, in order:
* The value of the :attr:`DayMixin.day` attribute.
* The value of the `day` argument captured in the URL pattern
* The value of the `day` GET query argument.
Raises a 404 if no valid day specification can be found.
.. method:: get_next_day(date)
Returns a date object containing the next day after the date provided.
Returns ``None`` if mixed with a view that sets ``allow_future = False``,
and the next day is in the future. If ``allow_empty = False``, returns
the next day that contains data.
.. method:: get_prev_day(date)
Returns a date object containing the previous day. If
``allow_empty = False``, returns the previous day that contained data.
.. class:: django.views.generic.dates.WeekMixin
A mixin that can be used to retrieve and provide parsing information for a
week component of a date.
**Methods and Attributes**
.. attribute:: week_format
The :func:`~time.strftime` format to use when parsing the week. By default, this is
``'%U'``.
.. attribute:: week
**Optional** The value for the week (as a string). By default, set to
``None``, which means the week will be determined using other means.
.. method:: get_week_format()
Returns the :func:`~time.strftime` format to use when parsing the week. Returns
:attr:`WeekMixin.week_format` by default.
.. method:: get_week()
Returns the week for which this view will display data. Tries the
following sources, in order:
* The value of the :attr:`WeekMixin.week` attribute.
* The value of the `week` argument captured in the URL pattern
* The value of the `week` GET query argument.
Raises a 404 if no valid week specification can be found.
.. class:: django.views.generic.dates.DateMixin
A mixin class providing common behavior for all date-based views.
**Methods and Attributes**
.. attribute:: date_field
The name of the ``DateField`` or ``DateTimeField`` in the
``QuerySet``'s model that the date-based archive should use to
determine the objects on the page.
When :doc:`time zone support </topics/i18n/timezones>` is enabled and
``date_field`` is a ``DateTimeField``, dates are assumed to be in the
current time zone. Otherwise, the queryset could include objects from
the previous or the next day in the end user's time zone.
.. warning::
In this situation, if you have implemented per-user time zone
selection, the same URL may show a different set of objects,
depending on the end user's time zone. To avoid this, you should
use a ``DateField`` as the ``date_field`` attribute.
.. attribute:: allow_future
A boolean specifying whether to include "future" objects on this page,
where "future" means objects in which the field specified in
``date_field`` is greater than the current date/time. By default, this
is ``False``.
.. method:: get_date_field()
Returns the name of the field that contains the date data that this
view will operate on. Returns :attr:`DateMixin.date_field` by default.
.. method:: get_allow_future()
Determine whether to include "future" objects on this page, where
"future" means objects in which the field specified in ``date_field``
is greater than the current date/time. Returns
:attr:`DateMixin.allow_future` by default.
.. class:: django.views.generic.dates.BaseDateListView
A base class that provides common behavior for all date-based views. There
won't normally be a reason to instantiate
:class:`~django.views.generic.dates.BaseDateListView`; instantiate one of
the subclasses instead.
While this view (and it's subclasses) are executing, ``self.object_list``
will contain the list of objects that the view is operating upon, and
``self.date_list`` will contain the list of dates for which data is
available.
**Mixins**
* :class:`~django.views.generic.dates.DateMixin`
* :class:`~django.views.generic.list.MultipleObjectMixin`
**Methods and Attributes**
.. attribute:: allow_empty
A boolean specifying whether to display the page if no objects are
available. If this is ``True`` and no objects are available, the view
will display an empty page instead of raising a 404. By default, this
is ``False``.
.. method:: get_dated_items():
Returns a 3-tuple containing (``date_list``, ``object_list``,
``extra_context``).
``date_list`` is the list of dates for which data is available.
``object_list`` is the list of objects. ``extra_context`` is a
dictionary of context data that will be added to any context data
provided by the
:class:`~django.views.generic.list.MultipleObjectMixin`.
.. method:: get_dated_queryset(**lookup)
Returns a queryset, filtered using the query arguments defined by
``lookup``. Enforces any restrictions on the queryset, such as
``allow_empty`` and ``allow_future``.
.. method:: get_date_list(queryset, date_type)
Returns the list of dates of type ``date_type`` for which
``queryset`` contains entries. For example, ``get_date_list(qs,
'year')`` will return the list of years for which ``qs`` has entries.
See :meth:`~django.db.models.query.QuerySet.dates()` for the
ways that the ``date_type`` argument can be used.

View File

@ -0,0 +1,183 @@
==============
Editing mixins
==============
.. class:: django.views.generic.edit.FormMixin
A mixin class that provides facilities for creating and displaying forms.
**Methods and Attributes**
.. attribute:: initial
A dictionary containing initial data for the form.
.. attribute:: form_class
The form class to instantiate.
.. attribute:: success_url
The URL to redirect to when the form is successfully processed.
.. method:: get_initial()
Retrieve initial data for the form. By default, returns a copy of
:attr:`~django.views.generic.edit.FormMixin.initial`.
.. versionchanged:: 1.4
In Django 1.3, this method was returning the
:attr:`~django.views.generic.edit.FormMixin.initial` class variable
itself.
.. method:: get_form_class()
Retrieve the form class to instantiate. By default
:attr:`.form_class`.
.. method:: get_form(form_class)
Instantiate an instance of ``form_class`` using
:meth:`~django.views.generic.edit.FormMixin.get_form_kwargs`.
.. method:: get_form_kwargs()
Build the keyword arguments required to instantiate the form.
The ``initial`` argument is set to :meth:`.get_initial`. If the
request is a ``POST`` or ``PUT``, the request data (``request.POST``
and ``request.FILES``) will also be provided.
.. method:: get_success_url()
Determine the URL to redirect to when the form is successfully
validated. Returns
:attr:`~django.views.generic.edit.FormMixin.success_url` by default.
.. method:: form_valid(form)
Redirects to
:meth:`~django.views.generic.edit.FormMixin.get_success_url`.
.. method:: form_invalid(form)
Renders a response, providing the invalid form as context.
.. method:: get_context_data(**kwargs)
Populates a context containing the contents of ``kwargs``.
**Context**
* ``form``: The form instance that was generated for the view.
.. note::
Views mixing :class:`FormMixin` must provide an implementation of
:meth:`~django.views.generic.FormMixin.form_valid` and
:meth:`~django.views.generic.FormMixin.form_invalid`.
.. class:: django.views.generic.edit.ModelFormMixin
A form mixin that works on ModelForms, rather than a standalone form.
Since this is a subclass of
:class:`~django.views.generic.detail.SingleObjectMixin`, instances of this
mixin have access to the :attr:`~SingleObjectMixin.model` and
:attr:`~SingleObjectMixin.queryset` attributes, describing the type of
object that the ModelForm is manipulating. The view also provides
``self.object``, the instance being manipulated. If the instance is being
created, ``self.object`` will be ``None``.
**Mixins**
* :class:`django.views.generic.edit.FormMixin`
* :class:`django.views.generic.detail.SingleObjectMixin`
**Methods and Attributes**
.. attribute:: success_url
The URL to redirect to when the form is successfully processed.
``success_url`` may contain dictionary string formatting, which
will be interpolated against the object's field attributes. For
example, you could use ``success_url="/polls/%(slug)s/"`` to
redirect to a URL composed out of the ``slug`` field on a model.
.. method:: get_form_class()
Retrieve the form class to instantiate. If
:attr:`FormMixin.form_class` is provided, that class will be used.
Otherwise, a ModelForm will be instantiated using the model associated
with the :attr:`~SingleObjectMixin.queryset`, or with the
:attr:`~SingleObjectMixin.model`, depending on which attribute is
provided.
.. method:: get_form_kwargs()
Add the current instance (``self.object``) to the standard
:meth:`FormMixin.get_form_kwargs`.
.. method:: get_success_url()
Determine the URL to redirect to when the form is successfully
validated. Returns :attr:`ModelFormMixin.success_url` if it is provided;
otherwise, attempts to use the ``get_absolute_url()`` of the object.
.. method:: form_valid(form)
Saves the form instance, sets the current object for the view, and
redirects to
:meth:`~django.views.generic.edit.FormMixin.get_success_url`.
.. method:: form_invalid()
Renders a response, providing the invalid form as context.
.. class:: django.views.generic.edit.ProcessFormView
A mixin that provides basic HTTP GET and POST workflow.
.. note::
This is named 'ProcessFormView' and inherits directly from
:class:`django.views.generic.base.View`, but breaks if used
independently, so it is more of a mixin.
**Extends**
* :class:`django.views.generic.base.View`
**Methods and Attributes**
.. method:: get(request, *args, **kwargs)
Constructs a form, then renders a response using a context that
contains that form.
.. method:: post(request, *args, **kwargs)
Constructs a form, checks the form for validity, and handles it
accordingly.
The PUT action is also handled, as an analog of POST.
.. class:: django.views.generic.edit.DeletionMixin
Enables handling of the ``DELETE`` http action.
**Methods and Attributes**
.. attribute:: success_url
The url to redirect to when the nominated object has been
successfully deleted.
.. method:: get_success_url(obj)
Returns the url to redirect to when the nominated object has been
successfully deleted. Returns
:attr:`~django.views.generic.edit.DeletionMixin.success_url` by
default.

View File

@ -0,0 +1,175 @@
======================
Multiple object mixins
======================
.. class:: django.views.generic.list.MultipleObjectMixin
A mixin that can be used to display a list of objects.
If ``paginate_by`` is specified, Django will paginate the results returned
by this. You can specify the page number in the URL in one of two ways:
* Use the ``page`` parameter in the URLconf. For example, this is what
your URLconf might look like::
(r'^objects/page(?P<page>[0-9]+)/$', PaginatedView.as_view())
* Pass the page number via the ``page`` query-string parameter. For
example, a URL would look like this::
/objects/?page=3
These values and lists are 1-based, not 0-based, so the first page would be
represented as page ``1``.
For more on pagination, read the :doc:`pagination documentation
</topics/pagination>`.
As a special case, you are also permitted to use ``last`` as a value for
``page``::
/objects/?page=last
This allows you to access the final page of results without first having to
determine how many pages there are.
Note that ``page`` *must* be either a valid page number or the value
``last``; any other value for ``page`` will result in a 404 error.
**Extends**
* :class:`django.views.generic.base.ContextMixin`
**Methods and Attributes**
.. attribute:: allow_empty
A boolean specifying whether to display the page if no objects are
available. If this is ``False`` and no objects are available, the view
will raise a 404 instead of displaying an empty page. By default, this
is ``True``.
.. attribute:: model
The model that this view will display data for. Specifying ``model
= Foo`` is effectively the same as specifying ``queryset =
Foo.objects.all()``.
.. attribute:: queryset
A ``QuerySet`` that represents the objects. If provided, the value of
:attr:`MultipleObjectMixin.queryset` supersedes the value provided for
:attr:`MultipleObjectMixin.model`.
.. attribute:: paginate_by
An integer specifying how many objects should be displayed per page. If
this is given, the view will paginate objects with
:attr:`MultipleObjectMixin.paginate_by` objects per page. The view will
expect either a ``page`` query string parameter (via ``GET``) or a
``page`` variable specified in the URLconf.
.. attribute:: paginator_class
The paginator class to be used for pagination. By default,
:class:`django.core.paginator.Paginator` is used. If the custom paginator
class doesn't have the same constructor interface as
:class:`django.core.paginator.Paginator`, you will also need to
provide an implementation for :meth:`MultipleObjectMixin.get_paginator`.
.. attribute:: context_object_name
Designates the name of the variable to use in the context.
.. method:: get_queryset()
Returns the queryset that represents the data this view will display.
.. method:: paginate_queryset(queryset, page_size)
Returns a 4-tuple containing (``paginator``, ``page``, ``object_list``,
``is_paginated``).
Constructed by paginating ``queryset`` into pages of size ``page_size``.
If the request contains a ``page`` argument, either as a captured URL
argument or as a GET argument, ``object_list`` will correspond to the
objects from that page.
.. method:: get_paginate_by(queryset)
Returns the number of items to paginate by, or ``None`` for no
pagination. By default this simply returns the value of
:attr:`MultipleObjectMixin.paginate_by`.
.. method:: get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True)
Returns an instance of the paginator to use for this view. By default,
instantiates an instance of :attr:`paginator_class`.
.. method:: get_allow_empty()
Return a boolean specifying whether to display the page if no objects
are available. If this method returns ``False`` and no objects are
available, the view will raise a 404 instead of displaying an empty
page. By default, this is ``True``.
.. method:: get_context_object_name(object_list)
Return the context variable name that will be used to contain
the list of data that this view is manipulating. If
``object_list`` is a queryset of Django objects and
:attr:`~MultipleObjectMixin.context_object_name` is not set,
the context name will be the ``object_name`` of the model that
the queryset is composed from, with postfix ``'_list'``
appended. For example, the model ``Article`` would have a
context object named ``article_list``.
.. method:: get_context_data(**kwargs)
Returns context data for displaying the list of objects.
**Context**
* ``object_list``: The list of objects that this view is displaying. If
``context_object_name`` is specified, that variable will also be set
in the context, with the same value as ``object_list``.
* ``is_paginated``: A boolean representing whether the results are
paginated. Specifically, this is set to ``False`` if no page size has
been specified, or if the available objects do not span multiple
pages.
* ``paginator``: An instance of
:class:`django.core.paginator.Paginator`. If the page is not
paginated, this context variable will be ``None``.
* ``page_obj``: An instance of
:class:`django.core.paginator.Page`. If the page is not paginated,
this context variable will be ``None``.
.. class:: django.views.generic.list.MultipleObjectTemplateResponseMixin
A mixin class that performs template-based response rendering for views
that operate upon a list of object instances. Requires that the view it is
mixed with provides ``self.object_list``, the list of object instances that
the view is operating on. ``self.object_list`` may be, but is not required
to be, a :class:`~django.db.models.query.QuerySet`.
**Extends**
* :class:`~django.views.generic.base.TemplateResponseMixin`
**Methods and Attributes**
.. attribute:: template_name_suffix
The suffix to append to the auto-generated candidate template name.
Default suffix is ``_list``.
.. method:: get_template_names()
Returns a list of candidate template names. Returns the following list:
* the value of ``template_name`` on the view (if provided)
* ``<app_label>/<object_name><template_name_suffix>.html``

View File

@ -0,0 +1,60 @@
=============
Simple mixins
=============
.. class:: django.views.generic.base.ContextMixin
.. versionadded:: 1.5
**classpath**
``django.views.generic.base.ContextMixin``
**Methods**
.. method:: get_context_data(**kwargs)
Returns a dictionary representing the template context. The
keyword arguments provided will make up the returned context.
.. class:: django.views.generic.base.TemplateResponseMixin
Provides a mechanism to construct a
:class:`~django.template.response.TemplateResponse`, given
suitable context. The template to use is configurable and can be
further customized by subclasses.
**Methods and Attributes**
.. attribute:: response_class
The response class to be returned by ``render_to_response`` method.
Default is
:class:`TemplateResponse <django.template.response.TemplateResponse>`.
The template and context of ``TemplateResponse`` instances can be
altered later (e.g. in
:ref:`template response middleware <template-response-middleware>`).
If you need custom template loading or custom context object
instantiation, create a ``TemplateResponse`` subclass and assign it to
``response_class``.
.. method:: render_to_response(context, **response_kwargs)
Returns a ``self.response_class`` instance.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
list of template names that will be searched looking for an existent
template.
.. method:: get_template_names()
Returns a list of template names to search for when rendering the
template.
If :attr:`TemplateResponseMixin.template_name` is specified, the
default implementation will return a list containing
:attr:`TemplateResponseMixin.template_name` (if it is specified).

View File

@ -0,0 +1,124 @@
====================
Single object mixins
====================
.. class:: django.views.generic.detail.SingleObjectMixin
Provides a mechanism for looking up an object associated with the
current HTTP request.
**Methods and Attributes**
.. attribute:: model
The model that this view will display data for. Specifying ``model
= Foo`` is effectively the same as specifying ``queryset =
Foo.objects.all()``.
.. attribute:: queryset
A ``QuerySet`` that represents the objects. If provided, the value of
:attr:`SingleObjectMixin.queryset` supersedes the value provided for
:attr:`SingleObjectMixin.model`.
.. attribute:: slug_field
The name of the field on the model that contains the slug. By default,
``slug_field`` is ``'slug'``.
.. attribute:: slug_url_kwarg
.. versionadded:: 1.4
The name of the URLConf keyword argument that contains the slug. By
default, ``slug_url_kwarg`` is ``'slug'``.
.. attribute:: pk_url_kwarg
.. versionadded:: 1.4
The name of the URLConf keyword argument that contains the primary key.
By default, ``pk_url_kwarg`` is ``'pk'``.
.. attribute:: context_object_name
Designates the name of the variable to use in the context.
.. method:: get_object(queryset=None)
Returns the single object that this view will display. If
``queryset`` is provided, that queryset will be used as the
source of objects; otherwise,
:meth:`~SingleObjectMixin.get_queryset` will be used.
``get_object()`` looks for a
:attr:`SingleObjectMixin.pk_url_kwarg` argument in the arguments
to the view; if this argument is found, this method performs a
primary-key based lookup using that value. If this argument is not
found, it looks for a :attr:`SingleObjectMixin.slug_url_kwarg`
argument, and performs a slug lookup using the
:attr:`SingleObjectMixin.slug_field`.
.. method:: get_queryset()
Returns the queryset that will be used to retrieve the object that
this view will display. By default,
:meth:`~SingleObjectMixin.get_queryset` returns the value of the
:attr:`~SingleObjectMixin.queryset` attribute if it is set, otherwise
it constructs a :class:`QuerySet` by calling the `all()` method on the
:attr:`~SingleObjectMixin.model` attribute's default manager.
.. method:: get_context_object_name(obj)
Return the context variable name that will be used to contain the
data that this view is manipulating. If
:attr:`~SingleObjectMixin.context_object_name` is not set, the context
name will be constructed from the ``object_name`` of the model that
the queryset is composed from. For example, the model ``Article``
would have context object named ``'article'``.
.. method:: get_context_data(**kwargs)
Returns context data for displaying the list of objects.
**Context**
* ``object``: The object that this view is displaying. If
``context_object_name`` is specified, that variable will also be
set in the context, with the same value as ``object``.
.. class:: django.views.generic.detail.SingleObjectTemplateResponseMixin
A mixin class that performs template-based response rendering for views
that operate upon a single object instance. Requires that the view it is
mixed with provides ``self.object``, the object instance that the view is
operating on. ``self.object`` will usually be, but is not required to be,
an instance of a Django model. It may be ``None`` if the view is in the
process of constructing a new instance.
**Extends**
* :class:`~django.views.generic.base.TemplateResponseMixin`
**Methods and Attributes**
.. attribute:: template_name_field
The field on the current object instance that can be used to determine
the name of a candidate template. If either ``template_name_field``
itself or the value of the ``template_name_field`` on the current
object instance is ``None``, the object will not be used for a
candidate template name.
.. attribute:: template_name_suffix
The suffix to append to the auto-generated candidate template name.
Default suffix is ``_detail``.
.. method:: get_template_names()
Returns a list of candidate template names. Returns the following list:
* the value of ``template_name`` on the view (if provided)
* the contents of the ``template_name_field`` field on the
object instance that the view is operating upon (if available)
* ``<app_label>/<object_name><template_name_suffix>.html``

View File

@ -0,0 +1,14 @@
========================
Class-based views mixins
========================
Class-based views API reference. For introductory material, see :doc:`/topics/class-based-views/mixins`.
.. toctree::
:maxdepth: 1
mixins-simple
mixins-single-object
mixins-multiple-object
mixins-editing
mixins-date-based

View File

@ -6,6 +6,7 @@ API Reference
:maxdepth: 1 :maxdepth: 1
authbackends authbackends
class-based-views/index
clickjacking clickjacking
contrib/index contrib/index
databases databases
@ -13,7 +14,6 @@ API Reference
exceptions exceptions
files/index files/index
forms/index forms/index
class-based-views
middleware middleware
models/index models/index
request-response request-response

View File

@ -39,7 +39,7 @@ along with a completely generic view base class that can be used as
the basis for reusable applications that can be easily extended. the basis for reusable applications that can be easily extended.
See :doc:`the documentation on Class-based Generic Views See :doc:`the documentation on Class-based Generic Views
</topics/class-based-views>` for more details. There is also a document to </topics/class-based-views/index>` for more details. There is also a document to
help you `convert your function-based generic views to class-based help you `convert your function-based generic views to class-based
views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_. views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_.

View File

@ -80,7 +80,7 @@ provided, along with a completely generic view base class that can be
used as the basis for reusable applications that can be easily used as the basis for reusable applications that can be easily
extended. extended.
See :doc:`the documentation on class-based generic views</topics/class-based-views>` See :doc:`the documentation on class-based generic views</topics/class-based-views/index>`
for more details. There is also a document to help you `convert for more details. There is also a document to help you `convert
your function-based generic views to class-based your function-based generic views to class-based
views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_. views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_.

View File

@ -1450,9 +1450,10 @@ The permission_required decorator
Limiting access to generic views Limiting access to generic views
-------------------------------- --------------------------------
To limit access to a :doc:`class-based generic view </ref/class-based-views>`, To limit access to a :doc:`class-based generic view
decorate the :meth:`View.dispatch <django.views.generic.base.View.dispatch>` </ref/class-based-views/index>`, decorate the :meth:`View.dispatch
method on the class. See :ref:`decorating-class-based-views` for details. <django.views.generic.base.View.dispatch>` method on the class. See
:ref:`decorating-class-based-views` for details.
.. _permissions: .. _permissions:

View File

@ -1,624 +0,0 @@
=========================
Class-based generic views
=========================
.. versionadded:: 1.3
.. note::
Prior to Django 1.3, generic views were implemented as functions. The
function-based implementation has been 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:
* Perform common "simple" tasks: redirect to a different page and
render a given template.
* Display list and detail pages for a single object. If we were creating an
application to manage conferences then a ``TalkListView`` and a
``RegisteredUserListView`` would be examples of list views. A single
talk page is an example of what we call a "detail" view.
* Present date-based objects in year/month/day archive pages,
associated detail, and "latest" pages.
* Allow users to create, update, and delete objects -- with or
without authorization.
Taken together, these views provide easy interfaces to perform the most common
tasks developers encounter.
Simple usage
============
Class-based generic views (and any class-based views that inherit from
the base classes Django provides) can be configured in two
ways: subclassing, or passing in arguments directly in the URLconf.
When you subclass a class-based view, you can override attributes
(such as the ``template_name``) or methods (such as ``get_context_data``)
in your subclass to provide new values or methods. Consider, for example,
a view that just displays one template, ``about.html``. Django has a
generic view to do this - :class:`~django.views.generic.base.TemplateView` -
so we can just subclass it, and override the template name::
# some_app/views.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
Then, we just need to add this new view into our URLconf. As the class-based
views themselves are classes, we point the URL to the ``as_view`` class method
instead, which is the entry point for class-based views::
# urls.py
from django.conf.urls 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`, another simple
generic view.
Generic views of objects
========================
:class:`~django.views.generic.base.TemplateView` certainly is useful,
but Django's generic views really shine when it comes to presenting
views of your database content. Because it's such a common task,
Django comes with a handful of built-in generic views that make
generating list and detail views of objects incredibly easy.
Let's take a look at one of these generic views: the "object list" view. We'll
be using these models::
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __unicode__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
To build a list page of all publishers, we'd use a URLconf along these lines::
from django.conf.urls import patterns, url, include
from django.views.generic import ListView
from books.models import Publisher
urlpatterns = patterns('',
(r'^publishers/$', ListView.as_view(
model=Publisher,
)),
)
That's all the Python code we need to write. We still need to write a template,
however. We could explicitly tell the view which template to use
by including a ``template_name`` key in the arguments to as_view, but in
the absence of an explicit template Django will infer one from the object's
name. In this case, the inferred template will be
``"books/publisher_list.html"`` -- the "books" part comes from the name of the
app that defines the model, while the "publisher" bit is just the lowercased
version of the model's name.
.. note::
Thus, when (for example) the :class:`django.template.loaders.app_directories.Loader`
template loader is enabled in :setting:`TEMPLATE_LOADERS`, the template
location would be::
/path/to/project/books/templates/books/publisher_list.html
.. highlightlang:: html+django
This template will be rendered against a context containing a variable called
``object_list`` that contains all the publisher objects. A very simple template
might look like the following::
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
That's really all there is to it. All the cool features of generic views come
from changing the "info" dictionary passed to the generic view. The
:doc:`generic views reference</ref/class-based-views>` documents all the generic
views and their options in detail; the rest of this document will consider
some of the common ways you might customize and extend generic views.
Extending generic views
=======================
.. highlightlang:: python
There's no question that using generic views can speed up development
substantially. In most projects, however, there comes a moment when the
generic views no longer suffice. Indeed, the most common question asked by new
Django developers is how to make generic views handle a wider array of
situations.
This is one of the reasons generic views were redesigned for the 1.3 release -
previously, they were just view functions with a bewildering array of options;
now, rather than passing in a large amount of configuration in the URLconf,
the recommended way to extend generic views is to subclass them, and override
their attributes or methods.
Making "friendly" template contexts
-----------------------------------
You might have noticed that our sample publisher list template stores
all the publishers in a variable named ``object_list``. While this
works just fine, it isn't all that "friendly" to template authors:
they have to "just know" that they're dealing with publishers here.
Well, if you're dealing with a model object, this is already done for
you. When you are dealing with an object or queryset, Django is able
to populate the context using the verbose name (or the plural verbose
name, in the case of a list of objects) of the object being displayed.
This is provided in addition to the default ``object_list`` entry, but
contains exactly the same data.
If the verbose name (or plural verbose name) still isn't a good match,
you can manually set the name of the context variable. The
``context_object_name`` attribute on a generic view specifies the
context variable to use. In this example, we'll override it in the
URLconf, since it's a simple change:
.. parsed-literal::
urlpatterns = patterns('',
(r'^publishers/$', ListView.as_view(
model=Publisher,
**context_object_name="publisher_list",**
)),
)
Providing a useful ``context_object_name`` is always a good idea. Your
coworkers who design templates will thank you.
Adding extra context
--------------------
Often you simply need to present some extra information beyond that
provided by the generic view. For example, think of showing a list of
all the books on each publisher detail page. The
:class:`~django.views.generic.detail.DetailView` generic view provides
the publisher to the context, but it seems there's no way to get
additional information in that template.
However, there is; you can subclass
:class:`~django.views.generic.detail.DetailView` and provide your own
implementation of the ``get_context_data`` method. The default
implementation of this that comes with
:class:`~django.views.generic.detail.DetailView` simply adds in the
object being displayed to the template, but you can override it to show
more::
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetailView(DetailView):
context_object_name = "publisher"
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetailView, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
.. 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.
Viewing subsets of objects
--------------------------
Now let's take a closer look at the ``model`` argument we've been
using all along. The ``model`` argument, which specifies the database
model that the view will operate upon, is available on all the
generic views that operate on a single object or a collection of
objects. However, the ``model`` argument is not the only way to
specify the objects that the view will operate upon -- you can also
specify the list of objects using the ``queryset`` argument::
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetailView(DetailView):
context_object_name = "publisher"
queryset = Publisher.objects.all()
Specifying ``model = Publisher`` is really just shorthand for saying
``queryset = Publisher.objects.all()``. However, by using ``queryset``
to define a filtered list of objects you can be more specific about the
objects that will be visible in the view (see :doc:`/topics/db/queries`
for more information about :class:`QuerySet` objects, and see the
:doc:`class-based views reference </ref/class-based-views>` for the complete
details).
To pick a simple example, we might want to order a list of books by
publication date, with the most recent first::
urlpatterns = patterns('',
(r'^publishers/$', ListView.as_view(
queryset=Publisher.objects.all(),
context_object_name="publisher_list",
)),
(r'^books/$', ListView.as_view(
queryset=Book.objects.order_by("-publication_date"),
context_object_name="book_list",
)),
)
That's a pretty simple example, but it illustrates the idea nicely. Of course,
you'll usually want to do more than just reorder objects. If you want to
present a list of books by a particular publisher, you can use the same
technique (here, illustrated using subclassing rather than by passing arguments
in the URLconf)::
from django.views.generic import ListView
from books.models import Book
class AcmeBookListView(ListView):
context_object_name = "book_list"
queryset = Book.objects.filter(publisher__name="Acme Publishing")
template_name = "books/acme_list.html"
Notice that along with a filtered ``queryset``, we're also using a custom
template name. If we didn't, the generic view would use the same template as the
"vanilla" object list, which might not be what we want.
Also notice that this isn't a very elegant way of doing publisher-specific
books. If we want to add another publisher page, we'd need another handful of
lines in the URLconf, and more than a few publishers would get unreasonable.
We'll deal with this problem in the next section.
.. note::
If you get a 404 when requesting ``/books/acme/``, check to ensure you
actually have a Publisher with the name 'ACME Publishing'. Generic
views have an ``allow_empty`` parameter for this case. See the
:doc:`class-based-views reference</ref/class-based-views>` for more details.
Dynamic filtering
-----------------
Another common need is to filter down the objects given in a list page by some
key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but
what if we wanted to write a view that displayed all the books by some arbitrary
publisher?
Handily, the ``ListView`` has a
:meth:`~django.views.generic.detail.ListView.get_queryset` method we can
override. Previously, it has just been returning the value of the ``queryset``
attribute, but now we can add more logic.
The key part to making this work is that when class-based views are called,
various useful things are stored on ``self``; as well as the request
(``self.request``) this includes the positional (``self.args``) and name-based
(``self.kwargs``) arguments captured according to the URLconf.
Here, we have a URLconf with a single captured group::
from books.views import PublisherBookListView
urlpatterns = patterns('',
(r'^books/(\w+)/$', PublisherBookListView.as_view()),
)
Next, we'll write the ``PublisherBookListView`` view itself::
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookListView(ListView):
context_object_name = "book_list"
template_name = "books/books_by_publisher.html"
def get_queryset(self):
publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
return Book.objects.filter(publisher=publisher)
As you can see, it's quite easy to add more logic to the queryset selection;
if we wanted, we could use ``self.request.user`` to filter using the current
user, or other more complex logic.
We can also add the publisher into the context at the same time, so we can
use it in the template::
class PublisherBookListView(ListView):
context_object_name = "book_list"
template_name = "books/books_by_publisher.html"
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
return Book.objects.filter(publisher=self.publisher)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherBookListView, self).get_context_data(**kwargs)
# Add in the publisher
context['publisher'] = self.publisher
return context
Performing extra work
---------------------
The last common pattern we'll look at involves doing some extra work before
or after calling the generic view.
Imagine we had a ``last_accessed`` field on our ``Author`` object that we were
using to keep track of the last time anybody looked at that author::
# models.py
class Author(models.Model):
salutation = models.CharField(max_length=10)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
headshot = models.ImageField(upload_to='/tmp')
last_accessed = models.DateTimeField()
The generic ``DetailView`` class, of course, wouldn't know anything about this
field, but once again we could easily write a custom view to keep that field
updated.
First, we'd need to add an author detail bit in the URLconf to point to a
custom view:
.. parsed-literal::
from books.views import AuthorDetailView
urlpatterns = patterns('',
#...
**(r'^authors/(?P<pk>\\d+)/$', AuthorDetailView.as_view()),**
)
Then we'd write our new view -- ``get_object`` is the method that retrieves the
object -- so we simply override it and wrap the call::
import datetime
from books.models import Author
from django.views.generic import DetailView
from django.shortcuts import get_object_or_404
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
# Call the superclass
object = super(AuthorDetailView, self).get_object()
# Record the last accessed date
object.last_accessed = datetime.datetime.now()
object.save()
# Return the object
return object
.. note::
This code won't actually work unless you create a
``books/author_detail.html`` template.
.. note::
The URLconf here uses the named group ``pk`` - this name is the default
name that ``DetailView`` uses to find the value of the primary key used to
filter the queryset.
If you want to change it, you'll need to do your own ``get()`` call
on ``self.queryset`` using the new named parameter from ``self.kwargs``.
More than just HTML
-------------------
So far, we've been focusing on rendering templates to generate
responses. However, that's not all generic views can do.
Each generic view is composed out of a series of mixins, and each
mixin contributes a little piece of the entire view. Some of these
mixins -- such as
:class:`~django.views.generic.base.TemplateResponseMixin` -- are
specifically designed for rendering content to an HTML response using a
template. However, you can write your own mixins that perform
different rendering behavior.
For example, a simple JSON mixin might look something like this::
import json
from django import http
class JSONResponseMixin(object):
def render_to_response(self, context):
"Returns a JSON response containing 'context' as payload"
return self.get_json_response(self.convert_context_to_json(context))
def get_json_response(self, content, **httpresponse_kwargs):
"Construct an `HttpResponse` object."
return http.HttpResponse(content,
content_type='application/json',
**httpresponse_kwargs)
def convert_context_to_json(self, context):
"Convert the context dictionary into a JSON object"
# Note: This is *EXTREMELY* naive; in reality, you'll need
# to do much more complex handling to ensure that arbitrary
# objects -- such as Django model instances or querysets
# -- can be serialized as JSON.
return json.dumps(context)
Then, you could build a JSON-returning
:class:`~django.views.generic.detail.DetailView` by mixing your
:class:`JSONResponseMixin` with the
:class:`~django.views.generic.detail.BaseDetailView` -- (the
:class:`~django.views.generic.detail.DetailView` before template
rendering behavior has been mixed in)::
class JSONDetailView(JSONResponseMixin, BaseDetailView):
pass
This view can then be deployed in the same way as any other
:class:`~django.views.generic.detail.DetailView`, with exactly the
same behavior -- except for the format of the response.
If you want to be really adventurous, you could even mix a
:class:`~django.views.generic.detail.DetailView` subclass that is able
to return *both* HTML and JSON content, depending on some property of
the HTTP request, such as a query argument or a HTTP header. Just mix
in both the :class:`JSONResponseMixin` and a
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
and override the implementation of :func:`render_to_response()` to defer
to the appropriate subclass depending on the type of response that the user
requested::
class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
if self.request.GET.get('format','html') == 'json':
return JSONResponseMixin.render_to_response(self, context)
else:
return SingleObjectTemplateResponseMixin.render_to_response(self, context)
Because of the way that Python resolves method overloading, the local
``render_to_response()`` implementation will override the versions provided by
:class:`JSONResponseMixin` and
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
Decorating class-based views
============================
.. highlightlang:: python
The extension of class-based views isn't limited to using mixins. You
can use also use decorators.
Decorating in URLconf
---------------------
The simplest way of decorating class-based views is to decorate the
result of the :meth:`~django.views.generic.base.View.as_view` method.
The easiest place to do this is in the URLconf where you deploy your
view::
from django.contrib.auth.decorators import login_required, 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,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.

View File

@ -82,6 +82,8 @@ If your form is going to be used to directly add or edit a Django model, you can
use a :doc:`ModelForm </topics/forms/modelforms>` to avoid duplicating your model use a :doc:`ModelForm </topics/forms/modelforms>` to avoid duplicating your model
description. description.
.. _using-a-form-in-a-view:
Using a form in a view Using a form in a view
---------------------- ----------------------

View File

@ -2,4 +2,4 @@
Generic views Generic views
============= =============
See :doc:`/ref/class-based-views`. See :doc:`/ref/class-based-views/index`.

View File

@ -11,7 +11,7 @@ Introductions to all the key parts of Django you'll need to know:
http/index http/index
forms/index forms/index
templates templates
class-based-views class-based-views/index
files files
testing testing
auth auth