|
1 ===================================== |
|
2 Writing your first Django app, part 4 |
|
3 ===================================== |
|
4 |
|
5 This tutorial begins where :doc:`Tutorial 3 </intro/tutorial03>` left off. We're |
|
6 continuing the Web-poll application and will focus on simple form processing and |
|
7 cutting down our code. |
|
8 |
|
9 Write a simple form |
|
10 =================== |
|
11 |
|
12 Let's update our poll detail template ("polls/detail.html") from the last |
|
13 tutorial, 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 |
|
30 A 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 |
|
55 The ``{% csrf_token %}`` tag requires information from the request object, which |
|
56 is not normally accessible from within the template context. To fix this, a |
|
57 small adjustment needs to be made to the ``detail`` view, so that it looks like |
|
58 the 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 |
|
67 The details of how this works are explained in the documentation for |
|
68 :ref:`RequestContext <subclassing-context-requestcontext>`. |
|
69 |
|
70 Now, let's create a Django view that handles the submitted data and does |
|
71 something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we |
|
72 created a URLconf for the polls application that includes this line:: |
|
73 |
|
74 (r'^(?P<poll_id>\d+)/vote/$', 'vote'), |
|
75 |
|
76 We also created a dummy implementation of the ``vote()`` function. Let's |
|
77 create 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 |
|
103 This 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 |
|
148 As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest` |
|
149 object. For more on :class:`~django.http.HttpRequest` objects, see the |
|
150 :doc:`request and response documentation </ref/request-response>`. |
|
151 |
|
152 After somebody votes in a poll, the ``vote()`` view redirects to the results |
|
153 page 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 |
|
159 This 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 |
|
161 redundancy later. |
|
162 |
|
163 Now, 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 |
|
177 Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a |
|
178 results page that gets updated each time you vote. If you submit the form |
|
179 without having chosen a choice, you should see the error message. |
|
180 |
|
181 Use generic views: Less code is better |
|
182 ====================================== |
|
183 |
|
184 The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()`` |
|
185 views are stupidly simple -- and, as mentioned above, redundant. The ``index()`` |
|
186 view (also from Tutorial 3), which displays a list of polls, is similar. |
|
187 |
|
188 These views represent a common case of basic Web development: getting data from |
|
189 the database according to a parameter passed in the URL, loading a template and |
|
190 returning the rendered template. Because this is so common, Django provides a |
|
191 shortcut, called the "generic views" system. |
|
192 |
|
193 Generic views abstract common patterns to the point where you don't even need |
|
194 to write Python code to write an app. |
|
195 |
|
196 Let's convert our poll app to use the generic views system, so we can delete a |
|
197 bunch of our own code. We'll just have to take a few steps to make the |
|
198 conversion. We will: |
|
199 |
|
200 1. Convert the URLconf. |
|
201 |
|
202 2. Rename a few templates. |
|
203 |
|
204 3. Delete some of the old, unneeded views. |
|
205 |
|
206 4. Fix up URL handling for the new views. |
|
207 |
|
208 Read on for details. |
|
209 |
|
210 .. admonition:: Why the code-shuffle? |
|
211 |
|
212 Generally, when writing a Django app, you'll evaluate whether generic views |
|
213 are a good fit for your problem, and you'll use them from the beginning, |
|
214 rather than refactoring your code halfway through. But this tutorial |
|
215 intentionally has focused on writing the views "the hard way" until now, to |
|
216 focus on core concepts. |
|
217 |
|
218 You should know basic math before you start using a calculator. |
|
219 |
|
220 First, open the ``polls/urls.py`` URLconf. It looks like this, according to the |
|
221 tutorial so far:: |
|
222 |
|
223 from django.conf.urls.defaults import * |
|
224 |
|
225 urlpatterns = patterns('polls.views', |
|
226 (r'^$', 'index'), |
|
227 (r'^(?P<poll_id>\d+)/$', 'detail'), |
|
228 (r'^(?P<poll_id>\d+)/results/$', 'results'), |
|
229 (r'^(?P<poll_id>\d+)/vote/$', 'vote'), |
|
230 ) |
|
231 |
|
232 Change it like so:: |
|
233 |
|
234 from django.conf.urls.defaults import * |
|
235 from polls.models import Poll |
|
236 |
|
237 info_dict = { |
|
238 'queryset': Poll.objects.all(), |
|
239 } |
|
240 |
|
241 urlpatterns = patterns('', |
|
242 (r'^$', 'django.views.generic.list_detail.object_list', info_dict), |
|
243 (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), |
|
244 url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), |
|
245 (r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), |
|
246 ) |
|
247 |
|
248 We're using two generic views here: |
|
249 :func:`~django.views.generic.list_detail.object_list` and |
|
250 :func:`~django.views.generic.list_detail.object_detail`. Respectively, those two |
|
251 views abstract the concepts of "display a list of objects" and "display a detail |
|
252 page for a particular type of object." |
|
253 |
|
254 * Each generic view needs to know what data it will be acting upon. This |
|
255 data is provided in a dictionary. The ``queryset`` key in this dictionary |
|
256 points to the list of objects to be manipulated by the generic view. |
|
257 |
|
258 * The :func:`~django.views.generic.list_detail.object_detail` generic view |
|
259 expects the ID value captured from the URL to be called ``"object_id"``, |
|
260 so we've changed ``poll_id`` to ``object_id`` for the generic views. |
|
261 |
|
262 * We've added a name, ``poll_results``, to the results view so that we have |
|
263 a way to refer to its URL later on (see the documentation about |
|
264 :ref:`naming URL patterns <naming-url-patterns>` for information). We're |
|
265 also using the :func:`~django.conf.urls.default.url` function from |
|
266 :mod:`django.conf.urls.defaults` here. It's a good habit to use |
|
267 :func:`~django.conf.urls.defaults.url` when you are providing a pattern |
|
268 name like this. |
|
269 |
|
270 By default, the :func:`~django.views.generic.list_detail.object_detail` generic |
|
271 view uses a template called ``<app name>/<model name>_detail.html``. In our |
|
272 case, it'll use the template ``"polls/poll_detail.html"``. Thus, rename your |
|
273 ``polls/detail.html`` template to ``polls/poll_detail.html``, and change the |
|
274 :func:`~django.shortcuts.render_to_response` line in ``vote()``. |
|
275 |
|
276 Similarly, the :func:`~django.views.generic.list_detail.object_list` generic |
|
277 view uses a template called ``<app name>/<model name>_list.html``. Thus, rename |
|
278 ``polls/index.html`` to ``polls/poll_list.html``. |
|
279 |
|
280 Because we have more than one entry in the URLconf that uses |
|
281 :func:`~django.views.generic.list_detail.object_detail` for the polls app, we |
|
282 manually specify a template name for the results view: |
|
283 ``template_name='polls/results.html'``. Otherwise, both views would use the same |
|
284 template. Note that we use ``dict()`` to return an altered dictionary in place. |
|
285 |
|
286 .. note:: :meth:`django.db.models.QuerySet.all` is lazy |
|
287 |
|
288 It might look a little frightening to see ``Poll.objects.all()`` being used |
|
289 in a detail view which only needs one ``Poll`` object, but don't worry; |
|
290 ``Poll.objects.all()`` is actually a special object called a |
|
291 :class:`~django.db.models.QuerySet`, which is "lazy" and doesn't hit your |
|
292 database until it absolutely has to. By the time the database query happens, |
|
293 the :func:`~django.views.generic.list_detail.object_detail` generic view |
|
294 will have narrowed its scope down to a single object, so the eventual query |
|
295 will only select one row from the database. |
|
296 |
|
297 If you'd like to know more about how that works, The Django database API |
|
298 documentation :ref:`explains the lazy nature of QuerySet objects |
|
299 <querysets-are-lazy>`. |
|
300 |
|
301 In previous parts of the tutorial, the templates have been provided with a |
|
302 context that contains the ``poll`` and ``latest_poll_list`` context variables. |
|
303 However, the generic views provide the variables ``object`` and ``object_list`` |
|
304 as context. Therefore, you need to change your templates to match the new |
|
305 context variables. Go through your templates, and modify any reference to |
|
306 ``latest_poll_list`` to ``object_list``, and change any reference to ``poll`` |
|
307 to ``object``. |
|
308 |
|
309 You can now delete the ``index()``, ``detail()`` and ``results()`` views |
|
310 from ``polls/views.py``. We don't need them anymore -- they have been replaced |
|
311 by generic views. |
|
312 |
|
313 The ``vote()`` view is still required. However, it must be modified to match the |
|
314 new context variables. In the :func:`~django.shortcuts.render_to_response` call, |
|
315 rename the ``poll`` context variable to ``object``. |
|
316 |
|
317 The last thing to do is fix the URL handling to account for the use of generic |
|
318 views. In the vote view above, we used the |
|
319 :func:`~django.core.urlresolvers.reverse` function to avoid hard-coding our |
|
320 URLs. Now that we've switched to a generic view, we'll need to change the |
|
321 :func:`~django.core.urlresolvers.reverse` call to point back to our new generic |
|
322 view. We can't simply use the view function anymore -- generic views can be (and |
|
323 are) used multiple times -- but we can use the name we've given:: |
|
324 |
|
325 return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) |
|
326 |
|
327 Run the server, and use your new polling app based on generic views. |
|
328 |
|
329 For full details on generic views, see the :doc:`generic views documentation |
|
330 </topics/http/generic-views>`. |
|
331 |
|
332 Coming soon |
|
333 =========== |
|
334 |
|
335 The tutorial ends here for the time being. Future installments of the tutorial |
|
336 will cover: |
|
337 |
|
338 * Advanced form processing |
|
339 * Using the RSS framework |
|
340 * Using the cache framework |
|
341 * Using the comments framework |
|
342 * Advanced admin features: Permissions |
|
343 * Advanced admin features: Custom JavaScript |
|
344 |
|
345 In the meantime, you might want to check out some pointers on :doc:`where to go |
|
346 from here </intro/whatsnext>` |