Fixed #20249 - Removed a "feature" in the tutorial that doesn't actually work.

Thanks bmispelon for the report and draft patch.
This commit is contained in:
Tim Graham 2013-05-11 19:08:57 -04:00
parent a6edde3260
commit 679a2ac843
2 changed files with 77 additions and 70 deletions

View File

@ -185,7 +185,7 @@ conversion. We will:
2. Delete some of the old, unneeded views. 2. Delete some of the old, unneeded views.
3. Fix up URL handling for the new views. 3. Introduce new views based on Django's generic views.
Read on for details. Read on for details.
@ -205,32 +205,51 @@ Amend URLconf
First, open the ``polls/urls.py`` URLconf and change it like so:: First, open the ``polls/urls.py`` URLconf and change it like so::
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.views.generic import DetailView, ListView
from polls.models import Poll from polls import views
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', url(r'^$', views.IndexView.as_view(), name='index'),
ListView.as_view( url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
queryset=Poll.objects.order_by('-pub_date')[:5], url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'),
context_object_name='latest_poll_list', url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
template_name='polls/index.html'),
name='index'),
url(r'^(?P<pk>\d+)/$',
DetailView.as_view(
model=Poll,
template_name='polls/detail.html'),
name='detail'),
url(r'^(?P<pk>\d+)/results/$',
DetailView.as_view(
model=Poll,
template_name='polls/results.html'),
name='results'),
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote', name='vote'),
) )
Amend views Amend views
----------- -----------
Next, we're going to remove our old ``index``, ``detail``, and ``results``
views and use Django's generic views instead. To do so, open the
``polls/views.py`` file and change it like so::
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from polls.models import Choice, Poll
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_poll_list'
def get_queryset(self):
"""Return the last five published polls."""
return Poll.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Poll
template_name = 'polls/detail.html'
class ResultsView(generic.DetailView):
model = Poll
template_name = 'polls/results.html'
def vote(request, poll_id):
....
We're using two generic views here: We're using two generic views here:
:class:`~django.views.generic.list.ListView` and :class:`~django.views.generic.list.ListView` and
:class:`~django.views.generic.detail.DetailView`. Respectively, those :class:`~django.views.generic.detail.DetailView`. Respectively, those
@ -238,7 +257,7 @@ two views abstract the concepts of "display a list of objects" and
"display a detail page for a particular type of object." "display a detail page for a particular type of object."
* Each generic view needs to know what model it will be acting * Each generic view needs to know what model it will be acting
upon. This is provided using the ``model`` parameter. upon. This is provided using the ``model`` attribute.
* The :class:`~django.views.generic.detail.DetailView` generic view * The :class:`~django.views.generic.detail.DetailView` generic view
expects the primary key value captured from the URL to be called expects the primary key value captured from the URL to be called
@ -248,7 +267,7 @@ two views abstract the concepts of "display a list of objects" and
By default, the :class:`~django.views.generic.detail.DetailView` generic By default, the :class:`~django.views.generic.detail.DetailView` generic
view uses a template called ``<app name>/<model name>_detail.html``. view uses a template called ``<app name>/<model name>_detail.html``.
In our case, it'll use the template ``"polls/poll_detail.html"``. The In our case, it'll use the template ``"polls/poll_detail.html"``. The
``template_name`` argument is used to tell Django to use a specific ``template_name`` attribute is used to tell Django to use a specific
template name instead of the autogenerated default template name. We template name instead of the autogenerated default template name. We
also specify the ``template_name`` for the ``results`` list view -- also specify the ``template_name`` for the ``results`` list view --
this ensures that the results view and the detail view have a this ensures that the results view and the detail view have a
@ -268,16 +287,11 @@ automatically -- since we're using a Django model (``Poll``), Django
is able to determine an appropriate name for the context variable. is able to determine an appropriate name for the context variable.
However, for ListView, the automatically generated context variable is However, for ListView, the automatically generated context variable is
``poll_list``. To override this we provide the ``context_object_name`` ``poll_list``. To override this we provide the ``context_object_name``
option, specifying that we want to use ``latest_poll_list`` instead. attribute, specifying that we want to use ``latest_poll_list`` instead.
As an alternative approach, you could change your templates to match As an alternative approach, you could change your templates to match
the new default context variables -- but it's a lot easier to just the new default context variables -- but it's a lot easier to just
tell Django to use the variable you want. tell Django to use the variable you want.
You can now delete the ``index()``, ``detail()`` and ``results()`` views from
``polls/views.py``. We don't need them anymore -- they have been replaced by
generic views. You can also delete the import for ``HttpResponse``, which is no
longer required.
Run the server, and use your new polling app based on generic views. Run the server, and use your new polling app based on generic views.
For full details on generic views, see the :doc:`generic views documentation For full details on generic views, see the :doc:`generic views documentation

