mirror of
https://github.com/django/django.git
synced 2024-11-29 22:56:46 +01:00
c4c7fbcc0d
Many thanks to Daniel Greenfeld, James Aylett, Marc Tamlyn, Simon Williams, Danilo Bargen and Luke Plant for their work on this.
606 lines
25 KiB
Plaintext
606 lines
25 KiB
Plaintext
===================================
|
|
Using mixins with class-based views
|
|
===================================
|
|
|
|
.. versionadded:: 1.3
|
|
|
|
.. caution::
|
|
|
|
This is an advanced topic. A working knowledge of :doc:`Django's
|
|
class-based views<index>` is advised before exploring these
|
|
techniques.
|
|
|
|
Django's built-in class-based views provide a lot of functionality,
|
|
but some of it you may want to use separately. For instance, you may
|
|
want to write a view that renders a template to make the HTTP
|
|
response, but you can't use
|
|
:class:`~django.views.generic.base.TemplateView`; perhaps you need to
|
|
render a template only on `POST`, with `GET` doing something else
|
|
entirely. While you could use
|
|
:class:`~django.template.response.TemplateResponse` directly, this
|
|
will likely result in duplicate code.
|
|
|
|
For this reason, Django also provides a number of mixins that provide
|
|
more discrete functionality. Template rendering, for instance, is
|
|
encapsulated in the
|
|
:class:`~django.views.generic.base.TemplateResponseMixin`. The Django
|
|
reference documentation contains :doc:`full documentation of all the
|
|
mixins</ref/class-based-views/mixins>`.
|
|
|
|
Context and template responses
|
|
==============================
|
|
|
|
Two central mixins are provided that help in providing a consistent
|
|
interface to working with templates in class-based views.
|
|
|
|
:class:`~django.views.generic.base.TemplateResponseMixin`
|
|
Every built in view which returns a
|
|
:class:`~django.template.response.TemplateResponse` will call the
|
|
:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
|
|
method that :class:`TemplateResponseMixin` provides. Most of the time this
|
|
will be called for you (for instance, it is called by the ``get()`` method
|
|
implemented by both :class:`~django.views.generic.base.TemplateView` and
|
|
:class:`~django.views.generic.base.DetailView`); similarly, it's unlikely
|
|
that you'll need to override it, although if you want your response to
|
|
return something not rendered via a Django template then you'll want to do
|
|
it. For an example of this, see the :ref:`JSONResponseMixin example
|
|
<jsonresponsemixin-example>`.
|
|
|
|
``render_to_response`` itself calls
|
|
:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`,
|
|
which by default will just look up
|
|
:attr:`~django.views.generic.base.TemplateResponseMixin.template_name` on
|
|
the class-based view; two other mixins
|
|
(:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
|
|
and
|
|
:class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`)
|
|
override this to provide more flexible defaults when dealing with actual
|
|
objects.
|
|
|
|
.. versionadded:: 1.5
|
|
|
|
:class:`~django.views.generic.base.ContextMixin`
|
|
Every built in view which needs context data, such as for rendering a
|
|
template (including :class:`TemplateResponseMixin` above), should call
|
|
:meth:`~django.views.generic.base.ContextMixin.get_context_data` passing
|
|
any data they want to ensure is in there as keyword arguments.
|
|
``get_context_data`` returns a dictionary; in :class:`ContextMixin` it
|
|
simply returns its keyword arguments, but it is common to override this to
|
|
add more members to the dictionary.
|
|
|
|
Building up Django's generic class-based views
|
|
===============================================
|
|
|
|
Let's look at how two of Django's generic class-based views are built
|
|
out of mixins providing discrete functionality. We'll consider
|
|
:class:`~django.views.generic.detail.DetailView`, which renders a
|
|
"detail" view of an object, and
|
|
:class:`~django.views.generic.list.ListView`, which will render a list
|
|
of objects, typically from a queryset, and optionally paginate
|
|
them. This will introduce us to four mixins which between them provide
|
|
useful functionality when working with either a single Django object,
|
|
or multiple objects.
|
|
|
|
There are also mixins involved in the generic edit views
|
|
(:class:`~django.views.generic.edit.FormView`, and the model-specific
|
|
views :class:`~django.views.generic.edit.CreateView`,
|
|
:class:`~django.views.generic.edit.UpdateView` and
|
|
:class:`~django.views.generic.edit.DeleteView`), and in the
|
|
date-based generic views. These are
|
|
covered in the :doc:`mixin reference
|
|
documentation</ref/class-based-views/mixins>`.
|
|
|
|
DetailView: working with a single Django object
|
|
-----------------------------------------------
|
|
|
|
To show the detail of an object, we basically need to do two things:
|
|
we need to look up the object and then we need to make a
|
|
:class:`TemplateResponse` with a suitable template, and that object as
|
|
context.
|
|
|
|
To get the object, :class:`~django.views.generic.detail.DetailView`
|
|
relies on :class:`~django.views.generic.detail.SingleObjectMixin`,
|
|
which provides a
|
|
:meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
|
|
method that figures out the object based on the URL of the request (it
|
|
looks for ``pk`` and ``slug`` keyword arguments as declared in the
|
|
URLConf, and looks the object up either from the
|
|
:attr:`~django.views.generic.detail.SingleObjectMixin.model` attribute
|
|
on the view, or the
|
|
:attr:`~django.views.generic.detail.SingleObjectMixin.queryset`
|
|
attribute if that's provided). :class:`SingleObjectMixin` also overrides
|
|
:meth:`~django.views.generic.base.ContextMixin.get_context_data`,
|
|
which is used across all Django's built in class-based views to supply
|
|
context data for template renders.
|
|
|
|
To then make a :class:`TemplateResponse`, :class:`DetailView` uses
|
|
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
|
|
which extends
|
|
:class:`~django.views.generic.base.TemplateResponseMixin`, overriding
|
|
:meth:`get_template_names()` as discussed above. It actually provides
|
|
a fairly sophisticated set of options, but the main one that most
|
|
people are going to use is
|
|
``<app_label>/<object_name>_detail.html``. The ``_detail`` part can be
|
|
changed by setting
|
|
:attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
|
|
on a subclass to something else. (For instance, the :doc:`generic edit
|
|
views<generic-editing>` use ``_form`` for create and update views, and
|
|
``_confirm_delete`` for delete views.)
|
|
|
|
ListView: working with many Django objects
|
|
------------------------------------------
|
|
|
|
Lists of objects follow roughly the same pattern: we need a (possibly
|
|
paginated) list of objects, typically a :class:`QuerySet`, and then we need
|
|
to make a :class:`TemplateResponse` with a suitable template using
|
|
that list of objects.
|
|
|
|
To get the objects, :class:`~django.views.generic.list.ListView` uses
|
|
:class:`~django.views.generic.list.MultipleObjectMixin`, which
|
|
provides both
|
|
:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`
|
|
and
|
|
:meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`. Unlike
|
|
with :class:`SingleObjectMixin`, there's no need to key off parts of
|
|
the URL to figure out the queryset to work with, so the default just
|
|
uses the
|
|
:attr:`~django.views.generic.list.MultipleObjectMixin.queryset` or
|
|
:attr:`~django.views.generic.list.MultipleObjectMixin.model` attribute
|
|
on the view class. A common reason to override
|
|
:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`
|
|
here would be to dynamically vary the objects, such as depending on
|
|
the current user or to exclude posts in the future for a blog.
|
|
|
|
:class:`MultipleObjectMixin` also overrides
|
|
:meth:`~django.views.generic.base.ContextMixin.get_context_data` to
|
|
include appropriate context variables for pagination (providing
|
|
dummies if pagination is disabled). It relies on ``object_list`` being
|
|
passed in as a keyword argument, which :class:`ListView` arranges for
|
|
it.
|
|
|
|
To make a :class:`TemplateResponse`, :class:`ListView` then uses
|
|
:class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`;
|
|
as with :class:`SingleObjectTemplateResponseMixin` above, this
|
|
overrides :meth:`get_template_names()` to provide :meth:`a range of
|
|
options
|
|
<~django.views.generic.list.MultipleObjectTempalteResponseMixin>`,
|
|
with the most commonly-used being
|
|
``<app_label>/<object_name>_list.html``, with the ``_list`` part again
|
|
being taken from the
|
|
:attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
|
|
attribute. (The date based generic views use suffixes such as ``_archive``,
|
|
``_archive_year`` and so on to use different templates for the various
|
|
specialised date-based list views.)
|
|
|
|
Using Django's class-based view mixins
|
|
======================================
|
|
|
|
Now we've seen how Django's generic class-based views use the provided
|
|
mixins, let's look at other ways we can combine them. Of course we're
|
|
still going to be combining them with either built-in class-based
|
|
views, or other generic class-based views, but there are a range of
|
|
rarer problems you can solve than are provided for by Django out of
|
|
the box.
|
|
|
|
.. warning::
|
|
|
|
Not all mixins can be used together, and not all generic class
|
|
based views can be used with all other mixins. Here we present a
|
|
few examples that do work; if you want to bring together other
|
|
functionality then you'll have to consider interactions between
|
|
attributes and methods that overlap between the different classes
|
|
you're using, and how `method resolution order`_ will affect which
|
|
versions of the methods will be called in what order.
|
|
|
|
The reference documentation for Django's :doc:`class-based
|
|
views</ref/class-based-views/index>` and :doc:`class-based view
|
|
mixins</ref/class-based-views/mixins>` will help you in
|
|
understanding which attributes and methods are likely to cause
|
|
conflict between different classes and mixins.
|
|
|
|
If in doubt, it's often better to back off and base your work on
|
|
:class:`View` or :class:`TemplateView`, perhaps with
|
|
:class:`SimpleObjectMixin` and
|
|
:class:`MultipleObjectMixin`. Although you will probably end up
|
|
writing more code, it is more likely to be clearly understandable
|
|
to someone else coming to it later, and with fewer interactions to
|
|
worry about you will save yourself some thinking. (Of course, you
|
|
can always dip into Django's implementation of the generic class
|
|
based views for inspiration on how to tackle problems.)
|
|
|
|
.. _method resolution order: http://www.python.org/download/releases/2.3/mro/
|
|
|
|
|
|
Using SingleObjectMixin with View
|
|
---------------------------------
|
|
|
|
If we want to write a simple class-based view that responds only to
|
|
``POST``, we'll subclass :class:`~django.views.generic.base.View` and
|
|
write a ``post()`` method in the subclass. However if we want our
|
|
processing to work on a particular object, identified from the URL,
|
|
we'll want the functionality provided by
|
|
:class:`~django.views.generic.detail.SingleObjectMixin`.
|
|
|
|
We'll demonstrate this with the publisher modelling we used in the
|
|
:doc:`generic class-based views
|
|
introduction<generic-display>`.
|
|
|
|
.. code-block:: python
|
|
|
|
# views.py
|
|
from django.http import HttpResponseForbidden, HttpResponseRedirect
|
|
from django.core.urlresolvers import reverse
|
|
from django.views.generic import View
|
|
from django.views.generic.detail import SingleObjectMixin
|
|
from books.models import Author
|
|
|
|
class RecordInterest(View, SingleObjectMixin):
|
|
"""Records the current user's interest in an author."""
|
|
model = Author
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if not request.user.is_authenticated():
|
|
return HttpResponseForbidden()
|
|
|
|
# Look up the author we're interested in.
|
|
self.object = self.get_object()
|
|
# Actually record interest somehow here!
|
|
|
|
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
|
|
|
|
In practice you'd probably want to record the interest in a key-value
|
|
store rather than in a relational database, so we've left that bit
|
|
out. The only bit of the view that needs to worry about using
|
|
:class:`SingleObjectMixin` is where we want to look up the author
|
|
we're interested in, which it just does with a simple call to
|
|
``self.get_object()``. Everything else is taken care of for us by the
|
|
mixin.
|
|
|
|
We can hook this into our URLs easily enough:
|
|
|
|
.. code-block:: python
|
|
|
|
# urls.py
|
|
from books.views import RecordInterest
|
|
|
|
urlpatterns = patterns('',
|
|
#...
|
|
url(r'^author/(?P<pk>\d+)/interest/$', RecordInterest.as_view(), name='author-interest'),
|
|
)
|
|
|
|
Note the ``pk`` named group, which
|
|
:meth:`~django.views.generic.detail.SingleObjectMixin.get_object` uses
|
|
to look up the :class:`Author` instance. You could also use a slug, or
|
|
any of the other features of :class:`SingleObjectMixin`.
|
|
|
|
Using SingleObjectMixin with ListView
|
|
-------------------------------------
|
|
|
|
:class:`~django.views.generic.list.ListView` provides built-in
|
|
pagination, but you might want to paginate a list of objects that are
|
|
all linked (by a foreign key) to another object. In our publishing
|
|
example, you might want to paginate through all the books by a
|
|
particular publisher.
|
|
|
|
One way to do this is to combine :class:`ListView` with
|
|
:class:`SingleObjectMixin`, so that the queryset for the paginated
|
|
list of books can hang off the publisher found as the single
|
|
object. In order to do this, we need to have two different querysets:
|
|
|
|
**Publisher queryset for use in get_object**
|
|
We'll set that up directly when we call :meth:`get_object()`.
|
|
|
|
**Book queryset for use by ListView**
|
|
We'll figure that out ourselves in :meth:`get_queryset()` so we
|
|
can take into account the Publisher we're looking at.
|
|
|
|
.. highlightlang:: python
|
|
|
|
.. note::
|
|
|
|
We have to think carefully about :meth:`get_context_data()`.
|
|
Since both :class:`SingleObjectMixin` and :class:`ListView` will
|
|
put things in the context data under the value of
|
|
:attr:`context_object_name` if it's set, we'll instead explictly
|
|
ensure the Publisher is in the context data. :class:`ListView`
|
|
will add in the suitable ``page_obj`` and ``paginator`` for us
|
|
providing we remember to call ``super()``.
|
|
|
|
Now we can write a new :class:`PublisherDetail`::
|
|
|
|
from django.views.generic import ListView
|
|
from django.views.generic.detail import SingleObjectMixin
|
|
from books.models import Publisher
|
|
|
|
class PublisherDetail(SingleObjectMixin, ListView):
|
|
paginate_by = 2
|
|
template_name = "books/publisher_detail.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs['publisher'] = self.object
|
|
return super(PublisherDetail, self).get_context_data(**kwargs)
|
|
|
|
def get_queryset(self):
|
|
self.object = self.get_object(Publisher.objects.all())
|
|
return self.object.book_set.all()
|
|
|
|
Notice how we set ``self.object`` within :meth:`get_queryset` so we
|
|
can use it again later in :meth:`get_context_data`. If you don't set
|
|
:attr:`template_name`, the template will default to the normal
|
|
:class:`ListView` choice, which in this case would be
|
|
``"books/book_list.html"`` because it's a list of books;
|
|
:class:`ListView` knows nothing about :class:`SingleObjectMixin`, so
|
|
it doesn't have any clue this view is anything to do with a Publisher.
|
|
|
|
.. highlightlang:: html+django
|
|
|
|
The ``paginate_by`` is deliberately small in the example so you don't
|
|
have to create lots of books to see the pagination working! Here's the
|
|
template you'd want to use::
|
|
|
|
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<h2>Publisher {{ publisher.name }}</h2>
|
|
|
|
<ol>
|
|
{% for book in page_obj %}
|
|
<li>{{ book.title }}</li>
|
|
{% endfor %}
|
|
</ol>
|
|
|
|
<div class="pagination">
|
|
<span class="step-links">
|
|
{% if page_obj.has_previous %}
|
|
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
|
{% endif %}
|
|
|
|
<span class="current">
|
|
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
|
|
</span>
|
|
|
|
{% if page_obj.has_next %}
|
|
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
Avoid anything more complex
|
|
===========================
|
|
|
|
Generally you can use
|
|
:class:`~django.views.generic.base.TemplateResponseMixin` and
|
|
:class:`~django.views.generic.detail.SingleObjectMixin` when you need
|
|
their functionality. As shown above, with a bit of care you can even
|
|
combine :class:`SingleObjectMixin` with
|
|
:class:`~django.views.generic.list.ListView`. However things get
|
|
increasingly complex as you try to do so, and a good rule of thumb is:
|
|
|
|
.. hint::
|
|
|
|
Each of your views should use only mixins or views from one of the
|
|
groups of generic class-based views: :doc:`detail,
|
|
list<generic-display>`, :doc:`editing<generic-editing>` and
|
|
date. For example it's fine to combine
|
|
:class:`TemplateView` (built in view) with
|
|
:class:`MultipleObjectMixin` (generic list), but you're likely to
|
|
have problems combining :class:`SingleObjectMixin` (generic
|
|
detail) with :class:`MultipleObjectMixin` (generic list).
|
|
|
|
To show what happens when you try to get more sophisticated, we show
|
|
an example that sacrifices readability and maintainability when there
|
|
is a simpler solution. First, let's look at a naive attempt to combine
|
|
:class:`~django.views.generic.detail.DetailView` with
|
|
:class:`~django.views.generic.edit.FormMixin` to enable use to
|
|
``POST`` a Django :class:`Form` to the same URL as we're displaying an
|
|
object using :class:`DetailView`.
|
|
|
|
Using FormMixin with DetailView
|
|
-------------------------------
|
|
|
|
Think back to our earlier example of using :class:`View` and
|
|
:class:`SingleObjectMixin` together. We were recording a user's
|
|
interest in a particular author; say now that we want to let them
|
|
leave a message saying why they like them. Again, let's assume we're
|
|
not going to store this in a relational database but instead in
|
|
something more esoteric that we won't worry about here.
|
|
|
|
At this point it's natural to reach for a :class:`Form` to encapsulate
|
|
the information sent from the user's browser to Django. Say also that
|
|
we're heavily invested in `REST`_, so we want to use the same URL for
|
|
displaying the author as for capturing the message from the
|
|
user. Let's rewrite our :class:`AuthorDetailView` to do that.
|
|
|
|
.. _REST: http://en.wikipedia.org/wiki/Representational_state_transfer
|
|
|
|
We'll keep the ``GET`` handling from :class:`DetailView`, although
|
|
we'll have to add a :class:`Form` into the context data so we can
|
|
render it in the template. We'll also want to pull in form processing
|
|
from :class:`~django.views.generic.edit.FormMixin`, and write a bit of
|
|
code so that on ``POST`` the form gets called appropriately.
|
|
|
|
.. note::
|
|
|
|
We use :class:`FormMixin` and implement :meth:`post()` ourselves
|
|
rather than try to mix :class:`DetailView` with :class:`FormView`
|
|
(which provides a suitable :meth:`post()` already) because both of
|
|
the views implement :meth:`get()`, and things would get much more
|
|
confusing.
|
|
|
|
Our new :class:`AuthorDetail` looks like this:
|
|
|
|
.. code-block:: python
|
|
|
|
# CAUTION: you almost certainly do not want to do this.
|
|
# It is provided as part of a discussion of problems you can
|
|
# run into when combining different generic class-based view
|
|
# functionality that is not designed to be used together.
|
|
|
|
from django import forms
|
|
from django.http import HttpResponseForbidden
|
|
from django.core.urlresolvers import reverse
|
|
from django.views.generic import DetailView
|
|
from django.views.generic.edit import FormMixin
|
|
|
|
class AuthorInterestForm(forms.Form):
|
|
message = forms.CharField()
|
|
|
|
class AuthorDetail(DetailView, FormMixin):
|
|
model = Author
|
|
form_class = AuthorInterestForm
|
|
|
|
def get_success_url(self):
|
|
return reverse(
|
|
'author-detail',
|
|
kwargs = {'pk': self.object.pk},
|
|
)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
form_class = self.get_form_class()
|
|
form = self.get_form(form_class)
|
|
context = {
|
|
'form': form
|
|
}
|
|
context.update(kwargs)
|
|
return super(AuthorDetail, self).get_context_data(**context)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
form_class = self.get_form_class()
|
|
form = self.get_form(form_class)
|
|
if form.is_valid():
|
|
return self.form_valid(form)
|
|
else:
|
|
return self.form_invalid(form)
|
|
|
|
def form_valid(self, form):
|
|
if not self.request.user.is_authenticated():
|
|
return HttpResponseForbidden()
|
|
self.object = self.get_object()
|
|
# record the interest using the message in form.cleaned_data
|
|
return super(AuthorDetail, self).form_valid(form)
|
|
|
|
:meth:`get_success_url()` is just providing somewhere to redirect to,
|
|
which gets used in the default implementation of
|
|
:meth:`form_valid()`. We have to provide our own :meth:`post()` as
|
|
noted earlier, and override :meth:`get_context_data()` to make the
|
|
:class:`Form` available in the context data.
|
|
|
|
A better solution
|
|
-----------------
|
|
|
|
It should be obvious that the number of subtle interactions between
|
|
:class:`FormMixin` and :class:`DetailView` is already testing our
|
|
ability to manage things. It's unlikely you'd want to write this kind
|
|
of class yourself.
|
|
|
|
In this case, it would be fairly easy to just write the :meth:`post()`
|
|
method yourself, keeping :class:`DetailView` as the only generic
|
|
functionality, although writing :class:`Form` handling code involves a
|
|
lot of duplication.
|
|
|
|
Alternatively, it would still be easier than the above approach to
|
|
have a separate view for processing the form, which could use
|
|
:class:`~django.views.generic.edit.FormView` distinct from
|
|
:class:`DetailView` without concerns.
|
|
|
|
An alternative better solution
|
|
------------------------------
|
|
|
|
What we're really trying to do here is to use two different class
|
|
based views from the same URL. So why not do just that? We have a very
|
|
clear division here: ``GET`` requests should get the
|
|
:class:`DetailView` (with the :class:`Form` added to the context
|
|
data), and ``POST`` requests should get the :class:`FormView`. Let's
|
|
set up those views first.
|
|
|
|
The :class:`AuthorDisplay` view is almost the same as :ref:`when we
|
|
first introduced AuthorDetail<generic-views-extra-work>`; we have to
|
|
write our own :meth:`get_context_data()` to make the
|
|
:class:`AuthorInterestForm` available to the template. We'll skip the
|
|
:meth:`get_object()` override from before for clarity.
|
|
|
|
.. code-block:: python
|
|
|
|
from django.views.generic import DetailView
|
|
from django import forms
|
|
from books.models import Author
|
|
|
|
class AuthorInterestForm(forms.Form):
|
|
message = forms.CharField()
|
|
|
|
class AuthorDisplay(DetailView):
|
|
|
|
queryset = Author.objects.all()
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = {
|
|
'form': AuthorInterestForm(),
|
|
}
|
|
context.update(kwargs)
|
|
return super(AuthorDisplay, self).get_context_data(**context)
|
|
|
|
Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
|
|
have to bring in :class:`SingleObjectMixin` so we can find the author
|
|
we're talking about, and we have to remember to set
|
|
:attr:`template_name` to ensure that form errors will render the same
|
|
template as :class:`AuthorDisplay` is using on ``GET``.
|
|
|
|
.. code-block:: python
|
|
|
|
from django.views.generic import FormView
|
|
from django.views.generic.detail import SingleObjectMixin
|
|
|
|
class AuthorInterest(FormView, SingleObjectMixin):
|
|
template_name = 'books/author_detail.html'
|
|
form_class = AuthorInterestForm
|
|
model = Author
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = {
|
|
'object': self.get_object(),
|
|
}
|
|
return super(AuthorInterest, self).get_context_data(**context)
|
|
|
|
def get_success_url(self):
|
|
return reverse(
|
|
'author-detail',
|
|
kwargs = {'pk': self.object.pk},
|
|
)
|
|
|
|
def form_valid(self, form):
|
|
if not self.request.user.is_authenticated():
|
|
return HttpResponseForbidden()
|
|
self.object = self.get_object()
|
|
# record the interest using the message in form.cleaned_data
|
|
return super(AuthorInterest, self).form_valid(form)
|
|
|
|
Finally we bring this together in a new :class:`AuthorDetail` view. We
|
|
already know that calling :meth:`as_view()` on a class-based view
|
|
gives us something that behaves exactly like a function based view, so
|
|
we can do that at the point we choose between the two subviews.
|
|
|
|
You can of course pass through keyword arguments to :meth:`as_view()`
|
|
in the same way you would in your URLconf, such as if you wanted the
|
|
:class:`AuthorInterest` behaviour to also appear at another URL but
|
|
using a different template.
|
|
|
|
.. code-block:: python
|
|
|
|
from django.views.generic import View
|
|
|
|
class AuthorDetail(View):
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
view = AuthorDisplay.as_view()
|
|
return view(request, *args, **kwargs)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
view = AuthorInterest.as_view()
|
|
return view(request, *args, **kwargs)
|
|
|
|
This approach can also be used with any other generic class-based
|
|
views or your own class-based views inheriting directly from
|
|
:class:`View` or :class:`TemplateView`, as it keeps the different
|
|
views as separate as possible.
|