| 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/ |
|---|