2
0

mixins.txt 29 KB

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