[1.5.x] Fixed errors and inconsistencies in CBV topic documentation.
Backport of bd9fbd1497
from master.
This commit is contained in:
parent
519fdacf51
commit
4821a99ca4
|
@ -92,6 +92,15 @@ We'll be using these models::
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class Author(models.Model):
|
||||||
|
salutation = models.CharField(max_length=10)
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
email = models.EmailField()
|
||||||
|
headshot = models.ImageField(upload_to='author_headshots')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
class Book(models.Model):
|
class Book(models.Model):
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
authors = models.ManyToManyField('Author')
|
authors = models.ManyToManyField('Author')
|
||||||
|
@ -132,11 +141,11 @@ bit is just the lowercased version of the model's name.
|
||||||
enabled in :setting:`TEMPLATE_LOADERS`, a template location could be:
|
enabled in :setting:`TEMPLATE_LOADERS`, a template location could be:
|
||||||
/path/to/project/books/templates/books/publisher_list.html
|
/path/to/project/books/templates/books/publisher_list.html
|
||||||
|
|
||||||
.. highlightlang:: html+django
|
|
||||||
|
|
||||||
This template will be rendered against a context containing a variable called
|
This template will be rendered against a context containing a variable called
|
||||||
``object_list`` that contains all the publisher objects. A very simple template
|
``object_list`` that contains all the publisher objects. A very simple template
|
||||||
might look like the following::
|
might look like the following:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
@ -159,8 +168,6 @@ consider some of the common ways you might customize and extend generic views.
|
||||||
Making "friendly" template contexts
|
Making "friendly" template contexts
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
.. highlightlang:: python
|
|
||||||
|
|
||||||
You might have noticed that our sample publisher list template stores all the
|
You might have noticed that our sample publisher list template stores all the
|
||||||
publishers in a variable named ``object_list``. While this works just fine, it
|
publishers in a variable named ``object_list``. While this works just fine, it
|
||||||
isn't all that "friendly" to template authors: they have to "just know" that
|
isn't all that "friendly" to template authors: they have to "just know" that
|
||||||
|
@ -219,10 +226,10 @@ template, but you can override it to send more::
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Generally, get_context_data will merge the context data of all parent
|
Generally, ``get_context_data`` will merge the context data of all parent
|
||||||
classes with those of the current class. To preserve this behavior in your
|
classes with those of the current class. To preserve this behavior in your
|
||||||
own classes where you want to alter the context, you should be sure to call
|
own classes where you want to alter the context, you should be sure to call
|
||||||
get_context_data on the super class. When no two classes try to define the
|
``get_context_data`` on the super class. When no two classes try to define the
|
||||||
same key, this will give the expected results. However if any class
|
same key, this will give the expected results. However if any class
|
||||||
attempts to override a key after parent classes have set it (after the call
|
attempts to override a key after parent classes have set it (after the call
|
||||||
to super), any children of that class will also need to explictly set it
|
to super), any children of that class will also need to explictly set it
|
||||||
|
@ -366,7 +373,7 @@ Performing extra work
|
||||||
The last common pattern we'll look at involves doing some extra work before
|
The last common pattern we'll look at involves doing some extra work before
|
||||||
or after calling the generic view.
|
or after calling the generic view.
|
||||||
|
|
||||||
Imagine we had a ``last_accessed`` field on our ``Author`` object that we were
|
Imagine we had a ``last_accessed`` field on our ``Author`` model that we were
|
||||||
using to keep track of the last time anybody looked at that author::
|
using to keep track of the last time anybody looked at that author::
|
||||||
|
|
||||||
# models.py
|
# models.py
|
||||||
|
@ -375,7 +382,7 @@ using to keep track of the last time anybody looked at that author::
|
||||||
salutation = models.CharField(max_length=10)
|
salutation = models.CharField(max_length=10)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
headshot = models.ImageField(upload_to='/tmp')
|
headshot = models.ImageField(upload_to='author_headshots')
|
||||||
last_accessed = models.DateTimeField()
|
last_accessed = models.DateTimeField()
|
||||||
|
|
||||||
The generic ``DetailView`` class, of course, wouldn't know anything about this
|
The generic ``DetailView`` class, of course, wouldn't know anything about this
|
||||||
|
|
|
@ -285,12 +285,17 @@ One way to do this is to combine :class:`ListView` with
|
||||||
for the paginated list of books can hang off the publisher found as the single
|
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:
|
object. In order to do this, we need to have two different querysets:
|
||||||
|
|
||||||
**Publisher queryset for use in get_object**
|
**``Publisher`` queryset for use in ``get_object``**
|
||||||
We'll set that up directly when we call ``get_object()``.
|
We'll set the ``model`` attribute on the view and rely on the default
|
||||||
|
implementation of ``get_object()`` to fetch the correct ``Publisher``
|
||||||
|
object.
|
||||||
|
|
||||||
**Book queryset for use by ListView**
|
**``Book`` queryset for use by ``ListView``**
|
||||||
We'll figure that out ourselves in ``get_queryset()`` so we
|
The default implementation of ``get_queryset`` uses the ``model`` attribute
|
||||||
can take into account the ``Publisher`` we're looking at.
|
to construct the queryset. This conflicts with our use of this attribute
|
||||||
|
for ``get_object`` so we'll override that method and have it return
|
||||||
|
the queryset of ``Book`` objects linked to the ``Publisher`` we're looking
|
||||||
|
at.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -299,7 +304,7 @@ object. In order to do this, we need to have two different querysets:
|
||||||
:class:`ListView` will
|
:class:`ListView` will
|
||||||
put things in the context data under the value of
|
put things in the context data under the value of
|
||||||
``context_object_name`` if it's set, we'll instead explictly
|
``context_object_name`` if it's set, we'll instead explictly
|
||||||
ensure the Publisher is in the context data. :class:`ListView`
|
ensure the ``Publisher`` is in the context data. :class:`ListView`
|
||||||
will add in the suitable ``page_obj`` and ``paginator`` for us
|
will add in the suitable ``page_obj`` and ``paginator`` for us
|
||||||
providing we remember to call ``super()``.
|
providing we remember to call ``super()``.
|
||||||
|
|
||||||
|
@ -310,31 +315,36 @@ Now we can write a new ``PublisherDetail``::
|
||||||
from books.models import Publisher
|
from books.models import Publisher
|
||||||
|
|
||||||
class PublisherDetail(SingleObjectMixin, ListView):
|
class PublisherDetail(SingleObjectMixin, ListView):
|
||||||
|
model = Publisher # for SingleObjectMixin.get_object
|
||||||
paginate_by = 2
|
paginate_by = 2
|
||||||
template_name = "books/publisher_detail.html"
|
template_name = "books/publisher_detail.html"
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
return super(PublisherDetail, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['publisher'] = self.object
|
context = super(PublisherDetail, self).get_context_data(**kwargs)
|
||||||
return super(PublisherDetail, self).get_context_data(**kwargs)
|
context['publisher'] = self.object
|
||||||
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.object = self.get_object(Publisher.objects.all())
|
|
||||||
return self.object.book_set.all()
|
return self.object.book_set.all()
|
||||||
|
|
||||||
Notice how we set ``self.object`` within ``get_queryset()`` so we
|
Notice how we set ``self.object`` within ``get()`` so we
|
||||||
can use it again later in ``get_context_data()``. If you don't set
|
can use it again later in ``get_context_data()`` and ``get_queryset()``.
|
||||||
``template_name``, the template will default to the normal
|
If you don't set ``template_name``, the template will default to the normal
|
||||||
:class:`ListView` choice, which in this case would be
|
:class:`ListView` choice, which in this case would be
|
||||||
``"books/book_list.html"`` because it's a list of books;
|
``"books/book_list.html"`` because it's a list of books;
|
||||||
:class:`ListView` knows nothing about
|
:class:`ListView` knows nothing about
|
||||||
:class:`~django.views.generic.detail.SingleObjectMixin`, so it doesn't have
|
:class:`~django.views.generic.detail.SingleObjectMixin`, so it doesn't have
|
||||||
any clue this view is anything to do with a Publisher.
|
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
|
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
|
have to create lots of books to see the pagination working! Here's the
|
||||||
template you'd want to use::
|
template you'd want to use:
|
||||||
|
|
||||||
|
.. code-block: html+django
|
||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
@ -426,8 +436,6 @@ code so that on ``POST`` the form gets called appropriately.
|
||||||
both of the views implement ``get()``, and things would get much more
|
both of the views implement ``get()``, and things would get much more
|
||||||
confusing.
|
confusing.
|
||||||
|
|
||||||
.. highlightlang:: python
|
|
||||||
|
|
||||||
Our new ``AuthorDetail`` looks like this::
|
Our new ``AuthorDetail`` looks like this::
|
||||||
|
|
||||||
# CAUTION: you almost certainly do not want to do this.
|
# CAUTION: you almost certainly do not want to do this.
|
||||||
|
@ -449,21 +457,18 @@ Our new ``AuthorDetail`` looks like this::
|
||||||
form_class = AuthorInterestForm
|
form_class = AuthorInterestForm
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse(
|
return reverse('author-detail', kwargs={'pk': self.object.pk})
|
||||||
'author-detail',
|
|
||||||
kwargs = {'pk': self.object.pk},
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(AuthorDetail, self).get_context_data(**kwargs)
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
form = self.get_form(form_class)
|
context['form'] = self.get_form(form_class)
|
||||||
context = {
|
return context
|
||||||
'form': form
|
|
||||||
}
|
|
||||||
context.update(kwargs)
|
|
||||||
return super(AuthorDetail, self).get_context_data(**context)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
self.object = self.get_object()
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
form = self.get_form(form_class)
|
form = self.get_form(form_class)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
@ -472,10 +477,8 @@ Our new ``AuthorDetail`` looks like this::
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if not self.request.user.is_authenticated():
|
# Here, we would record the user's interest using the message
|
||||||
return HttpResponseForbidden()
|
# passed in form.cleaned_data['message']
|
||||||
self.object = self.get_object()
|
|
||||||
# record the interest using the message in form.cleaned_data
|
|
||||||
return super(AuthorDetail, self).form_valid(form)
|
return super(AuthorDetail, self).form_valid(form)
|
||||||
|
|
||||||
``get_success_url()`` is just providing somewhere to redirect to,
|
``get_success_url()`` is just providing somewhere to redirect to,
|
||||||
|
@ -528,15 +531,12 @@ write our own ``get_context_data()`` to make the
|
||||||
message = forms.CharField()
|
message = forms.CharField()
|
||||||
|
|
||||||
class AuthorDisplay(DetailView):
|
class AuthorDisplay(DetailView):
|
||||||
|
model = Author
|
||||||
queryset = Author.objects.all()
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = super(AuthorDisplay, self).get_context_data(**kwargs)
|
||||||
'form': AuthorInterestForm(),
|
context['form'] = AuthorInterestForm()
|
||||||
}
|
return context
|
||||||
context.update(kwargs)
|
|
||||||
return super(AuthorDisplay, self).get_context_data(**context)
|
|
||||||
|
|
||||||
Then the ``AuthorInterest`` is a simple :class:`FormView`, but we
|
Then the ``AuthorInterest`` is a simple :class:`FormView`, but we
|
||||||
have to bring in :class:`~django.views.generic.detail.SingleObjectMixin` so we
|
have to bring in :class:`~django.views.generic.detail.SingleObjectMixin` so we
|
||||||
|
@ -554,24 +554,14 @@ template as ``AuthorDisplay`` is using on ``GET``.
|
||||||
form_class = AuthorInterestForm
|
form_class = AuthorInterestForm
|
||||||
model = Author
|
model = Author
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
context = {
|
if not request.user.is_authenticated():
|
||||||
'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()
|
return HttpResponseForbidden()
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
# record the interest using the message in form.cleaned_data
|
return super(AuthorInterest, self).post(request, *args, **kwargs)
|
||||||
return super(AuthorInterest, self).form_valid(form)
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('author-detail', kwargs={'pk': self.object.pk})
|
||||||
|
|
||||||
Finally we bring this together in a new ``AuthorDetail`` view. We
|
Finally we bring this together in a new ``AuthorDetail`` view. We
|
||||||
already know that calling :meth:`~django.views.generic.base.View.as_view()` on
|
already know that calling :meth:`~django.views.generic.base.View.as_view()` on
|
||||||
|
|
Loading…
Reference in New Issue