19
19
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
21
<form action="/polls/{{ poll.id }}/vote/" method="post">
21
<form action="{% url 'polls:vote' poll.id %}" method="post">
23
23
{% for choice in poll.choice_set.all %}
24
24
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
25
<label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
25
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
27
27
<input type="submit" value="Vote" />
33
33
``value`` of each radio button is the associated poll choice's ID. The
34
34
``name`` of each radio button is ``"choice"``. That means, when somebody
35
35
selects one of the radio buttons and submits the form, it'll send the
36
POST data ``choice=3``. This is HTML Forms 101.
36
POST data ``choice=3``. This is the basic concept of HTML forms.
38
* We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we
38
* We set the form's ``action`` to ``{% url 'polls:vote' poll.id %}``, and we
39
39
set ``method="post"``. Using ``method="post"`` (as opposed to
40
40
``method="get"``) is very important, because the act of submitting this
41
41
form will alter data server-side. Whenever you create a form that alters
52
52
forms that are targeted at internal URLs should use the
53
53
:ttag:`{% csrf_token %}<csrf_token>` template tag.
55
The :ttag:`{% csrf_token %}<csrf_token>` tag requires information from the
56
request object, which is not normally accessible from within the template
57
context. To fix this, a small adjustment needs to be made to the ``detail``
58
view, so that it looks like the following::
60
from django.template import RequestContext
62
def detail(request, poll_id):
63
p = get_object_or_404(Poll, pk=poll_id)
64
return render_to_response('polls/detail.html', {'poll': p},
65
context_instance=RequestContext(request))
67
The details of how this works are explained in the documentation for
68
:ref:`RequestContext <subclassing-context-requestcontext>`.
70
55
Now, let's create a Django view that handles the submitted data and does
71
56
something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
72
57
created a URLconf for the polls application that includes this line::
74
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
59
url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
76
61
We also created a dummy implementation of the ``vote()`` function. Let's
77
62
create a real version. Add the following to ``polls/views.py``::
79
from django.shortcuts import get_object_or_404, render_to_response
64
from django.shortcuts import get_object_or_404, render
80
65
from django.http import HttpResponseRedirect, HttpResponse
81
66
from django.core.urlresolvers import reverse
82
from django.template import RequestContext
83
67
from polls.models import Choice, Poll
85
69
def vote(request, poll_id):
88
72
selected_choice = p.choice_set.get(pk=request.POST['choice'])
89
73
except (KeyError, Choice.DoesNotExist):
90
74
# Redisplay the poll voting form.
91
return render_to_response('polls/detail.html', {
75
return render(request, 'polls/detail.html', {
93
77
'error_message': "You didn't select a choice.",
94
}, context_instance=RequestContext(request))
96
80
selected_choice.votes += 1
97
81
selected_choice.save()
98
82
# Always return an HttpResponseRedirect after successfully dealing
99
83
# with POST data. This prevents data from being posted twice if a
100
84
# user hits the Back button.
101
return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
85
return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
103
87
This code includes a few things we haven't covered yet in this tutorial:
114
98
<django.http.HttpRequest.POST>` in our code, to ensure that data is only
115
99
altered via a POST call.
117
* ``request.POST['choice']`` will raise :exc:`KeyError` if ``choice`` wasn't
118
provided in POST data. The above code checks for :exc:`KeyError` and
119
redisplays the poll form with an error message if ``choice`` isn't given.
101
* ``request.POST['choice']`` will raise :exc:`~exceptions.KeyError` if
102
``choice`` wasn't provided in POST data. The above code checks for
103
:exc:`~exceptions.KeyError` and redisplays the poll form with an error
104
message if ``choice`` isn't given.
121
106
* After incrementing the choice count, the code returns an
122
107
:class:`~django.http.HttpResponseRedirect` rather than a normal
142
127
'/polls/3/results/'
144
129
... where the ``3`` is the value of ``p.id``. This redirected URL will
145
then call the ``'results'`` view to display the final page. Note that you
146
need to use the full name of the view here (including the prefix).
130
then call the ``'results'`` view to display the final page.
148
132
As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
149
133
object. For more on :class:`~django.http.HttpRequest` objects, see the
153
137
page for the poll. Let's write that view::
155
139
def results(request, poll_id):
156
p = get_object_or_404(Poll, pk=poll_id)
157
return render_to_response('polls/results.html', {'poll': p})
140
poll = get_object_or_404(Poll, pk=poll_id)
141
return render(request, 'polls/results.html', {'poll': poll})
159
143
This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
160
144
</intro/tutorial03>`. The only difference is the template name. We'll fix this
161
145
redundancy later.
163
Now, create a ``results.html`` template:
147
Now, create a ``polls/results.html`` template:
165
149
.. code-block:: html+django
170
154
{% for choice in poll.choice_set.all %}
171
<li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
155
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
175
<a href="/polls/{{ poll.id }}/">Vote again?</a>
159
<a href="{% url 'polls:detail' poll.id %}">Vote again?</a>
177
161
Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a
178
162
results page that gets updated each time you vote. If you submit the form
216
200
You should know basic math before you start using a calculator.
218
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
221
from django.conf.urls import patterns, include, url
223
urlpatterns = patterns('polls.views',
225
url(r'^(?P<poll_id>\d+)/$', 'detail'),
226
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
227
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
232
from django.conf.urls import patterns, include, url
205
First, open the ``polls/urls.py`` URLconf and change it like so::
207
from django.conf.urls import patterns, url
233
208
from django.views.generic import DetailView, ListView
234
209
from polls.models import Poll
238
213
ListView.as_view(
239
214
queryset=Poll.objects.order_by('-pub_date')[:5],
240
215
context_object_name='latest_poll_list',
241
template_name='polls/index.html')),
216
template_name='polls/index.html'),
242
218
url(r'^(?P<pk>\d+)/$',
243
219
DetailView.as_view(
245
template_name='polls/detail.html')),
221
template_name='polls/detail.html'),
246
223
url(r'^(?P<pk>\d+)/results/$',
247
224
DetailView.as_view(
249
226
template_name='polls/results.html'),
250
name='poll_results'),
251
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
228
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote', name='vote'),
254
234
We're using two generic views here:
255
235
:class:`~django.views.generic.list.ListView` and
256
236
:class:`~django.views.generic.detail.DetailView`. Respectively, those
260
240
* Each generic view needs to know what model it will be acting
261
241
upon. This is provided using the ``model`` parameter.
263
* The :class:`~django.views.generic.list.DetailView` generic view
243
* The :class:`~django.views.generic.detail.DetailView` generic view
264
244
expects the primary key value captured from the URL to be called
265
245
``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic
268
* We've added a name, ``poll_results``, to the results view so
269
that we have a way to refer to its URL later on (see the
270
documentation about :ref:`naming URL patterns
271
<naming-url-patterns>` for information). We're also using the
272
:func:`~django.conf.urls.url` function from
273
:mod:`django.conf.urls` here. It's a good habit to use
274
:func:`~django.conf.urls.url` when you are providing a
275
pattern name like this.
277
By default, the :class:`~django.views.generic.list.DetailView` generic
248
By default, the :class:`~django.views.generic.detail.DetailView` generic
278
249
view uses a template called ``<app name>/<model name>_detail.html``.
279
250
In our case, it'll use the template ``"polls/poll_detail.html"``. The
280
251
``template_name`` argument is used to tell Django to use a specific
282
253
also specify the ``template_name`` for the ``results`` list view --
283
254
this ensures that the results view and the detail view have a
284
255
different appearance when rendered, even though they're both a
285
:class:`~django.views.generic.list.DetailView` behind the scenes.
256
:class:`~django.views.generic.detail.DetailView` behind the scenes.
287
258
Similarly, the :class:`~django.views.generic.list.ListView` generic
288
259
view uses a default template called ``<app name>/<model
293
264
In previous parts of the tutorial, the templates have been provided
294
265
with a context that contains the ``poll`` and ``latest_poll_list``
295
context variables. For DetailView the ``poll`` variable is provided
266
context variables. For ``DetailView`` the ``poll`` variable is provided
296
267
automatically -- since we're using a Django model (``Poll``), Django
297
268
is able to determine an appropriate name for the context variable.
298
269
However, for ListView, the automatically generated context variable is
302
273
the new default context variables -- but it's a lot easier to just
303
274
tell Django to use the variable you want.
305
You can now delete the ``index()``, ``detail()`` and ``results()``
306
views from ``polls/views.py``. We don't need them anymore -- they have
307
been replaced by generic views.
309
The last thing to do is fix the URL handling to account for the use of
310
generic views. In the vote view above, we used the
311
:func:`~django.core.urlresolvers.reverse` function to avoid
312
hard-coding our URLs. Now that we've switched to a generic view, we'll
313
need to change the :func:`~django.core.urlresolvers.reverse` call to
314
point back to our new generic view. We can't simply use the view
315
function anymore -- generic views can be (and are) used multiple times
317
return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
276
You can now delete the ``index()``, ``detail()`` and ``results()`` views from
277
``polls/views.py``. We don't need them anymore -- they have been replaced by
278
generic views. You can also delete the import for ``HttpResponse``, which is no
319
281
Run the server, and use your new polling app based on generic views.
321
283
For full details on generic views, see the :doc:`generic views documentation
322
</topics/http/generic-views>`.
327
The tutorial ends here for the time being. Future installments of the tutorial
330
* Advanced form processing
331
* Using the RSS framework
332
* Using the cache framework
333
* Using the comments framework
334
* Advanced admin features: Permissions
335
* Advanced admin features: Custom JavaScript
337
In the meantime, you might want to check out some pointers on :doc:`where to go
338
from here </intro/whatsnext>`
284
</topics/class-based-views/index>`.
286
When you're comfortable with forms and generic views, read :doc:`part 5 of this
287
tutorial</intro/tutorial05>` to learn about testing our polls app.