|
1 ===================================== |
|
2 Writing your first Django app, part 3 |
|
3 ===================================== |
|
4 |
|
5 This tutorial begins where `Tutorial 2`_ left off. We're continuing the Web-poll |
|
6 application and will focus on creating the public interface -- "views." |
|
7 |
|
8 .. _Tutorial 2: ../tutorial2/ |
|
9 |
|
10 Philosophy |
|
11 ========== |
|
12 |
|
13 A view is a "type" of Web page in your Django application that generally serves |
|
14 a specific function and has a specific template. For example, in a weblog |
|
15 application, you might have the following views: |
|
16 |
|
17 * Blog homepage -- displays the latest few entries. |
|
18 * Entry "detail" page -- permalink page for a single entry. |
|
19 * Year-based archive page -- displays all months with entries in the |
|
20 given year. |
|
21 * Month-based archive page -- displays all days with entries in the |
|
22 given month. |
|
23 * Day-based archive page -- displays all entries in the given day. |
|
24 * Comment action -- handles posting comments to a given entry. |
|
25 |
|
26 In our poll application, we'll have the following four views: |
|
27 |
|
28 * Poll "archive" page -- displays the latest few polls. |
|
29 * Poll "detail" page -- displays a poll question, with no results but |
|
30 with a form to vote. |
|
31 * Poll "results" page -- displays results for a particular poll. |
|
32 * Vote action -- handles voting for a particular choice in a particular |
|
33 poll. |
|
34 |
|
35 In Django, each view is represented by a simple Python function. |
|
36 |
|
37 Design your URLs |
|
38 ================ |
|
39 |
|
40 The first step of writing views is to design your URL structure. You do this by |
|
41 creating a Python module, called a URLconf. URLconfs are how Django associates |
|
42 a given URL with given Python code. |
|
43 |
|
44 When a user requests a Django-powered page, the system looks at the |
|
45 ``ROOT_URLCONF`` setting, which contains a string in Python dotted syntax. |
|
46 Django loads that module and looks for a module-level variable called |
|
47 ``urlpatterns``, which is a sequence of tuples in the following format:: |
|
48 |
|
49 (regular expression, Python callback function [, optional dictionary]) |
|
50 |
|
51 Django starts at the first regular expression and makes its way down the list, |
|
52 comparing the requested URL against each regular expression until it finds one |
|
53 that matches. |
|
54 |
|
55 When it finds a match, Django calls the Python callback function, with an |
|
56 ``HTTPRequest`` object as the first argument, any "captured" values from the |
|
57 regular expression as keyword arguments, and, optionally, arbitrary keyword |
|
58 arguments from the dictionary (an optional third item in the tuple). |
|
59 |
|
60 For more on ``HTTPRequest`` objects, see the `request and response documentation`_. |
|
61 For more details on URLconfs, see the `URLconf documentation`_. |
|
62 |
|
63 When you ran ``python manage.py startproject mysite`` at the beginning of |
|
64 Tutorial 1, it created a default URLconf in ``mysite/urls.py``. It also |
|
65 automatically set your ``ROOT_URLCONF`` setting to point at that file:: |
|
66 |
|
67 ROOT_URLCONF = 'mysite.urls' |
|
68 |
|
69 Time for an example. Edit ``mysite/urls.py`` so it looks like this:: |
|
70 |
|
71 from django.conf.urls.defaults import * |
|
72 |
|
73 urlpatterns = patterns('', |
|
74 (r'^polls/$', 'mysite.polls.views.index'), |
|
75 (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'), |
|
76 (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'), |
|
77 (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'), |
|
78 ) |
|
79 |
|
80 This is worth a review. When somebody requests a page from your Web site -- |
|
81 say, "/polls/23/", Django will load this Python module, because it's pointed to |
|
82 by the ``ROOT_URLCONF`` setting. It finds the variable named ``urlpatterns`` |
|
83 and traverses the regular expressions in order. When it finds a regular |
|
84 expression that matches -- ``r'^polls/(?P<poll_id>\d+)/$'`` -- it loads the |
|
85 associated Python package/module: ``mysite.polls.views.detail``. That |
|
86 corresponds to the function ``detail()`` in ``mysite/polls/views.py``. |
|
87 Finally, it calls that ``detail()`` function like so:: |
|
88 |
|
89 detail(request=<HttpRequest object>, poll_id='23') |
|
90 |
|
91 The ``poll_id='23'`` part comes from ``(?P<poll_id>\d+)``. Using parenthesis around a |
|
92 pattern "captures" the text matched by that pattern and sends it as an argument |
|
93 to the view function; the ``?P<poll_id>`` defines the name that will be used to |
|
94 identify the matched pattern; and ``\d+`` is a regular expression to match a sequence of |
|
95 digits (i.e., a number). |
|
96 |
|
97 Because the URL patterns are regular expressions, there really is no limit on |
|
98 what you can do with them. And there's no need to add URL cruft such as |
|
99 ``.php`` -- unless you have a sick sense of humor, in which case you can do |
|
100 something like this:: |
|
101 |
|
102 (r'^polls/latest\.php$', 'mysite.polls.views.index'), |
|
103 |
|
104 But, don't do that. It's silly. |
|
105 |
|
106 Note that these regular expressions do not search GET and POST parameters, or |
|
107 the domain name. For example, in a request to ``http://www.example.com/myapp/``, |
|
108 the URLconf will look for ``/myapp/``. In a request to |
|
109 ``http://www.example.com/myapp/?page=3``, the URLconf will look for ``/myapp/``. |
|
110 |
|
111 If you need help with regular expressions, see `Wikipedia's entry`_ and the |
|
112 `Python documentation`_. Also, the O'Reilly book "Mastering Regular |
|
113 Expressions" by Jeffrey Friedl is fantastic. |
|
114 |
|
115 Finally, a performance note: these regular expressions are compiled the first |
|
116 time the URLconf module is loaded. They're super fast. |
|
117 |
|
118 .. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression |
|
119 .. _Python documentation: http://www.python.org/doc/current/lib/module-re.html |
|
120 .. _request and response documentation: ../request_response/ |
|
121 .. _URLconf documentation: ../url_dispatch/ |
|
122 |
|
123 Write your first view |
|
124 ===================== |
|
125 |
|
126 Well, we haven't created any views yet -- we just have the URLconf. But let's |
|
127 make sure Django is following the URLconf properly. |
|
128 |
|
129 Fire up the Django development Web server:: |
|
130 |
|
131 python manage.py runserver |
|
132 |
|
133 Now go to "http://localhost:8000/polls/" on your domain in your Web browser. |
|
134 You should get a pleasantly-colored error page with the following message:: |
|
135 |
|
136 ViewDoesNotExist at /polls/ |
|
137 |
|
138 Tried index in module mysite.polls.views. Error was: 'module' |
|
139 object has no attribute 'index' |
|
140 |
|
141 This error happened because you haven't written a function ``index()`` in the |
|
142 module ``mysite/polls/views.py``. |
|
143 |
|
144 Try "/polls/23/", "/polls/23/results/" and "/polls/23/vote/". The error |
|
145 messages tell you which view Django tried (and failed to find, because you |
|
146 haven't written any views yet). |
|
147 |
|
148 Time to write the first view. Open the file ``mysite/polls/views.py`` |
|
149 and put the following Python code in it:: |
|
150 |
|
151 from django.http import HttpResponse |
|
152 |
|
153 def index(request): |
|
154 return HttpResponse("Hello, world. You're at the poll index.") |
|
155 |
|
156 This is the simplest view possible. Go to "/polls/" in your browser, and you |
|
157 should see your text. |
|
158 |
|
159 Now add the following view. It's slightly different, because it takes an |
|
160 argument (which, remember, is passed in from whatever was captured by the |
|
161 regular expression in the URLconf):: |
|
162 |
|
163 def detail(request, poll_id): |
|
164 return HttpResponse("You're looking at poll %s." % poll_id) |
|
165 |
|
166 Take a look in your browser, at "/polls/34/". It'll display whatever ID you |
|
167 provide in the URL. |
|
168 |
|
169 Write views that actually do something |
|
170 ====================================== |
|
171 |
|
172 Each view is responsible for doing one of two things: Returning an ``HttpResponse`` |
|
173 object containing the content for the requested page, or raising an exception |
|
174 such as ``Http404``. The rest is up to you. |
|
175 |
|
176 Your view can read records from a database, or not. It can use a template |
|
177 system such as Django's -- or a third-party Python template system -- or not. |
|
178 It can generate a PDF file, output XML, create a ZIP file on the fly, anything |
|
179 you want, using whatever Python libraries you want. |
|
180 |
|
181 All Django wants is that ``HttpResponse``. Or an exception. |
|
182 |
|
183 Because it's convenient, let's use Django's own database API, which we covered |
|
184 in Tutorial 1. Here's one stab at the ``index()`` view, which displays the |
|
185 latest 5 poll questions in the system, separated by commas, according to |
|
186 publication date:: |
|
187 |
|
188 from mysite.polls.models import Poll |
|
189 from django.http import HttpResponse |
|
190 |
|
191 def index(request): |
|
192 latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] |
|
193 output = ', '.join([p.question for p in latest_poll_list]) |
|
194 return HttpResponse(output) |
|
195 |
|
196 There's a problem here, though: The page's design is hard-coded in the view. If |
|
197 you want to change the way the page looks, you'll have to edit this Python code. |
|
198 So let's use Django's template system to separate the design from Python:: |
|
199 |
|
200 from django.template import Context, loader |
|
201 from mysite.polls.models import Poll |
|
202 from django.http import HttpResponse |
|
203 |
|
204 def index(request): |
|
205 latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] |
|
206 t = loader.get_template('polls/index.html') |
|
207 c = Context({ |
|
208 'latest_poll_list': latest_poll_list, |
|
209 }) |
|
210 return HttpResponse(t.render(c)) |
|
211 |
|
212 That code loads the template called "polls/index.html" and passes it a context. The |
|
213 context is a dictionary mapping template variable names to Python objects. |
|
214 |
|
215 Reload the page. Now you'll see an error:: |
|
216 |
|
217 TemplateDoesNotExist at /polls/ |
|
218 polls/index.html |
|
219 |
|
220 Ah. There's no template yet. First, create a directory, somewhere on your |
|
221 filesystem, whose contents Django can access. (Django runs as whatever user |
|
222 your server runs.) Don't put them under your document root, though. You |
|
223 probably shouldn't make them public, just for security's sake. |
|
224 |
|
225 Then edit ``TEMPLATE_DIRS`` in your ``settings.py`` to tell Django where it can |
|
226 find templates -- just as you did in the "Customize the admin look and feel" |
|
227 section of Tutorial 2. |
|
228 |
|
229 When you've done that, create a directory ``polls`` in your template directory. |
|
230 Within that, create a file called ``index.html``. Note that our |
|
231 ``loader.get_template('polls/index.html')`` code from above maps to |
|
232 "[template_directory]/polls/index.html" on the filesystem. |
|
233 |
|
234 Put the following code in that template:: |
|
235 |
|
236 {% if latest_poll_list %} |
|
237 <ul> |
|
238 {% for poll in latest_poll_list %} |
|
239 <li>{{ poll.question }}</li> |
|
240 {% endfor %} |
|
241 </ul> |
|
242 {% else %} |
|
243 <p>No polls are available.</p> |
|
244 {% endif %} |
|
245 |
|
246 Load the page in your Web browser, and you should see a bulleted-list |
|
247 containing the "What's up" poll from Tutorial 1. |
|
248 |
|
249 A shortcut: render_to_response() |
|
250 -------------------------------- |
|
251 |
|
252 It's a very common idiom to load a template, fill a context and return an |
|
253 ``HttpResponse`` object with the result of the rendered template. Django |
|
254 provides a shortcut. Here's the full ``index()`` view, rewritten:: |
|
255 |
|
256 from django.shortcuts import render_to_response |
|
257 from mysite.polls.models import Poll |
|
258 |
|
259 def index(request): |
|
260 latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] |
|
261 return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list}) |
|
262 |
|
263 Note that once we've done this in all these views, we no longer need to import ``loader``, ``Context`` and ``HttpResponse``. |
|
264 |
|
265 The ``render_to_response()`` function takes a template name as its first |
|
266 argument and a dictionary as its optional second argument. It returns an |
|
267 ``HttpResponse`` object of the given template rendered with the given context. |
|
268 |
|
269 Raising 404 |
|
270 =========== |
|
271 |
|
272 Now, let's tackle the poll detail view -- the page that displays the question |
|
273 for a given poll. Here's the view:: |
|
274 |
|
275 from django.http import Http404 |
|
276 # ... |
|
277 def detail(request, poll_id): |
|
278 try: |
|
279 p = Poll.objects.get(pk=poll_id) |
|
280 except Poll.DoesNotExist: |
|
281 raise Http404 |
|
282 return render_to_response('polls/detail.html', {'poll': p}) |
|
283 |
|
284 The new concept here: The view raises the ``django.http.Http404`` |
|
285 exception if a poll with the requested ID doesn't exist. |
|
286 |
|
287 A shortcut: get_object_or_404() |
|
288 ------------------------------- |
|
289 |
|
290 It's a very common idiom to use ``get()`` and raise ``Http404`` if the |
|
291 object doesn't exist. Django provides a shortcut. Here's the ``detail()`` view, |
|
292 rewritten:: |
|
293 |
|
294 from django.shortcuts import render_to_response, get_object_or_404 |
|
295 # ... |
|
296 def detail(request, poll_id): |
|
297 p = get_object_or_404(Poll, pk=poll_id) |
|
298 return render_to_response('polls/detail.html', {'poll': p}) |
|
299 |
|
300 The ``get_object_or_404()`` function takes a Django model module as its first |
|
301 argument and an arbitrary number of keyword arguments, which it passes to the |
|
302 module's ``get()`` function. It raises ``Http404`` if the object doesn't |
|
303 exist. |
|
304 |
|
305 .. admonition:: Philosophy |
|
306 |
|
307 Why do we use a helper function ``get_object_or_404()`` instead of |
|
308 automatically catching the ``DoesNotExist`` exceptions at a higher level, |
|
309 or having the model API raise ``Http404`` instead of ``DoesNotExist``? |
|
310 |
|
311 Because that would couple the model layer to the view layer. One of the |
|
312 foremost design goals of Django is to maintain loose coupling. |
|
313 |
|
314 There's also a ``get_list_or_404()`` function, which works just as |
|
315 ``get_object_or_404()`` -- except using ``filter()`` instead of |
|
316 ``get()``. It raises ``Http404`` if the list is empty. |
|
317 |
|
318 Write a 404 (page not found) view |
|
319 ================================= |
|
320 |
|
321 When you raise ``Http404`` from within a view, Django will load a special view |
|
322 devoted to handling 404 errors. It finds it by looking for the variable |
|
323 ``handler404``, which is a string in Python dotted syntax -- the same format |
|
324 the normal URLconf callbacks use. A 404 view itself has nothing special: It's |
|
325 just a normal view. |
|
326 |
|
327 You normally won't have to bother with writing 404 views. By default, URLconfs |
|
328 have the following line up top:: |
|
329 |
|
330 from django.conf.urls.defaults import * |
|
331 |
|
332 That takes care of setting ``handler404`` in the current module. As you can see |
|
333 in ``django/conf/urls/defaults.py``, ``handler404`` is set to |
|
334 ``'django.views.defaults.page_not_found'`` by default. |
|
335 |
|
336 Three more things to note about 404 views: |
|
337 |
|
338 * The 404 view is also called if Django doesn't find a match after checking |
|
339 every regular expression in the URLconf. |
|
340 * If you don't define your own 404 view -- and simply use the default, |
|
341 which is recommended -- you still have one obligation: To create a |
|
342 ``404.html`` template in the root of your template directory. The default |
|
343 404 view will use that template for all 404 errors. |
|
344 * If ``DEBUG`` is set to ``True`` (in your settings module) then your 404 |
|
345 view will never be used, and the traceback will be displayed instead. |
|
346 |
|
347 Write a 500 (server error) view |
|
348 =============================== |
|
349 |
|
350 Similarly, URLconfs may define a ``handler500``, which points to a view to call |
|
351 in case of server errors. Server errors happen when you have runtime errors in |
|
352 view code. |
|
353 |
|
354 Use the template system |
|
355 ======================= |
|
356 |
|
357 Back to our ``polls.detail`` view. Given the context variable ``poll``, here's |
|
358 what the template might look like:: |
|
359 |
|
360 <h1>{{ poll.question }}</h1> |
|
361 <ul> |
|
362 {% for choice in poll.choice_set.all %} |
|
363 <li>{{ choice.choice }}</li> |
|
364 {% endfor %} |
|
365 </ul> |
|
366 |
|
367 The template system uses dot-lookup syntax to access variable attributes. In |
|
368 the example of ``{{ poll.question }}``, first Django does a dictionary lookup |
|
369 on the object ``poll``. Failing that, it tries attribute lookup -- which works, |
|
370 in this case. If attribute lookup had failed, it would've tried calling the |
|
371 method ``question()`` on the poll object. |
|
372 |
|
373 Method-calling happens in the ``{% for %}`` loop: ``poll.choice_set.all`` is |
|
374 interpreted as the Python code ``poll.choice_set.all()``, which returns an |
|
375 iterable of Choice objects and is suitable for use in the ``{% for %}`` tag. |
|
376 |
|
377 See the `template guide`_ for full details on how templates work. |
|
378 |
|
379 .. _template guide: ../templates/ |
|
380 |
|
381 Simplifying the URLconfs |
|
382 ======================== |
|
383 |
|
384 Take some time to play around with the views and template system. As you edit |
|
385 the URLconf, you may notice there's a fair bit of redundancy in it:: |
|
386 |
|
387 urlpatterns = patterns('', |
|
388 (r'^polls/$', 'mysite.polls.views.index'), |
|
389 (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'), |
|
390 (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'), |
|
391 (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'), |
|
392 ) |
|
393 |
|
394 Namely, ``mysite.polls.views`` is in every callback. |
|
395 |
|
396 Because this is a common case, the URLconf framework provides a shortcut for |
|
397 common prefixes. You can factor out the common prefixes and add them as the |
|
398 first argument to ``patterns()``, like so:: |
|
399 |
|
400 urlpatterns = patterns('mysite.polls.views', |
|
401 (r'^polls/$', 'index'), |
|
402 (r'^polls/(?P<poll_id>\d+)/$', 'detail'), |
|
403 (r'^polls/(?P<poll_id>\d+)/results/$', 'results'), |
|
404 (r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'), |
|
405 ) |
|
406 |
|
407 This is functionally identical to the previous formatting. It's just a bit |
|
408 tidier. |
|
409 |
|
410 Decoupling the URLconfs |
|
411 ======================= |
|
412 |
|
413 While we're at it, we should take the time to decouple our poll-app URLs from |
|
414 our Django project configuration. Django apps are meant to be pluggable -- that |
|
415 is, each particular app should be transferrable to another Django installation |
|
416 with minimal fuss. |
|
417 |
|
418 Our poll app is pretty decoupled at this point, thanks to the strict directory |
|
419 structure that ``python manage.py startapp`` created, but one part of it is |
|
420 coupled to the Django settings: The URLconf. |
|
421 |
|
422 We've been editing the URLs in ``mysite/urls.py``, but the URL design of an |
|
423 app is specific to the app, not to the Django installation -- so let's move the |
|
424 URLs within the app directory. |
|
425 |
|
426 Copy the file ``mysite/urls.py`` to ``mysite/polls/urls.py``. Then, |
|
427 change ``mysite/urls.py`` to remove the poll-specific URLs and insert an |
|
428 ``include()``:: |
|
429 |
|
430 (r'^polls/', include('mysite.polls.urls')), |
|
431 |
|
432 ``include()``, simply, references another URLconf. Note that the regular |
|
433 expression doesn't have a ``$`` (end-of-string match character) but has the |
|
434 trailing slash. Whenever Django encounters ``include()``, it chops off whatever |
|
435 part of the URL matched up to that point and sends the remaining string to the |
|
436 included URLconf for further processing. |
|
437 |
|
438 Here's what happens if a user goes to "/polls/34/" in this system: |
|
439 |
|
440 * Django will find the match at ``'^polls/'`` |
|
441 * It will strip off the matching text (``"polls/"``) and send the remaining |
|
442 text -- ``"34/"`` -- to the 'mysite.polls.urls' urlconf for |
|
443 further processing. |
|
444 |
|
445 Now that we've decoupled that, we need to decouple the |
|
446 'mysite.polls.urls' urlconf by removing the leading "polls/" from each |
|
447 line:: |
|
448 |
|
449 urlpatterns = patterns('mysite.polls.views', |
|
450 (r'^$', 'index'), |
|
451 (r'^(?P<poll_id>\d+)/$', 'detail'), |
|
452 (r'^(?P<poll_id>\d+)/results/$', 'results'), |
|
453 (r'^(?P<poll_id>\d+)/vote/$', 'vote'), |
|
454 ) |
|
455 |
|
456 The idea behind ``include()`` and URLconf decoupling is to make it easy to |
|
457 plug-and-play URLs. Now that polls are in their own URLconf, they can be placed |
|
458 under "/polls/", or under "/fun_polls/", or under "/content/polls/", or any |
|
459 other URL root, and the app will still work. |
|
460 |
|
461 All the poll app cares about is its relative URLs, not its absolute URLs. |
|
462 |
|
463 When you're comfortable with writing views, read `part 4 of this tutorial`_ to |
|
464 learn about simple form processing and generic views. |
|
465 |
|
466 .. _part 4 of this tutorial: ../tutorial4/ |