mixins.txt 28 KB

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