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