PageRenderTime 160ms CodeModel.GetById 154ms app.highlight 2ms RepoModel.GetById 1ms app.codeStats 0ms

/docs/intro/tutorial04.txt

https://code.google.com/p/mango-py/
Plain Text | 339 lines | 267 code | 72 blank | 0 comment | 0 complexity | 7e409adadadab24303c7747de9bcfe3f MD5 | raw file
  1=====================================
  2Writing your first Django app, part 4
  3=====================================
  4
  5This tutorial begins where :doc:`Tutorial 3 </intro/tutorial03>` left off. We're
  6continuing the Web-poll application and will focus on simple form processing and
  7cutting down our code.
  8
  9Write a simple form
 10===================
 11
 12Let's update our poll detail template ("polls/detail.html") from the last
 13tutorial, so that the template contains an HTML ``<form>`` element:
 14
 15.. code-block:: html+django
 16
 17    <h1>{{ poll.question }}</h1>
 18
 19    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
 20
 21    <form action="/polls/{{ poll.id }}/vote/" method="post">
 22    {% csrf_token %}
 23    {% for choice in poll.choice_set.all %}
 24        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
 25        <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
 26    {% endfor %}
 27    <input type="submit" value="Vote" />
 28    </form>
 29
 30A quick rundown:
 31
 32    * The above template displays a radio button for each poll choice. The
 33      ``value`` of each radio button is the associated poll choice's ID. The
 34      ``name`` of each radio button is ``"choice"``. That means, when somebody
 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.
 37
 38    * We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we
 39      set ``method="post"``. Using ``method="post"`` (as opposed to
 40      ``method="get"``) is very important, because the act of submitting this
 41      form will alter data server-side. Whenever you create a form that alters
 42      data server-side, use ``method="post"``. This tip isn't specific to
 43      Django; it's just good Web development practice.
 44
 45    * ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone
 46      through its loop
 47
 48    * Since we're creating a POST form (which can have the effect of modifying
 49      data), we need to worry about Cross Site Request Forgeries.
 50      Thankfully, you don't have to worry too hard, because Django comes with
 51      a very easy-to-use system for protecting against it. In short, all POST
 52      forms that are targeted at internal URLs should use the ``{% csrf_token %}``
 53      template tag.
 54
 55The ``{% csrf_token %}`` tag requires information from the request object, which
 56is not normally accessible from within the template context. To fix this, a
 57small adjustment needs to be made to the ``detail`` view, so that it looks like
 58the following::
 59
 60    from django.template import RequestContext
 61    # ...
 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))
 66
 67The details of how this works are explained in the documentation for
 68:ref:`RequestContext <subclassing-context-requestcontext>`.
 69
 70Now, let's create a Django view that handles the submitted data and does
 71something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
 72created a URLconf for the polls application that includes this line::
 73
 74    (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
 75
 76We also created a dummy implementation of the ``vote()`` function. Let's
 77create a real version. Add the following to ``polls/views.py``::
 78
 79    from django.shortcuts import get_object_or_404, render_to_response
 80    from django.http import HttpResponseRedirect, HttpResponse
 81    from django.core.urlresolvers import reverse
 82    from django.template import RequestContext
 83    from polls.models import Choice, Poll
 84    # ...
 85    def vote(request, poll_id):
 86        p = get_object_or_404(Poll, pk=poll_id)
 87        try:
 88            selected_choice = p.choice_set.get(pk=request.POST['choice'])
 89        except (KeyError, Choice.DoesNotExist):
 90            # Redisplay the poll voting form.
 91            return render_to_response('polls/detail.html', {
 92                'poll': p,
 93                'error_message': "You didn't select a choice.",
 94            }, context_instance=RequestContext(request))
 95        else:
 96            selected_choice.votes += 1
 97            selected_choice.save()
 98            # Always return an HttpResponseRedirect after successfully dealing
 99            # with POST data. This prevents data from being posted twice if a
100            # user hits the Back button.
101            return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
102
103This code includes a few things we haven't covered yet in this tutorial:
104
105    * :attr:`request.POST <django.http.HttpRequest.POST>` is a dictionary-like
106      object that lets you access submitted data by key name. In this case,
107      ``request.POST['choice']`` returns the ID of the selected choice, as a
108      string. :attr:`request.POST <django.http.HttpRequest.POST>` values are
109      always strings.
110
111      Note that Django also provides :attr:`request.GET
112      <django.http.HttpRequest.GET>` for accessing GET data in the same way --
113      but we're explicitly using :attr:`request.POST
114      <django.http.HttpRequest.POST>` in our code, to ensure that data is only
115      altered via a POST call.
116
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.
120
121    * After incrementing the choice count, the code returns an
122      :class:`~django.http.HttpResponseRedirect` rather than a normal
123      :class:`~django.http.HttpResponse`.
124      :class:`~django.http.HttpResponseRedirect` takes a single argument: the
125      URL to which the user will be redirected (see the following point for how
126      we construct the URL in this case).
127
128      As the Python comment above points out, you should always return an
129      :class:`~django.http.HttpResponseRedirect` after successfully dealing with
130      POST data. This tip isn't specific to Django; it's just good Web
131      development practice.
132
133    * We are using the :func:`~django.core.urlresolvers.reverse` function in the
134      :class:`~django.http.HttpResponseRedirect` constructor in this example.
135      This function helps avoid having to hardcode a URL in the view function.
136      It is given the name of the view that we want to pass control to and the
137      variable portion of the URL pattern that points to that view. In this
138      case, using the URLconf we set up in Tutorial 3, this
139      :func:`~django.core.urlresolvers.reverse` call will return a string like
140      ::
141
142        '/polls/3/results/'
143
144      ... 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).
147
148As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
149object. For more on :class:`~django.http.HttpRequest` objects, see the
150:doc:`request and response documentation </ref/request-response>`.
151
152After somebody votes in a poll, the ``vote()`` view redirects to the results
153page for the poll. Let's write that view::
154
155    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})
158
159This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
160</intro/tutorial03>`. The only difference is the template name. We'll fix this
161redundancy later.
162
163Now, create a ``results.html`` template:
164
165.. code-block:: html+django
166
167    <h1>{{ poll.question }}</h1>
168
169    <ul>
170    {% for choice in poll.choice_set.all %}
171        <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
172    {% endfor %}
173    </ul>
174
175    <a href="/polls/{{ poll.id }}/">Vote again?</a>
176
177Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a
178results page that gets updated each time you vote. If you submit the form
179without having chosen a choice, you should see the error message.
180
181Use generic views: Less code is better
182======================================
183
184The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()``
185views are stupidly simple -- and, as mentioned above, redundant. The ``index()``
186view (also from Tutorial 3), which displays a list of polls, is similar.
187
188These views represent a common case of basic Web development: getting data from
189the database according to a parameter passed in the URL, loading a template and
190returning the rendered template. Because this is so common, Django provides a
191shortcut, called the "generic views" system.
192
193Generic views abstract common patterns to the point where you don't even need
194to write Python code to write an app.
195
196Let's convert our poll app to use the generic views system, so we can delete a
197bunch of our own code. We'll just have to take a few steps to make the
198conversion. We will:
199
200    1. Convert the URLconf.
201
202    2. Delete some of the old, unneeded views.
203
204    3. Fix up URL handling for the new views.
205
206Read on for details.
207
208.. admonition:: Why the code-shuffle?
209
210    Generally, when writing a Django app, you'll evaluate whether generic views
211    are a good fit for your problem, and you'll use them from the beginning,
212    rather than refactoring your code halfway through. But this tutorial
213    intentionally has focused on writing the views "the hard way" until now, to
214    focus on core concepts.
215
216    You should know basic math before you start using a calculator.
217
218First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
219tutorial so far::
220
221    from django.conf.urls.defaults import *
222
223    urlpatterns = patterns('polls.views',
224        (r'^$', 'index'),
225        (r'^(?P<poll_id>\d+)/$', 'detail'),
226        (r'^(?P<poll_id>\d+)/results/$', 'results'),
227        (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
228    )
229
230Change it like so::
231
232    from django.conf.urls.defaults import *
233    from django.views.generic import DetailView, ListView
234    from polls.models import Poll
235
236    urlpatterns = patterns('',
237        (r'^$',
238            ListView.as_view(
239                queryset=Poll.objects.order_by('-pub_date')[:5],
240                context_object_name='latest_poll_list',
241                template_name='polls/index.html')),
242        (r'^(?P<pk>\d+)/$',
243            DetailView.as_view(
244                model=Poll,
245                template_name='polls/detail.html')),
246        url(r'^(?P<pk>\d+)/results/$',
247            DetailView.as_view(
248                model=Poll,
249                template_name='polls/results.html'),
250            name='poll_results'),
251        (r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
252    )
253
254We're using two generic views here:
255:class:`~django.views.generic.list.ListView` and
256:class:`~django.views.generic.detail.DetailView`. Respectively, those
257two views abstract the concepts of "display a list of objects" and
258"display a detail page for a particular type of object."
259
260    * Each generic view needs to know what model it will be acting
261      upon. This is provided using the ``model`` parameter.
262
263    * The :class:`~django.views.generic.list.DetailView` generic view
264      expects the primary key value captured from the URL to be called
265      ``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic
266      views.
267
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.default.url` function from
273      :mod:`django.conf.urls.defaults` here. It's a good habit to use
274      :func:`~django.conf.urls.defaults.url` when you are providing a
275      pattern name like this.
276
277By default, the :class:`~django.views.generic.list.DetailView` generic
278view uses a template called ``<app name>/<model name>_detail.html``.
279In our case, it'll use the template ``"polls/poll_detail.html"``. The
280``template_name`` argument is used to tell Django to use a specific
281template name instead of the autogenerated default template name. We
282also specify the ``template_name`` for the ``results`` list view --
283this ensures that the results view and the detail view have a
284different appearance when rendered, even though they're both a
285:class:`~django.views.generic.list.DetailView` behind the scenes.
286
287Similarly, the :class:`~django.views.generic.list.ListView` generic
288view uses a default template called ``<app name>/<model
289name>_list.html``; we use ``template_name`` to tell
290:class:`~django.views.generic.list.ListView` to use our existing
291``"polls/index.html"`` template.
292
293In previous parts of the tutorial, the templates have been provided
294with a context that contains the ``poll`` and ``latest_poll_list``
295context variables. For DetailView the ``poll`` variable is provided
296automatically -- since we're using a Django model (``Poll``), Django
297is able to determine an appropriate name for the context variable.
298However, for ListView, the automatically generated context variable is
299``poll_list``. To override this we provide the ``context_object_name``
300option, specifying that we want to use ``latest_poll_list`` instead.
301As an alternative approach, you could change your templates to match
302the new default context variables -- but it's a lot easier to just
303tell Django to use the variable you want.
304
305You can now delete the ``index()``, ``detail()`` and ``results()``
306views from ``polls/views.py``. We don't need them anymore -- they have
307been replaced by generic views.
308
309The last thing to do is fix the URL handling to account for the use of
310generic views. In the vote view above, we used the
311:func:`~django.core.urlresolvers.reverse` function to avoid
312hard-coding our URLs. Now that we've switched to a generic view, we'll
313need to change the :func:`~django.core.urlresolvers.reverse` call to
314point back to our new generic view. We can't simply use the view
315function anymore -- generic views can be (and are) used multiple times
316-- but we can use the name we've given::
317
318    return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
319
320Run the server, and use your new polling app based on generic views.
321
322For full details on generic views, see the :doc:`generic views documentation
323</topics/http/generic-views>`.
324
325Coming soon
326===========
327
328The tutorial ends here for the time being. Future installments of the tutorial
329will cover:
330
331    * Advanced form processing
332    * Using the RSS framework
333    * Using the cache framework
334    * Using the comments framework
335    * Advanced admin features: Permissions
336    * Advanced admin features: Custom JavaScript
337
338In the meantime, you might want to check out some pointers on :doc:`where to go
339from here </intro/whatsnext>`