This tutorial begins where Tutorial 3 left off. We’re continuing the Web-poll application and will focus on simple form processing and cutting down our code.
Let’s update our poll detail template (“polls/detail.html”) from the last
tutorial, so that the template contains an HTML <form>
element:
<h1>{{ poll.question }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="/polls/{{ poll.id }}/vote/" method="post">
{% csrf_token %}
{% for choice in poll.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
A quick rundown:
value
of each radio button is the associated poll choice’s ID. The
name
of each radio button is "choice"
. That means, when somebody
selects one of the radio buttons and submits the form, it’ll send the
POST data choice=3
. This is HTML Forms 101.action
to /polls/{{ poll.id }}/vote/
, and we
set method="post"
. Using method="post"
(as opposed to
method="get"
) is very important, because the act of submitting this
form will alter data server-side. Whenever you create a form that alters
data server-side, use method="post"
. This tip isn’t specific to
Django; it’s just good Web development practice.forloop.counter
indicates how many times the for
tag has gone
through its loop{% csrf_token %}
template tag.The {% csrf_token %}
tag requires information from the
request object, which is not normally accessible from within the template
context. To fix this, a small adjustment needs to be made to the detail
view, so that it looks like the following:
from django.template import RequestContext
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p},
context_instance=RequestContext(request))
The details of how this works are explained in the documentation for RequestContext.
Now, let’s create a Django view that handles the submitted data and does something with it. Remember, in Tutorial 3, we created a URLconf for the polls application that includes this line:
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
We also created a dummy implementation of the vote()
function. Let’s
create a real version. Add the following to polls/views.py
:
from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from polls.models import Choice, Poll
# ...
def vote(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
try:
selected_choice = p.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the poll voting form.
return render_to_response('polls/detail.html', {
'poll': p,
'error_message': "You didn't select a choice.",
}, context_instance=RequestContext(request))
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
This code includes a few things we haven’t covered yet in this tutorial:
request.POST
is a dictionary-like
object that lets you access submitted data by key name. In this case,
request.POST['choice']
returns the ID of the selected choice, as a
string. request.POST
values are
always strings.
Note that Django also provides request.GET
for accessing GET data in the same way –
but we’re explicitly using request.POST
in our code, to ensure that data is only
altered via a POST call.
request.POST['choice']
will raise KeyError
if choice
wasn’t
provided in POST data. The above code checks for KeyError
and
redisplays the poll form with an error message if choice
isn’t given.
After incrementing the choice count, the code returns an
HttpResponseRedirect
rather than a normal
HttpResponse
.
HttpResponseRedirect
takes a single argument: the
URL to which the user will be redirected (see the following point for how
we construct the URL in this case).
As the Python comment above points out, you should always return an
HttpResponseRedirect
after successfully dealing with
POST data. This tip isn’t specific to Django; it’s just good Web
development practice.
We are using the reverse()
function in the
HttpResponseRedirect
constructor in this example.
This function helps avoid having to hardcode a URL in the view function.
It is given the name of the view that we want to pass control to and the
variable portion of the URL pattern that points to that view. In this
case, using the URLconf we set up in Tutorial 3, this
reverse()
call will return a string like
'/polls/3/results/'
... where the 3
is the value of p.id
. This redirected URL will
then call the 'results'
view to display the final page. Note that you
need to use the full name of the view here (including the prefix).
As mentioned in Tutorial 3, request
is a HttpRequest
object. For more on HttpRequest
objects, see the
request and response documentation.
After somebody votes in a poll, the vote()
view redirects to the results
page for the poll. Let’s write that view:
def results(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/results.html', {'poll': p})
This is almost exactly the same as the detail()
view from Tutorial 3. The only difference is the template name. We’ll fix this
redundancy later.
Now, create a results.html
template:
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="/polls/{{ poll.id }}/">Vote again?</a>
Now, go to /polls/1/
in your browser and vote in the poll. You should see a
results page that gets updated each time you vote. If you submit the form
without having chosen a choice, you should see the error message.
The detail()
(from Tutorial 3) and results()
views are stupidly simple – and, as mentioned above, redundant. The index()
view (also from Tutorial 3), which displays a list of polls, is similar.
These views represent a common case of basic Web development: getting data from the database according to a parameter passed in the URL, loading a template and returning the rendered template. Because this is so common, Django provides a shortcut, called the “generic views” system.
Generic views abstract common patterns to the point where you don’t even need to write Python code to write an app.
Let’s convert our poll app to use the generic views system, so we can delete a bunch of our own code. We’ll just have to take a few steps to make the conversion. We will:
Read on for details.
Why the code-shuffle?
Generally, when writing a Django app, you’ll evaluate whether generic views are a good fit for your problem, and you’ll use them from the beginning, rather than refactoring your code halfway through. But this tutorial intentionally has focused on writing the views “the hard way” until now, to focus on core concepts.
You should know basic math before you start using a calculator.
First, open the polls/urls.py
URLconf. It looks like this, according to the
tutorial so far:
from django.conf.urls import patterns, include, url
urlpatterns = patterns('polls.views',
url(r'^$', 'index'),
url(r'^(?P<poll_id>\d+)/$', 'detail'),
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)
Change it like so:
from django.conf.urls import patterns, include, url
from django.views.generic import DetailView, ListView
from polls.models import Poll
urlpatterns = patterns('',
url(r'^$',
ListView.as_view(
queryset=Poll.objects.order_by('-pub_date')[:5],
context_object_name='latest_poll_list',
template_name='polls/index.html')),
url(r'^(?P<pk>\d+)/$',
DetailView.as_view(
model=Poll,
template_name='polls/detail.html')),
url(r'^(?P<pk>\d+)/results/$',
DetailView.as_view(
model=Poll,
template_name='polls/results.html'),
name='poll_results'),
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
)
We’re using two generic views here:
ListView
and
DetailView
. Respectively, those
two views abstract the concepts of “display a list of objects” and
“display a detail page for a particular type of object.”
model
parameter.DetailView
generic view
expects the primary key value captured from the URL to be called
"pk"
, so we’ve changed poll_id
to pk
for the generic
views.poll_results
, to the results view so
that we have a way to refer to its URL later on (see the
documentation about naming URL patterns for information). We’re also using the
url()
function from
django.conf.urls
here. It’s a good habit to use
url()
when you are providing a
pattern name like this.By default, the DetailView
generic
view uses a template called <app name>/<model name>_detail.html
.
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 instead of the autogenerated default template name. We
also specify the template_name
for the results
list view –
this ensures that the results view and the detail view have a
different appearance when rendered, even though they’re both a
DetailView
behind the scenes.
Similarly, the ListView
generic
view uses a default template called <app name>/<model
name>_list.html
; we use template_name
to tell
ListView
to use our existing
"polls/index.html"
template.
In previous parts of the tutorial, the templates have been provided
with a context that contains the poll
and latest_poll_list
context variables. For DetailView the poll
variable is provided
automatically – since we’re using a Django model (Poll
), Django
is able to determine an appropriate name for the context variable.
However, for ListView, the automatically generated context variable is
poll_list
. To override this we provide the context_object_name
option, specifying that we want to use latest_poll_list
instead.
As an alternative approach, you could change your templates to match
the new default context variables – but it’s a lot easier to just
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.
The last thing to do is fix the URL handling to account for the use of
generic views. In the vote view above, we used the
reverse()
function to avoid
hard-coding our URLs. Now that we’ve switched to a generic view, we’ll
need to change the reverse()
call to
point back to our new generic view. We can’t simply use the view
function anymore – generic views can be (and are) used multiple times
– but we can use the name we’ve given:
return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
Run the server, and use your new polling app based on generic views.
For full details on generic views, see the generic views documentation.
The tutorial ends here for the time being. Future installments of the tutorial will cover:
In the meantime, you might want to check out some pointers on where to go from here
Oct 11, 2017