mirror of
				https://github.com/django/django.git
				synced 2025-10-28 16:16:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			690 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			690 lines
		
	
	
		
			29 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 ``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.detail.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 ``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 ``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:`~django.template.response.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). ``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:`~django.template.response.TemplateResponse`,
 | |
| :class:`DetailView` uses
 | |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
 | |
| which extends :class:`~django.views.generic.base.TemplateResponseMixin`,
 | |
| overriding
 | |
| :meth:`~django.views.generic.base.TemplateResponseMixin.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:`~django.db.models.query.QuerySet`, and then we need to make a
 | |
| :class:`~django.template.response.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:`~django.views.generic.detail.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:`~django.views.generic.list.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:`~django.template.response.TemplateResponse`,
 | |
| :class:`ListView` then uses
 | |
| :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`;
 | |
| as with :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
 | |
| above, this overrides ``get_template_names()`` to provide :meth:`a range of
 | |
| options <django.views.generic.list.MultipleObjectTemplateResponseMixin>`,
 | |
| 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:`~django.views.generic.detail.SingleObjectMixin` and
 | |
|     :class:`~django.views.generic.list.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:`~django.views.generic.detail.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 ``Author`` instance. You could also use a slug, or
 | |
| any of the other features of
 | |
| :class:`~django.views.generic.detail.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:`~django.views.generic.detail.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 ``get_object()``.
 | |
| 
 | |
| **Book queryset for use by ListView**
 | |
|     We'll figure that out ourselves in ``get_queryset()`` so we
 | |
|     can take into account the ``Publisher`` we're looking at.
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     We have to think carefully about ``get_context_data()``.
 | |
|     Since both :class:`~django.views.generic.detail.SingleObjectMixin` and
 | |
|     :class:`ListView` will
 | |
|     put things in the context data under the value of
 | |
|     ``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 ``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 ``get_queryset()`` so we
 | |
| can use it again later in ``get_context_data()``. If you don't set
 | |
| ``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:`~django.views.generic.detail.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 ``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:`~django.views.generic.list.MultipleObjectMixin` (generic list), but
 | |
|     you're likely to have problems combining ``SingleObjectMixin`` (generic
 | |
|     detail) with ``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:`~django.forms.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:`~django.views.generic.detail.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:`~django.forms.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 ``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:`~django.forms.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:`~django.views.generic.edit.FormMixin` and implement
 | |
|     ``post()`` ourselves rather than try to mix :class:`DetailView` with
 | |
|     :class:`FormView` (which provides a suitable ``post()`` already) because
 | |
|     both of the views implement ``get()``, and things would get much more
 | |
|     confusing.
 | |
| 
 | |
| .. highlightlang:: python
 | |
| 
 | |
| Our new ``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)
 | |
| 
 | |
| ``get_success_url()`` is just providing somewhere to redirect to,
 | |
| which gets used in the default implementation of
 | |
| ``form_valid()``. We have to provide our own ``post()`` as
 | |
| noted earlier, and override ``get_context_data()`` to make the
 | |
| :class:`~django.forms.Form` available in the context data.
 | |
| 
 | |
| A better solution
 | |
| -----------------
 | |
| 
 | |
| It should be obvious that the number of subtle interactions between
 | |
| :class:`~django.views.generic.edit.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 ``post()``
 | |
| method yourself, keeping :class:`DetailView` as the only generic
 | |
| functionality, although writing :class:`~django.forms.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:`~django.forms.Form` added to the context
 | |
| data), and ``POST`` requests should get the :class:`FormView`. Let's
 | |
| set up those views first.
 | |
| 
 | |
| The ``AuthorDisplay`` view is almost the same as :ref:`when we
 | |
| first introduced AuthorDetail<generic-views-extra-work>`; we have to
 | |
| write our own ``get_context_data()`` to make the
 | |
| ``AuthorInterestForm`` available to the template. We'll skip the
 | |
| ``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 ``AuthorInterest`` is a simple :class:`FormView`, but we
 | |
| have to bring in :class:`~django.views.generic.detail.SingleObjectMixin` so we
 | |
| can find the author we're talking about, and we have to remember to set
 | |
| ``template_name`` to ensure that form errors will render the same
 | |
| template as ``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 ``AuthorDetail`` view. We
 | |
| already know that calling :meth:`~django.views.generic.base.View.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:`~django.views.generic.base.View.as_view()` in the same way you
 | |
| would in your URLconf, such as if you wanted the ``AuthorInterest`` behavior
 | |
| 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
 | |
| ``JSONResponseMixin`` with the
 | |
| ``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 ``JSONResponseMixin`` and a
 | |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
 | |
| and override the implementation of
 | |
| :func:`~django.views.generic.base.TemplateResponseMixin.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
 | |
| ``JSONResponseMixin`` and
 | |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
 |