1. ===================================
    
  2. Using mixins with class-based views
    
  3. ===================================
    
  4. 
    
  5. .. caution::
    
  6. 
    
  7.     This is an advanced topic. A working knowledge of :doc:`Django's
    
  8.     class-based views<index>` is advised before exploring these
    
  9.     techniques.
    
  10. 
    
  11. Django's built-in class-based views provide a lot of functionality,
    
  12. but some of it you may want to use separately. For instance, you may
    
  13. want to write a view that renders a template to make the HTTP
    
  14. response, but you can't use
    
  15. :class:`~django.views.generic.base.TemplateView`; perhaps you need to
    
  16. render a template only on ``POST``, with ``GET`` doing something else
    
  17. entirely. While you could use
    
  18. :class:`~django.template.response.TemplateResponse` directly, this
    
  19. will likely result in duplicate code.
    
  20. 
    
  21. For this reason, Django also provides a number of mixins that provide
    
  22. more discrete functionality. Template rendering, for instance, is
    
  23. encapsulated in the
    
  24. :class:`~django.views.generic.base.TemplateResponseMixin`. The Django
    
  25. reference documentation contains :doc:`full documentation of all the
    
  26. mixins</ref/class-based-views/mixins>`.
    
  27. 
    
  28. Context and template responses
    
  29. ==============================
    
  30. 
    
  31. Two central mixins are provided that help in providing a consistent
    
  32. interface to working with templates in class-based views.
    
  33. 
    
  34. :class:`~django.views.generic.base.TemplateResponseMixin`
    
  35.     Every built in view which returns a
    
  36.     :class:`~django.template.response.TemplateResponse` will call the
    
  37.     :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response()`
    
  38.     method that ``TemplateResponseMixin`` provides. Most of the time this
    
  39.     will be called for you (for instance, it is called by the ``get()`` method
    
  40.     implemented by both :class:`~django.views.generic.base.TemplateView` and
    
  41.     :class:`~django.views.generic.detail.DetailView`); similarly, it's unlikely
    
  42.     that you'll need to override it, although if you want your response to
    
  43.     return something not rendered via a Django template then you'll want to do
    
  44.     it. For an example of this, see the :ref:`JSONResponseMixin example
    
  45.     <jsonresponsemixin-example>`.
    
  46. 
    
  47.     ``render_to_response()`` itself calls
    
  48.     :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`,
    
  49.     which by default will look up
    
  50.     :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` on
    
  51.     the class-based view; two other mixins
    
  52.     (:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
    
  53.     and
    
  54.     :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`)
    
  55.     override this to provide more flexible defaults when dealing with actual
    
  56.     objects.
    
  57. 
    
  58. :class:`~django.views.generic.base.ContextMixin`
    
  59.     Every built in view which needs context data, such as for rendering a
    
  60.     template (including ``TemplateResponseMixin`` above), should call
    
  61.     :meth:`~django.views.generic.base.ContextMixin.get_context_data()` passing
    
  62.     any data they want to ensure is in there as keyword arguments.
    
  63.     ``get_context_data()`` returns a dictionary; in ``ContextMixin`` it
    
  64.     returns its keyword arguments, but it is common to override this to add
    
  65.     more members to the dictionary. You can also use the
    
  66.     :attr:`~django.views.generic.base.ContextMixin.extra_context` attribute.
    
  67. 
    
  68. Building up Django's generic class-based views
    
  69. ==============================================
    
  70. 
    
  71. Let's look at how two of Django's generic class-based views are built
    
  72. out of mixins providing discrete functionality. We'll consider
    
  73. :class:`~django.views.generic.detail.DetailView`, which renders a
    
  74. "detail" view of an object, and
    
  75. :class:`~django.views.generic.list.ListView`, which will render a list
    
  76. of objects, typically from a queryset, and optionally paginate
    
  77. them. This will introduce us to four mixins which between them provide
    
  78. useful functionality when working with either a single Django object,
    
  79. or multiple objects.
    
  80. 
    
  81. There are also mixins involved in the generic edit views
    
  82. (:class:`~django.views.generic.edit.FormView`, and the model-specific
    
  83. views :class:`~django.views.generic.edit.CreateView`,
    
  84. :class:`~django.views.generic.edit.UpdateView` and
    
  85. :class:`~django.views.generic.edit.DeleteView`), and in the
    
  86. date-based generic views. These are
    
  87. covered in the :doc:`mixin reference
    
  88. documentation</ref/class-based-views/mixins>`.
    
  89. 
    
  90. ``DetailView``: working with a single Django object
    
  91. ---------------------------------------------------
    
  92. 
    
  93. To show the detail of an object, we basically need to do two things:
    
  94. we need to look up the object and then we need to make a
    
  95. :class:`~django.template.response.TemplateResponse` with a suitable template,
    
  96. and that object as context.
    
  97. 
    
  98. To get the object, :class:`~django.views.generic.detail.DetailView`
    
  99. relies on :class:`~django.views.generic.detail.SingleObjectMixin`,
    
  100. which provides a
    
  101. :meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
    
  102. method that figures out the object based on the URL of the request (it
    
  103. looks for ``pk`` and ``slug`` keyword arguments as declared in the
    
  104. URLConf, and looks the object up either from the
    
  105. :attr:`~django.views.generic.detail.SingleObjectMixin.model` attribute
    
  106. on the view, or the
    
  107. :attr:`~django.views.generic.detail.SingleObjectMixin.queryset`
    
  108. attribute if that's provided). ``SingleObjectMixin`` also overrides
    
  109. :meth:`~django.views.generic.base.ContextMixin.get_context_data()`,
    
  110. which is used across all Django's built in class-based views to supply
    
  111. context data for template renders.
    
  112. 
    
  113. To then make a :class:`~django.template.response.TemplateResponse`,
    
  114. :class:`DetailView` uses
    
  115. :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
    
  116. which extends :class:`~django.views.generic.base.TemplateResponseMixin`,
    
  117. overriding
    
  118. :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()`
    
  119. as discussed above. It actually provides a fairly sophisticated set of options,
    
  120. but the main one that most people are going to use is
    
  121. ``<app_label>/<model_name>_detail.html``. The ``_detail`` part can be changed
    
  122. by setting
    
  123. :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
    
  124. on a subclass to something else. (For instance, the :doc:`generic edit
    
  125. views<generic-editing>` use ``_form`` for create and update views, and
    
  126. ``_confirm_delete`` for delete views.)
    
  127. 
    
  128. ``ListView``: working with many Django objects
    
  129. ----------------------------------------------
    
  130. 
    
  131. Lists of objects follow roughly the same pattern: we need a (possibly
    
  132. paginated) list of objects, typically a
    
  133. :class:`~django.db.models.query.QuerySet`, and then we need to make a
    
  134. :class:`~django.template.response.TemplateResponse` with a suitable template
    
  135. using that list of objects.
    
  136. 
    
  137. To get the objects, :class:`~django.views.generic.list.ListView` uses
    
  138. :class:`~django.views.generic.list.MultipleObjectMixin`, which
    
  139. provides both
    
  140. :meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`
    
  141. and
    
  142. :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`. Unlike
    
  143. with :class:`~django.views.generic.detail.SingleObjectMixin`, there's no need
    
  144. to key off parts of the URL to figure out the queryset to work with, so the
    
  145. default uses the
    
  146. :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` or
    
  147. :attr:`~django.views.generic.list.MultipleObjectMixin.model` attribute
    
  148. on the view class. A common reason to override
    
  149. :meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`
    
  150. here would be to dynamically vary the objects, such as depending on
    
  151. the current user or to exclude posts in the future for a blog.
    
  152. 
    
  153. :class:`~django.views.generic.list.MultipleObjectMixin` also overrides
    
  154. :meth:`~django.views.generic.base.ContextMixin.get_context_data()` to
    
  155. include appropriate context variables for pagination (providing
    
  156. dummies if pagination is disabled). It relies on ``object_list`` being
    
  157. passed in as a keyword argument, which :class:`ListView` arranges for
    
  158. it.
    
  159. 
    
  160. To make a :class:`~django.template.response.TemplateResponse`,
    
  161. :class:`ListView` then uses
    
  162. :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`;
    
  163. as with :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
    
  164. above, this overrides ``get_template_names()`` to provide :meth:`a range of
    
  165. options <django.views.generic.list.MultipleObjectTemplateResponseMixin>`,
    
  166. with the most commonly-used being
    
  167. ``<app_label>/<model_name>_list.html``, with the ``_list`` part again
    
  168. being taken from the
    
  169. :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
    
  170. attribute. (The date based generic views use suffixes such as ``_archive``,
    
  171. ``_archive_year`` and so on to use different templates for the various
    
  172. specialized date-based list views.)
    
  173. 
    
  174. Using Django's class-based view mixins
    
  175. ======================================
    
  176. 
    
  177. Now we've seen how Django's generic class-based views use the provided mixins,
    
  178. let's look at other ways we can combine them. We're still going to be combining
    
  179. them with either built-in class-based views, or other generic class-based
    
  180. views, but there are a range of rarer problems you can solve than are provided
    
  181. for by Django out of the box.
    
  182. 
    
  183. .. warning::
    
  184. 
    
  185.     Not all mixins can be used together, and not all generic class
    
  186.     based views can be used with all other mixins. Here we present a
    
  187.     few examples that do work; if you want to bring together other
    
  188.     functionality then you'll have to consider interactions between
    
  189.     attributes and methods that overlap between the different classes
    
  190.     you're using, and how `method resolution order`_ will affect which
    
  191.     versions of the methods will be called in what order.
    
  192. 
    
  193.     The reference documentation for Django's :doc:`class-based
    
  194.     views</ref/class-based-views/index>` and :doc:`class-based view
    
  195.     mixins</ref/class-based-views/mixins>` will help you in
    
  196.     understanding which attributes and methods are likely to cause
    
  197.     conflict between different classes and mixins.
    
  198. 
    
  199.     If in doubt, it's often better to back off and base your work on
    
  200.     :class:`View` or :class:`TemplateView`, perhaps with
    
  201.     :class:`~django.views.generic.detail.SingleObjectMixin` and
    
  202.     :class:`~django.views.generic.list.MultipleObjectMixin`. Although you
    
  203.     will probably end up writing more code, it is more likely to be clearly
    
  204.     understandable to someone else coming to it later, and with fewer
    
  205.     interactions to worry about you will save yourself some thinking. (Of
    
  206.     course, you can always dip into Django's implementation of the generic
    
  207.     class-based views for inspiration on how to tackle problems.)
    
  208. 
    
  209. .. _method resolution order: https://www.python.org/download/releases/2.3/mro/
    
  210. 
    
  211. Using ``SingleObjectMixin`` with View
    
  212. -------------------------------------
    
  213. 
    
  214. If we want to write a class-based view that responds only to ``POST``, we'll
    
  215. subclass :class:`~django.views.generic.base.View` and write a ``post()`` method
    
  216. in the subclass. However if we want our processing to work on a particular
    
  217. object, identified from the URL, we'll want the functionality provided by
    
  218. :class:`~django.views.generic.detail.SingleObjectMixin`.
    
  219. 
    
  220. We'll demonstrate this with the ``Author`` model we used in the
    
  221. :doc:`generic class-based views introduction<generic-display>`.
    
  222. 
    
  223. .. code-block:: python
    
  224.     :caption: ``views.py``
    
  225. 
    
  226.     from django.http import HttpResponseForbidden, HttpResponseRedirect
    
  227.     from django.urls import reverse
    
  228.     from django.views import View
    
  229.     from django.views.generic.detail import SingleObjectMixin
    
  230.     from books.models import Author
    
  231. 
    
  232.     class RecordInterestView(SingleObjectMixin, View):
    
  233.         """Records the current user's interest in an author."""
    
  234.         model = Author
    
  235. 
    
  236.         def post(self, request, *args, **kwargs):
    
  237.             if not request.user.is_authenticated:
    
  238.                 return HttpResponseForbidden()
    
  239. 
    
  240.             # Look up the author we're interested in.
    
  241.             self.object = self.get_object()
    
  242.             # Actually record interest somehow here!
    
  243. 
    
  244.             return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
    
  245. 
    
  246. In practice you'd probably want to record the interest in a key-value
    
  247. store rather than in a relational database, so we've left that bit
    
  248. out. The only bit of the view that needs to worry about using
    
  249. :class:`~django.views.generic.detail.SingleObjectMixin` is where we want to
    
  250. look up the author we're interested in, which it does with a call to
    
  251. ``self.get_object()``. Everything else is taken care of for us by the mixin.
    
  252. 
    
  253. We can hook this into our URLs easily enough:
    
  254. 
    
  255. .. code-block:: python
    
  256.     :caption: ``urls.py``
    
  257. 
    
  258.     from django.urls import path
    
  259.     from books.views import RecordInterestView
    
  260. 
    
  261.     urlpatterns = [
    
  262.         #...
    
  263.         path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
    
  264.     ]
    
  265. 
    
  266. Note the ``pk`` named group, which
    
  267. :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` uses
    
  268. to look up the ``Author`` instance. You could also use a slug, or
    
  269. any of the other features of
    
  270. :class:`~django.views.generic.detail.SingleObjectMixin`.
    
  271. 
    
  272. Using ``SingleObjectMixin`` with ``ListView``
    
  273. ---------------------------------------------
    
  274. 
    
  275. :class:`~django.views.generic.list.ListView` provides built-in
    
  276. pagination, but you might want to paginate a list of objects that are
    
  277. all linked (by a foreign key) to another object. In our publishing
    
  278. example, you might want to paginate through all the books by a
    
  279. particular publisher.
    
  280. 
    
  281. One way to do this is to combine :class:`ListView` with
    
  282. :class:`~django.views.generic.detail.SingleObjectMixin`, so that the queryset
    
  283. for the paginated list of books can hang off the publisher found as the single
    
  284. object. In order to do this, we need to have two different querysets:
    
  285. 
    
  286. ``Book`` queryset for use by :class:`~django.views.generic.list.ListView`
    
  287.     Since we have access to the ``Publisher`` whose books we want to list, we
    
  288.     override ``get_queryset()`` and use the ``Publisher``’s :ref:`reverse
    
  289.     foreign key manager<backwards-related-objects>`.
    
  290. 
    
  291. ``Publisher`` queryset for use in :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
    
  292.     We'll rely on the default implementation of ``get_object()`` to fetch the
    
  293.     correct ``Publisher`` object.
    
  294.     However, we need to explicitly pass a ``queryset`` argument because
    
  295.     otherwise the default implementation of ``get_object()`` would call
    
  296.     ``get_queryset()`` which we have overridden to return ``Book`` objects
    
  297.     instead of ``Publisher`` ones.
    
  298. 
    
  299. .. note::
    
  300. 
    
  301.     We have to think carefully about ``get_context_data()``.
    
  302.     Since both :class:`~django.views.generic.detail.SingleObjectMixin` and
    
  303.     :class:`ListView` will
    
  304.     put things in the context data under the value of
    
  305.     ``context_object_name`` if it's set, we'll instead explicitly
    
  306.     ensure the ``Publisher`` is in the context data. :class:`ListView`
    
  307.     will add in the suitable ``page_obj`` and ``paginator`` for us
    
  308.     providing we remember to call ``super()``.
    
  309. 
    
  310. Now we can write a new ``PublisherDetailView``::
    
  311. 
    
  312.     from django.views.generic import ListView
    
  313.     from django.views.generic.detail import SingleObjectMixin
    
  314.     from books.models import Publisher
    
  315. 
    
  316.     class PublisherDetailView(SingleObjectMixin, ListView):
    
  317.         paginate_by = 2
    
  318.         template_name = "books/publisher_detail.html"
    
  319. 
    
  320.         def get(self, request, *args, **kwargs):
    
  321.             self.object = self.get_object(queryset=Publisher.objects.all())
    
  322.             return super().get(request, *args, **kwargs)
    
  323. 
    
  324.         def get_context_data(self, **kwargs):
    
  325.             context = super().get_context_data(**kwargs)
    
  326.             context['publisher'] = self.object
    
  327.             return context
    
  328. 
    
  329.         def get_queryset(self):
    
  330.             return self.object.book_set.all()
    
  331. 
    
  332. Notice how we set ``self.object`` within ``get()`` so we
    
  333. can use it again later in ``get_context_data()`` and ``get_queryset()``.
    
  334. If you don't set ``template_name``, the template will default to the normal
    
  335. :class:`ListView` choice, which in this case would be
    
  336. ``"books/book_list.html"`` because it's a list of books;
    
  337. :class:`ListView` knows nothing about
    
  338. :class:`~django.views.generic.detail.SingleObjectMixin`, so it doesn't have
    
  339. any clue this view is anything to do with a ``Publisher``.
    
  340. 
    
  341. The ``paginate_by`` is deliberately small in the example so you don't
    
  342. have to create lots of books to see the pagination working! Here's the
    
  343. template you'd want to use:
    
  344. 
    
  345. .. code-block:: html+django
    
  346. 
    
  347.     {% extends "base.html" %}
    
  348. 
    
  349.     {% block content %}
    
  350.         <h2>Publisher {{ publisher.name }}</h2>
    
  351. 
    
  352.         <ol>
    
  353.           {% for book in page_obj %}
    
  354.             <li>{{ book.title }}</li>
    
  355.           {% endfor %}
    
  356.         </ol>
    
  357. 
    
  358.         <div class="pagination">
    
  359.             <span class="step-links">
    
  360.                 {% if page_obj.has_previous %}
    
  361.                     <a href="?page={{ page_obj.previous_page_number }}">previous</a>
    
  362.                 {% endif %}
    
  363. 
    
  364.                 <span class="current">
    
  365.                     Page {{ page_obj.number }} of {{ paginator.num_pages }}.
    
  366.                 </span>
    
  367. 
    
  368.                 {% if page_obj.has_next %}
    
  369.                     <a href="?page={{ page_obj.next_page_number }}">next</a>
    
  370.                 {% endif %}
    
  371.             </span>
    
  372.         </div>
    
  373.     {% endblock %}
    
  374. 
    
  375. Avoid anything more complex
    
  376. ===========================
    
  377. 
    
  378. Generally you can use
    
  379. :class:`~django.views.generic.base.TemplateResponseMixin` and
    
  380. :class:`~django.views.generic.detail.SingleObjectMixin` when you need
    
  381. their functionality. As shown above, with a bit of care you can even
    
  382. combine ``SingleObjectMixin`` with
    
  383. :class:`~django.views.generic.list.ListView`. However things get
    
  384. increasingly complex as you try to do so, and a good rule of thumb is:
    
  385. 
    
  386. .. hint::
    
  387. 
    
  388.     Each of your views should use only mixins or views from one of the
    
  389.     groups of generic class-based views: :doc:`detail,
    
  390.     list<generic-display>`, :doc:`editing<generic-editing>` and
    
  391.     date. For example it's fine to combine
    
  392.     :class:`TemplateView` (built in view) with
    
  393.     :class:`~django.views.generic.list.MultipleObjectMixin` (generic list), but
    
  394.     you're likely to have problems combining ``SingleObjectMixin`` (generic
    
  395.     detail) with ``MultipleObjectMixin`` (generic list).
    
  396. 
    
  397. To show what happens when you try to get more sophisticated, we show
    
  398. an example that sacrifices readability and maintainability when there
    
  399. is a simpler solution. First, let's look at a naive attempt to combine
    
  400. :class:`~django.views.generic.detail.DetailView` with
    
  401. :class:`~django.views.generic.edit.FormMixin` to enable us to
    
  402. ``POST`` a Django :class:`~django.forms.Form` to the same URL as we're
    
  403. displaying an object using :class:`DetailView`.
    
  404. 
    
  405. Using ``FormMixin`` with ``DetailView``
    
  406. ---------------------------------------
    
  407. 
    
  408. Think back to our earlier example of using :class:`View` and
    
  409. :class:`~django.views.generic.detail.SingleObjectMixin` together. We were
    
  410. recording a user's interest in a particular author; say now that we want to
    
  411. let them leave a message saying why they like them. Again, let's assume we're
    
  412. not going to store this in a relational database but instead in
    
  413. something more esoteric that we won't worry about here.
    
  414. 
    
  415. At this point it's natural to reach for a :class:`~django.forms.Form` to
    
  416. encapsulate the information sent from the user's browser to Django. Say also
    
  417. that we're heavily invested in `REST`_, so we want to use the same URL for
    
  418. displaying the author as for capturing the message from the
    
  419. user. Let's rewrite our ``AuthorDetailView`` to do that.
    
  420. 
    
  421. .. _REST: https://en.wikipedia.org/wiki/Representational_state_transfer
    
  422. 
    
  423. We'll keep the ``GET`` handling from :class:`DetailView`, although
    
  424. we'll have to add a :class:`~django.forms.Form` into the context data so we can
    
  425. render it in the template. We'll also want to pull in form processing
    
  426. from :class:`~django.views.generic.edit.FormMixin`, and write a bit of
    
  427. code so that on ``POST`` the form gets called appropriately.
    
  428. 
    
  429. .. note::
    
  430. 
    
  431.     We use :class:`~django.views.generic.edit.FormMixin` and implement
    
  432.     ``post()`` ourselves rather than try to mix :class:`DetailView` with
    
  433.     :class:`FormView` (which provides a suitable ``post()`` already) because
    
  434.     both of the views implement ``get()``, and things would get much more
    
  435.     confusing.
    
  436. 
    
  437. Our new ``AuthorDetailView`` looks like this::
    
  438. 
    
  439.     # CAUTION: you almost certainly do not want to do this.
    
  440.     # It is provided as part of a discussion of problems you can
    
  441.     # run into when combining different generic class-based view
    
  442.     # functionality that is not designed to be used together.
    
  443. 
    
  444.     from django import forms
    
  445.     from django.http import HttpResponseForbidden
    
  446.     from django.urls import reverse
    
  447.     from django.views.generic import DetailView
    
  448.     from django.views.generic.edit import FormMixin
    
  449.     from books.models import Author
    
  450. 
    
  451.     class AuthorInterestForm(forms.Form):
    
  452.         message = forms.CharField()
    
  453. 
    
  454.     class AuthorDetailView(FormMixin, DetailView):
    
  455.         model = Author
    
  456.         form_class = AuthorInterestForm
    
  457. 
    
  458.         def get_success_url(self):
    
  459.             return reverse('author-detail', kwargs={'pk': self.object.pk})
    
  460. 
    
  461.         def post(self, request, *args, **kwargs):
    
  462.             if not request.user.is_authenticated:
    
  463.                 return HttpResponseForbidden()
    
  464.             self.object = self.get_object()
    
  465.             form = self.get_form()
    
  466.             if form.is_valid():
    
  467.                 return self.form_valid(form)
    
  468.             else:
    
  469.                 return self.form_invalid(form)
    
  470. 
    
  471.         def form_valid(self, form):
    
  472.             # Here, we would record the user's interest using the message
    
  473.             # passed in form.cleaned_data['message']
    
  474.             return super().form_valid(form)
    
  475. 
    
  476. ``get_success_url()`` provides somewhere to redirect to, which gets used
    
  477. in the default implementation of ``form_valid()``. We have to provide our
    
  478. own ``post()`` as noted earlier.
    
  479. 
    
  480. A better solution
    
  481. -----------------
    
  482. 
    
  483. The number of subtle interactions between
    
  484. :class:`~django.views.generic.edit.FormMixin` and :class:`DetailView` is
    
  485. already testing our ability to manage things. It's unlikely you'd want to
    
  486. write this kind of class yourself.
    
  487. 
    
  488. In this case, you could write the ``post()`` method yourself, keeping
    
  489. :class:`DetailView` as the only generic functionality, although writing
    
  490. :class:`~django.forms.Form` handling code involves a lot of duplication.
    
  491. 
    
  492. Alternatively, it would still be less work than the above approach to
    
  493. have a separate view for processing the form, which could use
    
  494. :class:`~django.views.generic.edit.FormView` distinct from
    
  495. :class:`DetailView` without concerns.
    
  496. 
    
  497. An alternative better solution
    
  498. ------------------------------
    
  499. 
    
  500. What we're really trying to do here is to use two different class
    
  501. based views from the same URL. So why not do just that? We have a very
    
  502. clear division here: ``GET`` requests should get the
    
  503. :class:`DetailView` (with the :class:`~django.forms.Form` added to the context
    
  504. data), and ``POST`` requests should get the :class:`FormView`. Let's
    
  505. set up those views first.
    
  506. 
    
  507. The ``AuthorDetailView`` view is almost the same as :ref:`when we
    
  508. first introduced AuthorDetailView<generic-views-extra-work>`; we have to
    
  509. write our own ``get_context_data()`` to make the
    
  510. ``AuthorInterestForm`` available to the template. We'll skip the
    
  511. ``get_object()`` override from before for clarity::
    
  512. 
    
  513.     from django import forms
    
  514.     from django.views.generic import DetailView
    
  515.     from books.models import Author
    
  516. 
    
  517.     class AuthorInterestForm(forms.Form):
    
  518.         message = forms.CharField()
    
  519. 
    
  520.     class AuthorDetailView(DetailView):
    
  521.         model = Author
    
  522. 
    
  523.         def get_context_data(self, **kwargs):
    
  524.             context = super().get_context_data(**kwargs)
    
  525.             context['form'] = AuthorInterestForm()
    
  526.             return context
    
  527. 
    
  528. Then the ``AuthorInterestForm`` is a :class:`FormView`, but we have to bring in
    
  529. :class:`~django.views.generic.detail.SingleObjectMixin` so we can find the
    
  530. author we're talking about, and we have to remember to set ``template_name`` to
    
  531. ensure that form errors will render the same template as ``AuthorDetailView``
    
  532. is using on ``GET``::
    
  533. 
    
  534.     from django.http import HttpResponseForbidden
    
  535.     from django.urls import reverse
    
  536.     from django.views.generic import FormView
    
  537.     from django.views.generic.detail import SingleObjectMixin
    
  538. 
    
  539.     class AuthorInterestFormView(SingleObjectMixin, FormView):
    
  540.         template_name = 'books/author_detail.html'
    
  541.         form_class = AuthorInterestForm
    
  542.         model = Author
    
  543. 
    
  544.         def post(self, request, *args, **kwargs):
    
  545.             if not request.user.is_authenticated:
    
  546.                 return HttpResponseForbidden()
    
  547.             self.object = self.get_object()
    
  548.             return super().post(request, *args, **kwargs)
    
  549. 
    
  550.         def get_success_url(self):
    
  551.             return reverse('author-detail', kwargs={'pk': self.object.pk})
    
  552. 
    
  553. Finally we bring this together in a new ``AuthorView`` view. We
    
  554. already know that calling :meth:`~django.views.generic.base.View.as_view()` on
    
  555. a class-based view gives us something that behaves exactly like a function
    
  556. based view, so we can do that at the point we choose between the two subviews.
    
  557. 
    
  558. You can pass through keyword arguments to
    
  559. :meth:`~django.views.generic.base.View.as_view()` in the same way you
    
  560. would in your URLconf, such as if you wanted the ``AuthorInterestFormView``
    
  561. behavior to also appear at another URL but using a different template::
    
  562. 
    
  563.     from django.views import View
    
  564. 
    
  565.     class AuthorView(View):
    
  566. 
    
  567.         def get(self, request, *args, **kwargs):
    
  568.             view = AuthorDetailView.as_view()
    
  569.             return view(request, *args, **kwargs)
    
  570. 
    
  571.         def post(self, request, *args, **kwargs):
    
  572.             view = AuthorInterestFormView.as_view()
    
  573.             return view(request, *args, **kwargs)
    
  574. 
    
  575. This approach can also be used with any other generic class-based
    
  576. views or your own class-based views inheriting directly from
    
  577. :class:`View` or :class:`TemplateView`, as it keeps the different
    
  578. views as separate as possible.
    
  579. 
    
  580. .. _jsonresponsemixin-example:
    
  581. 
    
  582. More than just HTML
    
  583. ===================
    
  584. 
    
  585. Where class-based views shine is when you want to do the same thing many times.
    
  586. Suppose you're writing an API, and every view should return JSON instead of
    
  587. rendered HTML.
    
  588. 
    
  589. We can create a mixin class to use in all of our views, handling the
    
  590. conversion to JSON once.
    
  591. 
    
  592. For example, a JSON mixin might look something like this::
    
  593. 
    
  594.     from django.http import JsonResponse
    
  595. 
    
  596.     class JSONResponseMixin:
    
  597.         """
    
  598.         A mixin that can be used to render a JSON response.
    
  599.         """
    
  600.         def render_to_json_response(self, context, **response_kwargs):
    
  601.             """
    
  602.             Returns a JSON response, transforming 'context' to make the payload.
    
  603.             """
    
  604.             return JsonResponse(
    
  605.                 self.get_data(context),
    
  606.                 **response_kwargs
    
  607.             )
    
  608. 
    
  609.         def get_data(self, context):
    
  610.             """
    
  611.             Returns an object that will be serialized as JSON by json.dumps().
    
  612.             """
    
  613.             # Note: This is *EXTREMELY* naive; in reality, you'll need
    
  614.             # to do much more complex handling to ensure that arbitrary
    
  615.             # objects -- such as Django model instances or querysets
    
  616.             # -- can be serialized as JSON.
    
  617.             return context
    
  618. 
    
  619. .. note::
    
  620. 
    
  621.     Check out the :doc:`/topics/serialization` documentation for more
    
  622.     information on how to correctly transform Django models and querysets into
    
  623.     JSON.
    
  624. 
    
  625. This mixin provides a ``render_to_json_response()`` method with the same signature
    
  626. as :func:`~django.views.generic.base.TemplateResponseMixin.render_to_response()`.
    
  627. To use it, we need to mix it into a ``TemplateView`` for example, and override
    
  628. ``render_to_response()`` to call ``render_to_json_response()`` instead::
    
  629. 
    
  630.     from django.views.generic import TemplateView
    
  631. 
    
  632.     class JSONView(JSONResponseMixin, TemplateView):
    
  633.         def render_to_response(self, context, **response_kwargs):
    
  634.             return self.render_to_json_response(context, **response_kwargs)
    
  635. 
    
  636. Equally we could use our mixin with one of the generic views. We can make our
    
  637. own version of :class:`~django.views.generic.detail.DetailView` by mixing
    
  638. ``JSONResponseMixin`` with the
    
  639. :class:`~django.views.generic.detail.BaseDetailView` -- (the
    
  640. :class:`~django.views.generic.detail.DetailView` before template
    
  641. rendering behavior has been mixed in)::
    
  642. 
    
  643.     from django.views.generic.detail import BaseDetailView
    
  644. 
    
  645.     class JSONDetailView(JSONResponseMixin, BaseDetailView):
    
  646.         def render_to_response(self, context, **response_kwargs):
    
  647.             return self.render_to_json_response(context, **response_kwargs)
    
  648. 
    
  649. This view can then be deployed in the same way as any other
    
  650. :class:`~django.views.generic.detail.DetailView`, with exactly the
    
  651. same behavior -- except for the format of the response.
    
  652. 
    
  653. If you want to be really adventurous, you could even mix a
    
  654. :class:`~django.views.generic.detail.DetailView` subclass that is able
    
  655. to return *both* HTML and JSON content, depending on some property of
    
  656. the HTTP request, such as a query argument or an HTTP header. Mix in both the
    
  657. ``JSONResponseMixin`` and a
    
  658. :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
    
  659. and override the implementation of
    
  660. :func:`~django.views.generic.base.TemplateResponseMixin.render_to_response()`
    
  661. to defer to the appropriate rendering method depending on the type of response
    
  662. that the user requested::
    
  663. 
    
  664.     from django.views.generic.detail import SingleObjectTemplateResponseMixin
    
  665. 
    
  666.     class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
    
  667.         def render_to_response(self, context):
    
  668.             # Look for a 'format=json' GET argument
    
  669.             if self.request.GET.get('format') == 'json':
    
  670.                 return self.render_to_json_response(context)
    
  671.             else:
    
  672.                 return super().render_to_response(context)
    
  673. 
    
  674. Because of the way that Python resolves method overloading, the call to
    
  675. ``super().render_to_response(context)`` ends up calling the
    
  676. :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response()`
    
  677. implementation of :class:`~django.views.generic.base.TemplateResponseMixin`.