mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			684 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			684 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| ===================================
 | |
| Using mixins with class-based views
 | |
| ===================================
 | |
| 
 | |
| .. 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::
 | |
| 
 | |
|     # 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.
 | |
| 
 | |
| .. 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.
 | |
| 
 | |
| .. highlightlang:: python
 | |
| 
 | |
| Our new :class:`AuthorDetail` looks like this::
 | |
| 
 | |
|     # 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.
 | |
| 
 | |
| .. _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`.
 |