mirror of https://github.com/django/django.git
[1.5.X] Fixed #20249 - Removed a "feature" in the tutorial that doesn't actually work.
Thanks bmispelon for the report and draft patch.
Backport of 679a2ac843
from master
This commit is contained in:
parent
b41ad87873
commit
13de270bd5
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue