Django

Code

root/django/branches/0.90-bugfixes/docs/tutorial03.txt

Revision 1258, 18.7 kB (checked in by adrian, 3 years ago)

Changed 'django-admin.py startapp' application template to use views.py instead of views package, for simplicity. Updated tutorial to reflect the change.

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