mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	Thank-you Katie Miller and Ben Sturmfels for the initial draft, as well as Russ and Carl for the reviews.
		
			
				
	
	
		
			287 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			287 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
=====================================
 | 
						|
Writing your first Django app, part 4
 | 
						|
=====================================
 | 
						|
 | 
						|
This tutorial begins where :doc:`Tutorial 3 </intro/tutorial03>` 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 ("polls/detail.html") from the last
 | 
						|
tutorial, so that the template contains an HTML ``<form>`` element:
 | 
						|
 | 
						|
.. code-block:: html+django
 | 
						|
 | 
						|
    <h1>{{ poll.question }}</h1>
 | 
						|
 | 
						|
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
 | 
						|
 | 
						|
    <form action="{% url 'polls:vote' poll.id %}" 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_text }}</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 ``{% url 'polls:vote' poll.id %}``, 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 :ttag:`for` tag has gone
 | 
						|
  through its loop
 | 
						|
 | 
						|
* Since we're creating a POST form (which can have the effect of modifying
 | 
						|
  data), we need to worry about Cross Site Request Forgeries.
 | 
						|
  Thankfully, you don't have to worry too hard, because Django comes with
 | 
						|
  a very easy-to-use system for protecting against it. In short, all POST
 | 
						|
  forms that are targeted at internal URLs should use the
 | 
						|
  :ttag:`{% csrf_token %}<csrf_token>` template tag.
 | 
						|
 | 
						|
Now, let's create a Django view that handles the submitted data and does
 | 
						|
something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
 | 
						|
created a URLconf for the polls application that includes this line::
 | 
						|
 | 
						|
    url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='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
 | 
						|
    from django.http import HttpResponseRedirect, HttpResponse
 | 
						|
    from django.core.urlresolvers import reverse
 | 
						|
    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(request, '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('polls:results', args=(p.id,)))
 | 
						|
 | 
						|
This code includes a few things we haven't covered yet in this tutorial:
 | 
						|
 | 
						|
* :attr:`request.POST <django.http.HttpRequest.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. :attr:`request.POST <django.http.HttpRequest.POST>` values are
 | 
						|
  always strings.
 | 
						|
 | 
						|
  Note that Django also provides :attr:`request.GET
 | 
						|
  <django.http.HttpRequest.GET>` for accessing GET data in the same way --
 | 
						|
  but we're explicitly using :attr:`request.POST
 | 
						|
  <django.http.HttpRequest.POST>` in our code, to ensure that data is only
 | 
						|
  altered via a POST call.
 | 
						|
 | 
						|
* ``request.POST['choice']`` will raise :exc:`KeyError` if ``choice`` wasn't
 | 
						|
  provided in POST data. The above code checks for :exc:`KeyError` and
 | 
						|
  redisplays the poll form with an error message if ``choice`` isn't given.
 | 
						|
 | 
						|
* After incrementing the choice count, the code returns an
 | 
						|
  :class:`~django.http.HttpResponseRedirect` rather than a normal
 | 
						|
  :class:`~django.http.HttpResponse`.
 | 
						|
  :class:`~django.http.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
 | 
						|
  :class:`~django.http.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 :func:`~django.core.urlresolvers.reverse` function in the
 | 
						|
  :class:`~django.http.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
 | 
						|
  :func:`~django.core.urlresolvers.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.
 | 
						|
 | 
						|
As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
 | 
						|
object. For more on :class:`~django.http.HttpRequest` objects, see the
 | 
						|
:doc:`request and response documentation </ref/request-response>`.
 | 
						|
 | 
						|
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):
 | 
						|
        poll = get_object_or_404(Poll, pk=poll_id)
 | 
						|
        return render(request, 'polls/results.html', {'poll': poll})
 | 
						|
 | 
						|
This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
 | 
						|
</intro/tutorial03>`. The only difference is the template name. We'll fix this
 | 
						|
redundancy later.
 | 
						|
 | 
						|
Now, create a ``polls/results.html`` template:
 | 
						|
 | 
						|
.. code-block:: html+django
 | 
						|
 | 
						|
    <h1>{{ poll.question }}</h1>
 | 
						|
 | 
						|
    <ul>
 | 
						|
    {% for choice in poll.choice_set.all %}
 | 
						|
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
 | 
						|
    {% endfor %}
 | 
						|
    </ul>
 | 
						|
 | 
						|
    <a href="{% url 'polls:detail' 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.
 | 
						|
 | 
						|
Use generic views: Less code is better
 | 
						|
======================================
 | 
						|
 | 
						|
The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) 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:
 | 
						|
 | 
						|
1. Convert the URLconf.
 | 
						|
 | 
						|
2. Delete some of the old, unneeded views.
 | 
						|
 | 
						|
3. Fix up URL handling for the new views.
 | 
						|
 | 
						|
Read on for details.
 | 
						|
 | 
						|
.. 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 and change it like so::
 | 
						|
 | 
						|
    from django.conf.urls import patterns, 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'),
 | 
						|
            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'),
 | 
						|
    )
 | 
						|
 | 
						|
We're using two generic views here:
 | 
						|
:class:`~django.views.generic.list.ListView` and
 | 
						|
:class:`~django.views.generic.detail.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."
 | 
						|
 | 
						|
* Each generic view needs to know what model it will be acting
 | 
						|
  upon. This is provided using the ``model`` parameter.
 | 
						|
 | 
						|
* The :class:`~django.views.generic.list.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.
 | 
						|
 | 
						|
By default, the :class:`~django.views.generic.list.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
 | 
						|
:class:`~django.views.generic.list.DetailView` behind the scenes.
 | 
						|
 | 
						|
Similarly, the :class:`~django.views.generic.list.ListView` generic
 | 
						|
view uses a default template called ``<app name>/<model
 | 
						|
name>_list.html``; we use ``template_name`` to tell
 | 
						|
:class:`~django.views.generic.list.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.
 | 
						|
 | 
						|
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
 | 
						|
</topics/class-based-views/index>`.
 | 
						|
 | 
						|
What's next?
 | 
						|
============
 | 
						|
 | 
						|
The beginner tutorial ends here for the time being. In the meantime, you might
 | 
						|
want to check out some pointers on :doc:`where to go from here
 | 
						|
</intro/whatsnext>`.
 | 
						|
 | 
						|
If you are familiar with Python packaging and interested in learning how to
 | 
						|
turn polls into a "reusable app", check out :doc:`Advanced tutorial: How to
 | 
						|
write reusable apps</intro/reusable-apps>`.
 |