Fixed errors and inconsistencies in CBV topic documentation.
The code examples should now work correctly. The `get_context_data` method in the examples was changed when necessary to adopt a singular style (get context with super(...), add the extra keys to the dict then return it). Thanks to Remco Wendt for the initial report and to Tim Graham for the review.
This commit is contained in:
parent
338ec9333d
commit
bd9fbd1497
|
@ -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
|
||||||
|
@ -221,10 +228,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 explicitly set it
|
to super), any children of that class will also need to explicitly set it
|
||||||
|
@ -369,7 +376,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
|
||||||
|
@ -379,7 +386,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
|
||||||
|
|
|
@ -190,8 +190,8 @@ the foreign key relation to the model::
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
In the view, ensure that you exclude ``created_by`` in the list of fields to
|
In the view, ensure that you don't include ``created_by`` in the list of fields
|
||||||
edit, and override
|
to edit, and override
|
||||||
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
|
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
|
||||||
|
|
||||||
# views.py
|
# views.py
|
||||||
|
@ -256,3 +256,4 @@ works for AJAX requests as well as 'normal' form POSTs::
|
||||||
|
|
||||||
class AuthorCreate(AjaxableResponseMixin, CreateView):
|
class AuthorCreate(AjaxableResponseMixin, CreateView):
|
||||||
model = Author
|
model = Author
|
||||||
|
fields = ['name']
|
||||||
|
|
|
@ -208,7 +208,7 @@ A similar class-based view might look like::
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
form = self.form_class(initial=self.initial)
|
form = self.form_class(initial=self.initial)
|
||||||
return render(request, self.template_name, {'form': form})
|
return render(request, self.template_name, {'form': form})
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
form = self.form_class(request.POST)
|
form = self.form_class(request.POST)
|
||||||
|
|
|
@ -286,12 +286,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::
|
||||||
|
|
||||||
|
@ -300,7 +305,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()``.
|
||||||
|
|
||||||
|
@ -311,31 +316,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" %}
|
||||||
|
|
||||||
|
@ -427,8 +437,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.
|
||||||
|
@ -451,21 +459,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():
|
||||||
|
@ -474,10 +479,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,
|
||||||
|
@ -530,15 +533,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
|
||||||
|
@ -558,24 +558,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