View File

@ -378,45 +378,40 @@ Improving our view
The list of polls shows polls that aren't published yet (i.e. those that have a The list of polls shows polls that aren't published yet (i.e. those that have a
``pub_date`` in the future). Let's fix that. ``pub_date`` in the future). Let's fix that.
In :doc:`Tutorial 4 </intro/tutorial04>` we deleted the view functions from In :doc:`Tutorial 4 </intro/tutorial04>` we introduced a class-based view,
``views.py`` in favor of a :class:`~django.views.generic.list.ListView` in based on :class:`~django.views.generic.list.ListView`::
``urls.py``::
url(r'^$', class IndexView(generic.ListView):
ListView.as_view( template_name = 'polls/index.html'
queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name = 'latest_poll_list'
context_object_name='latest_poll_list',
template_name='polls/index.html'), def get_queryset(self):
name='index'), """Return the last five published polls."""
return Poll.objects.order_by('-pub_date')[:5]
``response.context_data['latest_poll_list']`` extracts the data this view ``response.context_data['latest_poll_list']`` extracts the data this view
places into the context. places into the context.
We need to amend the line that gives us the ``queryset``:: We need to amend the ``get_queryset`` method and change it so that it also
checks the date by comparing it with ``timezone.now()``. First we need to add
queryset=Poll.objects.order_by('-pub_date')[:5], an import::
Let's change the queryset so that it also checks the date by comparing it with
``timezone.now()``. First we need to add an import::
from django.utils import timezone from django.utils import timezone
and then we must amend the existing ``url`` function to:: and then we must amend the ``get_queryset`` method like so::
url(r'^$', def get_queryset(self):
ListView.as_view( """
queryset=Poll.objects.filter(pub_date__lte=timezone.now) \ Return the last five published polls (not including those set to be
.order_by('-pub_date')[:5], published in the future).
context_object_name='latest_poll_list', """
template_name='polls/index.html'), return Poll.objects.filter(
name='index'), pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
``Poll.objects.filter(pub_date__lte=timezone.now)`` returns a queryset ``Poll.objects.filter(pub_date__lte=timezone.now())`` returns a queryset
containing Polls whose ``pub_date`` is less than or equal to - that is, earlier containing Polls whose ``pub_date`` is less than or equal to - that is, earlier
than or equal to - ``timezone.now``. Notice that we use a callable queryset than or equal to - ``timezone.now``.
argument, ``timezone.now``, which will be evaluated at request time. If we had
included the parentheses, ``timezone.now()`` would be evaluated just once when
the web server is started.
Testing our new view Testing our new view
-------------------- --------------------
@ -527,20 +522,18 @@ Testing the ``DetailView``
What we have works well; however, even though future polls don't appear in the What we have works well; however, even though future polls don't appear in the
*index*, users can still reach them if they know or guess the right URL. So we *index*, users can still reach them if they know or guess the right URL. So we
need similar constraints in the ``DetailViews``, by adding:: need to add a similar constraint to ``DetailView``::
queryset=Poll.objects.filter(pub_date__lte=timezone.now)
to them - for example:: class DetailView(generic.DetailView):
...
def get_queryset(self):
"""
Excludes any polls that aren't published yet.
"""
return Poll.objects.filter(pub_date__lte=timezone.now())
url(r'^(?P<pk>\d+)/$', And of course, we will add some tests, to check that a ``Poll`` whose
DetailView.as_view(
queryset=Poll.objects.filter(pub_date__lte=timezone.now),
model=Poll,
template_name='polls/detail.html'),
name='detail'),
and of course, we will add some tests, to check that a ``Poll`` whose
``pub_date`` is in the past can be displayed, and that one with a ``pub_date`` ``pub_date`` is in the past can be displayed, and that one with a ``pub_date``
in the future is not:: in the future is not::
@ -566,9 +559,9 @@ in the future is not::
Ideas for more tests Ideas for more tests
-------------------- --------------------
We ought to add similar ``queryset`` arguments to the other ``DetailView`` We ought to add a similar ``get_queryset`` method to ``ResultsView`` and
URLs, and create a new test class for each view. They'll be very similar to create a new test class for that view. It'll be very similar to what we have
what we have just created; in fact there will be a lot of repetition. just created; in fact there will be a lot of repetition.
We could also improve our application in other ways, adding tests along the We could also improve our application in other ways, adding tests along the
way. For example, it's silly that ``Polls`` can be published on the site that way. For example, it's silly that ``Polls`` can be published on the site that