mirror of https://github.com/django/django.git
281 lines
12 KiB
Plaintext
281 lines
12 KiB
Plaintext
=====================================
|
|
Writing your first Django app, part 4
|
|
=====================================
|
|
|
|
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.
|
|
|
|
Write a simple form
|
|
===================
|
|
|
|
Let's update our poll detail template 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">
|
|
{% 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:
|
|
|
|
* The above template displays a radio button for each poll choice. The
|
|
``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.
|
|
|
|
* We set the form's ``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.
|
|
|
|
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/$', 'mysite.polls.views.vote'),
|
|
|
|
So let's create a ``vote()`` function in ``mysite/polls/views.py``::
|
|
|
|
from django.shortcuts import get_object_or_404, render_to_response
|
|
from django.http import HttpResponseRedirect
|
|
from django.core.urlresolvers import reverse
|
|
from mysite.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.",
|
|
})
|
|
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('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.
|
|
|
|
For more information about ``reverse()``, see the `URL dispatcher`_
|
|
documentation.
|
|
|
|
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>
|
|
|
|
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.
|
|
|
|
.. _request and response documentation: ../request_response/
|
|
.. _URL dispatcher: ../url_dispatch#reverse
|
|
|
|
Use generic views: Less code is better
|
|
======================================
|
|
|
|
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.
|
|
|
|
.. admonition:: 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.defaults import *
|
|
|
|
urlpatterns = patterns('mysite.polls.views',
|
|
(r'^$', 'index'),
|
|
(r'^(?P<poll_id>\d+)/$', 'detail'),
|
|
(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
|
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
|
)
|
|
|
|
Change it like so::
|
|
|
|
from django.conf.urls.defaults import *
|
|
from mysite.polls.models import Poll
|
|
|
|
info_dict = {
|
|
'queryset': Poll.objects.all(),
|
|
}
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^$', 'django.views.generic.list_detail.object_list', info_dict),
|
|
(r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
|
|
(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html')),
|
|
(r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
|
|
)
|
|
|
|
We're using two generic views here: ``object_list`` and ``object_detail``.
|
|
Respectively, those two views abstract the concepts of "display a list of
|
|
objects" and "display a detail page for a particular type of object."
|
|
|
|
* Each generic view needs to know what data it will be acting upon. This
|
|
data is provided in a dictionary. The ``queryset`` key in this dictionary
|
|
points to the list of objects to be manipulated by the generic view.
|
|
|
|
* The ``object_detail`` generic view expects the ID value captured
|
|
from the URL to be called ``"object_id"``, so we've changed ``poll_id`` to
|
|
``object_id`` for the generic views.
|
|
|
|
By default, the ``object_detail`` generic view uses a template called
|
|
``<app name>/<model name>_detail.html``. In our case, it'll use the template
|
|
``"polls/poll_detail.html"``. Thus, rename your ``polls/detail.html`` template to
|
|
``polls/poll_detail.html``, and change the ``render_to_response()`` line in
|
|
``vote()``.
|
|
|
|
Similarly, the ``object_list`` generic view uses a template called
|
|
``<app name>/<model name>_list.html``. Thus, rename ``polls/index.html`` to
|
|
``polls/poll_list.html``.
|
|
|
|
Because we have more than one entry in the URLconf that uses ``object_detail``
|
|
for the polls app, we manually specify a template name for the results view:
|
|
``template_name='polls/results.html'``. Otherwise, both views would use the same
|
|
template. Note that we use ``dict()`` to return an altered dictionary in place.
|
|
|
|
.. note:: ``all()`` is lazy
|
|
|
|
It might look a little frightening to see ``Poll.objects.all()`` being used
|
|
in a detail view which only needs one ``Poll`` object, but don't worry;
|
|
``Poll.objects.all()`` is actually a special object called a ``QuerySet``,
|
|
which is "lazy" and doesn't hit your database until it absolutely has to. By
|
|
the time the database query happens, the ``object_detail`` generic view will
|
|
have narrowed its scope down to a single object, so the eventual query will
|
|
only select one row from the database.
|
|
|
|
If you'd like to know more about how that works, The Django database API
|
|
documentation `explains the lazy nature of QuerySet objects`_.
|
|
|
|
.. _explains the lazy nature of QuerySet objects: ../db-api/#querysets-are-lazy
|
|
|
|
In previous parts of the tutorial, the templates have been provided with a context
|
|
that contains the ``poll`` and ``latest_poll_list`` context variables. However,
|
|
the generic views provide the variables ``object`` and ``object_list`` as context.
|
|
Therefore, you need to change your templates to match the new context variables.
|
|
Go through your templates, and modify any reference to ``latest_poll_list`` to
|
|
``object_list``, and change any reference to ``poll`` to ``object``.
|
|
|
|
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 ``vote()`` view is still required. However, it must be modified to match
|
|
the new templates and context variables. Change the template call from
|
|
``polls/detail.html`` to ``polls/poll_detail.html``, and pass ``object`` in the
|
|
context instead of ``poll``.
|
|
|
|
Run the server, and use your new polling app based on generic views.
|
|
|
|
For full details on generic views, see the `generic views documentation`_.
|
|
|
|
.. _generic views documentation: ../generic_views/
|
|
|
|
Coming soon
|
|
===========
|
|
|
|
The tutorial ends here for the time being. But check back soon for the next
|
|
installments:
|
|
|
|
* Advanced form processing
|
|
* Using the RSS framework
|
|
* Using the cache framework
|
|
* Using the comments framework
|
|
* Advanced admin features: Permissions
|
|
* Advanced admin features: Custom JavaScript
|
|
|
|
In the meantime, you can read through the rest of the `Django documentation`_
|
|
and start writing your own applications.
|
|
|
|
.. _Tutorial 3: ../tutorial03/
|
|
.. _Django documentation: http://www.djangoproject.com/documentation/
|