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