/docs/intro/tutorial04.txt
Plain Text | 339 lines | 267 code | 72 blank | 0 comment | 0 complexity | 7e409adadadab24303c7747de9bcfe3f MD5 | raw file
Possible License(s): BSD-3-Clause
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>`