parts/django/docs/intro/tutorial03.txt
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     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.