Fixed #18804 - Reorganized class based views docs a bit; thanks anthonyb for the initial patch.
This commit is contained in:
parent
01b9c3d519
commit
df7c1a13a0
|
@ -11,8 +11,7 @@ to structure your views and reuse code by harnessing inheritance and
|
||||||
mixins. There are also some generic views for simple tasks which we'll
|
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
|
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
|
reusable views which suits your use case. For full details, see the
|
||||||
:doc:`class-based views reference
|
:doc:`class-based views reference documentation</ref/class-based-views/index>`.
|
||||||
documentation</ref/class-based-views/index>`.
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
@ -32,19 +31,35 @@ redirect, and :class:`~django.views.generic.base.TemplateView` extends the base
|
||||||
to make it also render a template.
|
to make it also render a template.
|
||||||
|
|
||||||
|
|
||||||
Simple usage
|
Simple usage in your URLconf
|
||||||
============
|
============================
|
||||||
|
|
||||||
Class-based generic views (and any class-based views that inherit from
|
The simplest way to use generic views is to create them directly in your
|
||||||
the base classes Django provides) can be configured in two
|
URLconf. If you're only changing a few simple attributes on a class-based view,
|
||||||
ways: subclassing, or passing in arguments directly in the URLconf.
|
you can simply pass them into the ``as_view`` method call itself::
|
||||||
|
|
||||||
When you subclass a class-based view, you can override attributes
|
from django.conf.urls import patterns, url, include
|
||||||
(such as the ``template_name``) or methods (such as ``get_context_data``)
|
from django.views.generic import TemplateView
|
||||||
in your subclass to provide new values or methods. Consider, for example,
|
|
||||||
a view that just displays one template, ``about.html``. Django has a
|
urlpatterns = patterns('',
|
||||||
generic view to do this - :class:`~django.views.generic.base.TemplateView` -
|
(r'^about/', TemplateView.as_view(template_name="about.html")),
|
||||||
so we can just subclass it, and override the template name::
|
)
|
||||||
|
|
||||||
|
Any arguments given will override the ``template_name`` on the
|
||||||
|
A similar overriding pattern can be used for the ``url`` attribute on
|
||||||
|
:class:`~django.views.generic.base.RedirectView`.
|
||||||
|
|
||||||
|
|
||||||
|
Subclassing generic views
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The second, more powerful way to use generic views is to inherit from an
|
||||||
|
existing view and 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
|
# some_app/views.py
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
@ -52,9 +67,10 @@ so we can just subclass it, and override the template name::
|
||||||
class AboutView(TemplateView):
|
class AboutView(TemplateView):
|
||||||
template_name = "about.html"
|
template_name = "about.html"
|
||||||
|
|
||||||
Then, we just need to add this new view into our URLconf. As the class-based
|
Then we just need to add this new view into our URLconf.
|
||||||
views themselves are classes, we point the URL to the ``as_view`` class method
|
`~django.views.generic.base.TemplateView` is a class, not a function, so we
|
||||||
instead, which is the entry point for class-based views::
|
point the URL to the ``as_view`` class method instead, which provides a
|
||||||
|
function-like entry to class-based views::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import patterns, url, include
|
from django.conf.urls import patterns, url, include
|
||||||
|
@ -64,104 +80,6 @@ instead, which is the entry point for class-based views::
|
||||||
(r'^about/', AboutView.as_view()),
|
(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 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.http import HttpResponse
|
|
||||||
|
|
||||||
class JSONResponseMixin(object):
|
|
||||||
"""
|
|
||||||
A mixin that can be used to render a JSON response.
|
|
||||||
"""
|
|
||||||
response_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 TemplateView::
|
|
||||||
|
|
||||||
from django.views.generic import TemplateView
|
|
||||||
|
|
||||||
class JSONView(JSONResponseMixin, TemplateView):
|
|
||||||
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
|
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>`.
|
topic on :doc:`generic class based views</topics/class-based-views/generic-display>`.
|
||||||
|
@ -171,16 +89,15 @@ Decorating class-based views
|
||||||
|
|
||||||
.. highlightlang:: python
|
.. highlightlang:: python
|
||||||
|
|
||||||
The extension of class-based views isn't limited to using mixins. You
|
Since class-based views aren't functions, decorating them works differently
|
||||||
can use also use decorators.
|
depending on if you're using ``as_view`` or creating a subclass.
|
||||||
|
|
||||||
Decorating in URLconf
|
Decorating in URLconf
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
The simplest way of decorating class-based views is to decorate the
|
The simplest way of decorating class-based views is to decorate the
|
||||||
result of the :meth:`~django.views.generic.base.View.as_view` method.
|
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
|
The easiest place to do this is in the URLconf where you deploy your view::
|
||||||
view::
|
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
|
@ -69,7 +69,7 @@ interface to working with templates in class-based views.
|
||||||
add more members to the dictionary.
|
add more members to the dictionary.
|
||||||
|
|
||||||
Building up Django's generic class-based views
|
Building up Django's generic class-based views
|
||||||
===============================================
|
==============================================
|
||||||
|
|
||||||
Let's look at how two of Django's generic class-based views are built
|
Let's look at how two of Django's generic class-based views are built
|
||||||
out of mixins providing discrete functionality. We'll consider
|
out of mixins providing discrete functionality. We'll consider
|
||||||
|
@ -222,8 +222,7 @@ we'll want the functionality provided by
|
||||||
:class:`~django.views.generic.detail.SingleObjectMixin`.
|
:class:`~django.views.generic.detail.SingleObjectMixin`.
|
||||||
|
|
||||||
We'll demonstrate this with the publisher modelling we used in the
|
We'll demonstrate this with the publisher modelling we used in the
|
||||||
:doc:`generic class-based views
|
:doc:`generic class-based views introduction<generic-display>`.
|
||||||
introduction<generic-display>`.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -233,11 +232,11 @@ introduction<generic-display>`.
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from books.models import Author
|
from books.models import Author
|
||||||
|
|
||||||
class RecordInterest(View, SingleObjectMixin):
|
class RecordInterest(View, SingleObjectMixin):
|
||||||
"""Records the current user's interest in an author."""
|
"""Records the current user's interest in an author."""
|
||||||
model = Author
|
model = Author
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
@ -256,9 +255,7 @@ 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
|
``self.get_object()``. Everything else is taken care of for us by the
|
||||||
mixin.
|
mixin.
|
||||||
|
|
||||||
We can hook this into our URLs easily enough:
|
We can hook this into our URLs easily enough::
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from books.views import RecordInterest
|
from books.views import RecordInterest
|
||||||
|
@ -294,8 +291,6 @@ object. In order to do this, we need to have two different querysets:
|
||||||
We'll figure that out ourselves in :meth:`get_queryset()` so we
|
We'll figure that out ourselves in :meth:`get_queryset()` so we
|
||||||
can take into account the Publisher we're looking at.
|
can take into account the Publisher we're looking at.
|
||||||
|
|
||||||
.. highlightlang:: python
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
We have to think carefully about :meth:`get_context_data()`.
|
We have to think carefully about :meth:`get_context_data()`.
|
||||||
|
@ -311,15 +306,15 @@ Now we can write a new :class:`PublisherDetail`::
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from books.models import Publisher
|
from books.models import Publisher
|
||||||
|
|
||||||
class PublisherDetail(SingleObjectMixin, ListView):
|
class PublisherDetail(SingleObjectMixin, ListView):
|
||||||
paginate_by = 2
|
paginate_by = 2
|
||||||
template_name = "books/publisher_detail.html"
|
template_name = "books/publisher_detail.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['publisher'] = self.object
|
kwargs['publisher'] = self.object
|
||||||
return super(PublisherDetail, self).get_context_data(**kwargs)
|
return super(PublisherDetail, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.object = self.get_object(Publisher.objects.all())
|
self.object = self.get_object(Publisher.objects.all())
|
||||||
return self.object.book_set.all()
|
return self.object.book_set.all()
|
||||||
|
@ -339,26 +334,26 @@ have to create lots of books to see the pagination working! Here's the
|
||||||
template you'd want to use::
|
template you'd want to use::
|
||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Publisher {{ publisher.name }}</h2>
|
<h2>Publisher {{ publisher.name }}</h2>
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
{% for book in page_obj %}
|
{% for book in page_obj %}
|
||||||
<li>{{ book.title }}</li>
|
<li>{{ book.title }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<span class="step-links">
|
<span class="step-links">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<span class="current">
|
<span class="current">
|
||||||
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
|
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -428,9 +423,9 @@ code so that on ``POST`` the form gets called appropriately.
|
||||||
the views implement :meth:`get()`, and things would get much more
|
the views implement :meth:`get()`, and things would get much more
|
||||||
confusing.
|
confusing.
|
||||||
|
|
||||||
Our new :class:`AuthorDetail` looks like this:
|
.. highlightlang:: python
|
||||||
|
|
||||||
.. code-block:: python
|
Our new :class:`AuthorDetail` looks like this::
|
||||||
|
|
||||||
# CAUTION: you almost certainly do not want to do this.
|
# CAUTION: you almost certainly do not want to do this.
|
||||||
# It is provided as part of a discussion of problems you can
|
# It is provided as part of a discussion of problems you can
|
||||||
|
@ -455,7 +450,7 @@ Our new :class:`AuthorDetail` looks like this:
|
||||||
'author-detail',
|
'author-detail',
|
||||||
kwargs = {'pk': self.object.pk},
|
kwargs = {'pk': self.object.pk},
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
form = self.get_form(form_class)
|
form = self.get_form(form_class)
|
||||||
|
@ -464,7 +459,7 @@ Our new :class:`AuthorDetail` looks like this:
|
||||||
}
|
}
|
||||||
context.update(kwargs)
|
context.update(kwargs)
|
||||||
return super(AuthorDetail, self).get_context_data(**context)
|
return super(AuthorDetail, self).get_context_data(**context)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
form = self.get_form(form_class)
|
form = self.get_form(form_class)
|
||||||
|
@ -472,14 +467,14 @@ Our new :class:`AuthorDetail` looks like this:
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if not self.request.user.is_authenticated():
|
if not self.request.user.is_authenticated():
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
# record the interest using the message in form.cleaned_data
|
# record the interest using the message in form.cleaned_data
|
||||||
return super(AuthorDetail, self).form_valid(form)
|
return super(AuthorDetail, self).form_valid(form)
|
||||||
|
|
||||||
:meth:`get_success_url()` is just providing somewhere to redirect to,
|
:meth:`get_success_url()` is just providing somewhere to redirect to,
|
||||||
which gets used in the default implementation of
|
which gets used in the default implementation of
|
||||||
:meth:`form_valid()`. We have to provide our own :meth:`post()` as
|
:meth:`form_valid()`. We have to provide our own :meth:`post()` as
|
||||||
|
@ -525,21 +520,21 @@ write our own :meth:`get_context_data()` to make the
|
||||||
from django.views.generic import DetailView
|
from django.views.generic import DetailView
|
||||||
from django import forms
|
from django import forms
|
||||||
from books.models import Author
|
from books.models import Author
|
||||||
|
|
||||||
class AuthorInterestForm(forms.Form):
|
class AuthorInterestForm(forms.Form):
|
||||||
message = forms.CharField()
|
message = forms.CharField()
|
||||||
|
|
||||||
class AuthorDisplay(DetailView):
|
class AuthorDisplay(DetailView):
|
||||||
|
|
||||||
queryset = Author.objects.all()
|
queryset = Author.objects.all()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'form': AuthorInterestForm(),
|
'form': AuthorInterestForm(),
|
||||||
}
|
}
|
||||||
context.update(kwargs)
|
context.update(kwargs)
|
||||||
return super(AuthorDisplay, self).get_context_data(**context)
|
return super(AuthorDisplay, self).get_context_data(**context)
|
||||||
|
|
||||||
Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
|
Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
|
||||||
have to bring in :class:`SingleObjectMixin` so we can find the author
|
have to bring in :class:`SingleObjectMixin` so we can find the author
|
||||||
we're talking about, and we have to remember to set
|
we're talking about, and we have to remember to set
|
||||||
|
@ -550,7 +545,7 @@ template as :class:`AuthorDisplay` is using on ``GET``.
|
||||||
|
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
class AuthorInterest(FormView, SingleObjectMixin):
|
class AuthorInterest(FormView, SingleObjectMixin):
|
||||||
template_name = 'books/author_detail.html'
|
template_name = 'books/author_detail.html'
|
||||||
form_class = AuthorInterestForm
|
form_class = AuthorInterestForm
|
||||||
|
@ -561,13 +556,13 @@ template as :class:`AuthorDisplay` is using on ``GET``.
|
||||||
'object': self.get_object(),
|
'object': self.get_object(),
|
||||||
}
|
}
|
||||||
return super(AuthorInterest, self).get_context_data(**context)
|
return super(AuthorInterest, self).get_context_data(**context)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
'author-detail',
|
'author-detail',
|
||||||
kwargs = {'pk': self.object.pk},
|
kwargs = {'pk': self.object.pk},
|
||||||
)
|
)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if not self.request.user.is_authenticated():
|
if not self.request.user.is_authenticated():
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
@ -588,13 +583,13 @@ using a different template.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
class AuthorDetail(View):
|
class AuthorDetail(View):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
view = AuthorDisplay.as_view()
|
view = AuthorDisplay.as_view()
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
view = AuthorInterest.as_view()
|
view = AuthorInterest.as_view()
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs)
|
||||||
|
@ -603,3 +598,88 @@ This approach can also be used with any other generic class-based
|
||||||
views or your own class-based views inheriting directly from
|
views or your own class-based views inheriting directly from
|
||||||
:class:`View` or :class:`TemplateView`, as it keeps the different
|
:class:`View` or :class:`TemplateView`, as it keeps the different
|
||||||
views as separate as possible.
|
views as separate as possible.
|
||||||
|
|
||||||
|
.. _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 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.http import HttpResponse
|
||||||
|
|
||||||
|
class JSONResponseMixin(object):
|
||||||
|
"""
|
||||||
|
A mixin that can be used to render a JSON response.
|
||||||
|
"""
|
||||||
|
response_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 TemplateView::
|
||||||
|
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
class JSONView(JSONResponseMixin, TemplateView):
|
||||||
|
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`.
|
||||||
|
|
Loading…
Reference in New Issue