|
1 ===================================== |
|
2 Writing your first Django app, part 4 |
|
3 ===================================== |
|
4 |
|
5 This tutorial begins where `Tutorial 3`_ left off. We're continuing the Web-poll |
|
6 application and will focus on simple form processing and cutting down our code. |
|
7 |
|
8 Write a simple form |
|
9 =================== |
|
10 |
|
11 Let's update our poll detail template from the last tutorial, so that the |
|
12 template contains an HTML ``<form>`` element:: |
|
13 |
|
14 <h1>{{ poll.question }}</h1> |
|
15 |
|
16 {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} |
|
17 |
|
18 <form action="/polls/{{ poll.id }}/vote/" method="post"> |
|
19 {% for choice in poll.choice_set.all %} |
|
20 <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> |
|
21 <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br /> |
|
22 {% endfor %} |
|
23 <input type="submit" value="Vote" /> |
|
24 </form> |
|
25 |
|
26 A quick rundown: |
|
27 |
|
28 * The above template displays a radio button for each poll choice. The |
|
29 ``value`` of each radio button is the associated poll choice's ID. The |
|
30 ``name`` of each radio button is ``"choice"``. That means, when somebody |
|
31 selects one of the radio buttons and submits the form, it'll send the |
|
32 POST data ``choice=3``. This is HTML Forms 101. |
|
33 |
|
34 * We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we |
|
35 set ``method="post"``. Using ``method="post"`` (as opposed to |
|
36 ``method="get"``) is very important, because the act of submitting this |
|
37 form will alter data server-side. Whenever you create a form that alters |
|
38 data server-side, use ``method="post"``. This tip isn't specific to |
|
39 Django; it's just good Web development practice. |
|
40 |
|
41 Now, let's create a Django view that handles the submitted data and does |
|
42 something with it. Remember, in `Tutorial 3`_, we created a URLconf for the |
|
43 polls application that includes this line:: |
|
44 |
|
45 (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'), |
|
46 |
|
47 So let's create a ``vote()`` function in ``mysite/polls/views.py``:: |
|
48 |
|
49 from django.shortcuts import get_object_or_404, render_to_response |
|
50 from django.http import HttpResponseRedirect |
|
51 from mysite.polls.models import Choice, Poll |
|
52 # ... |
|
53 def vote(request, poll_id): |
|
54 p = get_object_or_404(Poll, pk=poll_id) |
|
55 try: |
|
56 selected_choice = p.choice_set.get(pk=request.POST['choice']) |
|
57 except (KeyError, Choice.DoesNotExist): |
|
58 # Redisplay the poll voting form. |
|
59 return render_to_response('polls/detail.html', { |
|
60 'poll': p, |
|
61 'error_message': "You didn't select a choice.", |
|
62 }) |
|
63 else: |
|
64 selected_choice.votes += 1 |
|
65 selected_choice.save() |
|
66 # Always return an HttpResponseRedirect after successfully dealing |
|
67 # with POST data. This prevents data from being posted twice if a |
|
68 # user hits the Back button. |
|
69 return HttpResponseRedirect('/polls/%s/results/' % p.id) |
|
70 |
|
71 This code includes a few things we haven't covered yet in this tutorial: |
|
72 |
|
73 * ``request.POST`` is a dictionary-like object that lets you access |
|
74 submitted data by key name. In this case, ``request.POST['choice']`` |
|
75 returns the ID of the selected choice, as a string. ``request.POST`` |
|
76 values are always strings. |
|
77 |
|
78 Note that Django also provides ``request.GET`` for accessing GET data |
|
79 in the same way -- but we're explicitly using ``request.POST`` in our |
|
80 code, to ensure that data is only altered via a POST call. |
|
81 |
|
82 * ``request.POST['choice']`` will raise ``KeyError`` if ``choice`` wasn't |
|
83 provided in POST data. The above code checks for ``KeyError`` and |
|
84 redisplays the poll form with an error message if ``choice`` isn't given. |
|
85 |
|
86 * After incrementing the choice count, the code returns an |
|
87 ``HttpResponseRedirect`` rather than a normal ``HttpResponse``. |
|
88 ``HttpResponseRedirect`` takes a single argument: the URL to which the |
|
89 user will be redirected. You should leave off the "http://" and domain |
|
90 name if you can. That helps your app become portable across domains. |
|
91 |
|
92 As the Python comment above points out, you should always return an |
|
93 ``HttpResponseRedirect`` after successfully dealing with POST data. This |
|
94 tip isn't specific to Django; it's just good Web development practice. |
|
95 |
|
96 As mentioned in Tutorial 3, ``request`` is a ``HTTPRequest`` object. For more |
|
97 on ``HTTPRequest`` objects, see the `request and response documentation`_. |
|
98 |
|
99 After somebody votes in a poll, the ``vote()`` view redirects to the results |
|
100 page for the poll. Let's write that view:: |
|
101 |
|
102 def results(request, poll_id): |
|
103 p = get_object_or_404(Poll, pk=poll_id) |
|
104 return render_to_response('polls/results.html', {'poll': p}) |
|
105 |
|
106 This is almost exactly the same as the ``detail()`` view from `Tutorial 3`_. |
|
107 The only difference is the template name. We'll fix this redundancy later. |
|
108 |
|
109 Now, create a ``results.html`` template:: |
|
110 |
|
111 <h1>{{ poll.question }}</h1> |
|
112 |
|
113 <ul> |
|
114 {% for choice in poll.choice_set.all %} |
|
115 <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> |
|
116 {% endfor %} |
|
117 </ul> |
|
118 |
|
119 Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a |
|
120 results page that gets updated each time you vote. If you submit the form |
|
121 without having chosen a choice, you should see the error message. |
|
122 |
|
123 .. _request and response documentation: ../request_response/ |
|
124 |
|
125 Use generic views: Less code is better |
|
126 ====================================== |
|
127 |
|
128 The ``detail()`` (from `Tutorial 3`_) and ``results()`` views are stupidly |
|
129 simple -- and, as mentioned above, redundant. The ``index()`` view (also from |
|
130 Tutorial 3), which displays a list of polls, is similar. |
|
131 |
|
132 These views represent a common case of basic Web development: getting data from |
|
133 the database according to a parameter passed in the URL, loading a template and |
|
134 returning the rendered template. Because this is so common, Django provides a |
|
135 shortcut, called the "generic views" system. |
|
136 |
|
137 Generic views abstract common patterns to the point where you don't even need |
|
138 to write Python code to write an app. |
|
139 |
|
140 Let's convert our poll app to use the generic views system, so we can delete a |
|
141 bunch of our own code. We'll just have to take a few steps to make the |
|
142 conversion. |
|
143 |
|
144 .. admonition:: Why the code-shuffle? |
|
145 |
|
146 Generally, when writing a Django app, you'll evaluate whether generic views |
|
147 are a good fit for your problem, and you'll use them from the beginning, |
|
148 rather than refactoring your code halfway through. But this tutorial |
|
149 intentionally has focused on writing the views "the hard way" until now, to |
|
150 focus on core concepts. |
|
151 |
|
152 You should know basic math before you start using a calculator. |
|
153 |
|
154 First, open the polls/urls.py URLconf. It looks like this, according to the |
|
155 tutorial so far:: |
|
156 |
|
157 from django.conf.urls.defaults import * |
|
158 |
|
159 urlpatterns = patterns('mysite.polls.views', |
|
160 (r'^$', 'index'), |
|
161 (r'^(?P<poll_id>\d+)/$', 'detail'), |
|
162 (r'^(?P<poll_id>\d+)/results/$', 'results'), |
|
163 (r'^(?P<poll_id>\d+)/vote/$', 'vote'), |
|
164 ) |
|
165 |
|
166 Change it like so:: |
|
167 |
|
168 from django.conf.urls.defaults import * |
|
169 from mysite.polls.models import Poll |
|
170 |
|
171 info_dict = { |
|
172 'queryset': Poll.objects.all(), |
|
173 } |
|
174 |
|
175 urlpatterns = patterns('', |
|
176 (r'^$', 'django.views.generic.list_detail.object_list', info_dict), |
|
177 (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), |
|
178 (r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html')), |
|
179 (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'), |
|
180 ) |
|
181 |
|
182 We're using two generic views here: ``object_list`` and ``object_detail``. |
|
183 Respectively, those two views abstract the concepts of "display a list of |
|
184 objects" and "display a detail page for a particular type of object." |
|
185 |
|
186 * Each generic view needs to know what data it will be acting upon. This |
|
187 data is provided in a dictionary. The ``queryset`` key in this dictionary |
|
188 points to the list of objects to be manipulated by the generic view. |
|
189 |
|
190 * The ``object_detail`` generic view expects the ID value captured |
|
191 from the URL to be called ``"object_id"``, so we've changed ``poll_id`` to |
|
192 ``object_id`` for the generic views. |
|
193 |
|
194 By default, the ``object_detail`` generic view uses a template called |
|
195 ``<app name>/<model name>_detail.html``. In our case, it'll use the template |
|
196 ``"polls/poll_detail.html"``. Thus, rename your ``polls/detail.html`` template to |
|
197 ``polls/poll_detail.html``, and change the ``render_to_response()`` line in |
|
198 ``vote()``. |
|
199 |
|
200 Similarly, the ``object_list`` generic view uses a template called |
|
201 ``<app name>/<model name>_list.html``. Thus, rename ``polls/index.html`` to |
|
202 ``polls/poll_list.html``. |
|
203 |
|
204 Because we have more than one entry in the URLconf that uses ``object_detail`` |
|
205 for the polls app, we manually specify a template name for the results view: |
|
206 ``template_name='polls/results.html'``. Otherwise, both views would use the same |
|
207 template. Note that we use ``dict()`` to return an altered dictionary in place. |
|
208 |
|
209 .. note:: ``all()`` is lazy |
|
210 |
|
211 It might look a little frightening to see ``Poll.objects.all()`` being used |
|
212 in a detail view which only needs one ``Poll`` object, but don't worry; |
|
213 ``Poll.objects.all()`` is actually a special object called a ``QuerySet``, |
|
214 which is "lazy" and doesn't hit your database until it absolutely has to. By |
|
215 the time the database query happens, the ``object_detail`` generic view will |
|
216 have narrowed its scope down to a single object, so the eventual query will |
|
217 only select one row from the database. |
|
218 |
|
219 If you'd like to know more about how that works, The Django database API |
|
220 documentation `explains the lazy nature of QuerySet objects`_. |
|
221 |
|
222 .. _explains the lazy nature of QuerySet objects: ../db_api/#querysets-are-lazy |
|
223 |
|
224 In previous parts of the tutorial, the templates have been provided with a context |
|
225 that contains the ``poll`` and ``latest_poll_list`` context variables. However, |
|
226 the generic views provide the variables ``object`` and ``object_list`` as context. |
|
227 Therefore, you need to change your templates to match the new context variables. |
|
228 Go through your templates, and modify any reference to ``latest_poll_list`` to |
|
229 ``object_list``, and change any reference to ``poll`` to ``object``. |
|
230 |
|
231 You can now delete the ``index()``, ``detail()`` and ``results()`` views |
|
232 from ``polls/views.py``. We don't need them anymore -- they have been replaced |
|
233 by generic views. |
|
234 |
|
235 The ``vote()`` view is still required. However, it must be modified to match |
|
236 the new templates and context variables. Change the template call from |
|
237 ``polls/detail.html`` to ``polls/poll_detail.html``, and pass ``object`` in the |
|
238 context instead of ``poll``. |
|
239 |
|
240 Run the server, and use your new polling app based on generic views. |
|
241 |
|
242 For full details on generic views, see the `generic views documentation`_. |
|
243 |
|
244 .. _generic views documentation: ../generic_views/ |
|
245 |
|
246 Coming soon |
|
247 =========== |
|
248 |
|
249 The tutorial ends here for the time being. But check back soon for the next |
|
250 installments: |
|
251 |
|
252 * Advanced form processing |
|
253 * Using the RSS framework |
|
254 * Using the cache framework |
|
255 * Using the comments framework |
|
256 * Advanced admin features: Permissions |
|
257 * Advanced admin features: Custom JavaScript |
|
258 |
|
259 .. _Tutorial 3: ../tutorial3/ |