PageRenderTime 891ms CodeModel.GetById 644ms app.highlight 4ms RepoModel.GetById 238ms app.codeStats 0ms

/docs/intro/tutorial03.txt

https://code.google.com/p/mango-py/
Plain Text | 546 lines | 400 code | 146 blank | 0 comment | 0 complexity | bc4cedc83dd81699d069d2f31a02b7d6 MD5 | raw file
  1=====================================
  2Writing your first Django app, part 3
  3=====================================
  4
  5This tutorial begins where :doc:`Tutorial 2 </intro/tutorial02>` left off. We're
  6continuing the Web-poll application and will focus on creating the public
  7interface -- "views."
  8
  9Philosophy
 10==========
 11
 12A view is a "type" of Web page in your Django application that generally serves
 13a specific function and has a specific template. For example, in a Weblog
 14application, 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
 30In our poll application, we'll have the following four views:
 31
 32    * Poll "index" 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
 42In Django, each view is represented by a simple Python function.
 43
 44Design your URLs
 45================
 46
 47The first step of writing views is to design your URL structure. You do this by
 48creating a Python module, called a URLconf. URLconfs are how Django associates
 49a given URL with given Python code.
 50
 51When a user requests a Django-powered page, the system looks at the
 52:setting:`ROOT_URLCONF` setting, which contains a string in Python dotted
 53syntax. 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
 58Django starts at the first regular expression and makes its way down the list,
 59comparing the requested URL against each regular expression until it finds one
 60that matches.
 61
 62When it finds a match, Django calls the Python callback function, with an
 63:class:`~django.http.HttpRequest` object as the first argument, any "captured"
 64values from the regular expression as keyword arguments, and, optionally,
 65arbitrary keyword arguments from the dictionary (an optional third item in the
 66tuple).
 67
 68For 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
 72When you ran ``django-admin.py startproject mysite`` at the beginning of
 73Tutorial 1, it created a default URLconf in ``mysite/urls.py``. It also
 74automatically set your :setting:`ROOT_URLCONF` setting (in ``settings.py``) to
 75point at that file::
 76
 77    ROOT_URLCONF = 'mysite.urls'
 78
 79Time 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
 94This 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
 96the :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
 97and traverses the regular expressions in order. When it finds a regular
 98expression that matches -- ``r'^polls/(?P<poll_id>\d+)/$'`` -- it loads the
 99function ``detail()`` from ``polls/views.py``. Finally, it calls that
100``detail()`` function like so::
101
102    detail(request=<HttpRequest object>, poll_id='23')
103
104The ``poll_id='23'`` part comes from ``(?P<poll_id>\d+)``. Using parentheses
105around a pattern "captures" the text matched by that pattern and sends it as an
106argument to the view function; the ``?P<poll_id>`` defines the name that will be
107used to identify the matched pattern; and ``\d+`` is a regular expression to
108match a sequence of digits (i.e., a number).
109
110Because the URL patterns are regular expressions, there really is no limit on
111what 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
113like this::
114
115    (r'^polls/latest\.php$', 'polls.views.index'),
116
117But, don't do that. It's silly.
118
119Note that these regular expressions do not search GET and POST parameters, or
120the domain name. For example, in a request to ``http://www.example.com/myapp/``,
121the URLconf will look for ``myapp/``. In a request to
122``http://www.example.com/myapp/?page=3``, the URLconf will look for ``myapp/``.
123
124If you need help with regular expressions, see `Wikipedia's entry`_ and the
125`Python documentation`_. Also, the O'Reilly book "Mastering Regular Expressions"
126by Jeffrey Friedl is fantastic.
127
128Finally, a performance note: these regular expressions are compiled the first
129time 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
134Write your first view
135=====================
136
137Well, we haven't created any views yet -- we just have the URLconf. But let's
138make sure Django is following the URLconf properly.
139
140Fire up the Django development Web server:
141
142.. code-block:: bash
143
144    python manage.py runserver
145
146Now go to "http://localhost:8000/polls/" on your domain in your Web browser.
147You 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
154This error happened because you haven't written a function ``index()`` in the
155module ``polls/views.py``.
156
157Try "/polls/23/", "/polls/23/results/" and "/polls/23/vote/". The error
158messages tell you which view Django tried (and failed to find, because you
159haven't written any views yet).
160
161Time to write the first view. Open the file ``polls/views.py``
162and 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
169This is the simplest view possible. Go to "/polls/" in your browser, and you
170should see your text.
171
172Now lets add a few more views. These views are slightly different, because
173they take an argument (which, remember, is passed in from whatever was
174captured 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
185Take a look in your browser, at "/polls/34/". It'll run the `detail()` method
186and 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
188pages.
189
190Write views that actually do something
191======================================
192
193Each view is responsible for doing one of two things: Returning an
194:class:`~django.http.HttpResponse` object containing the content for the
195requested page, or raising an exception such as :exc:`~django.http.Http404`. The
196rest is up to you.
197
198Your view can read records from a database, or not. It can use a template
199system such as Django's -- or a third-party Python template system -- or not.
200It can generate a PDF file, output XML, create a ZIP file on the fly, anything
201you want, using whatever Python libraries you want.
202
203All Django wants is that :class:`~django.http.HttpResponse`. Or an exception.
204
205Because it's convenient, let's use Django's own database API, which we covered
206in :doc:`Tutorial 1 </intro/tutorial01>`. Here's one stab at the ``index()``
207view, which displays the latest 5 poll questions in the system, separated by
208commas, 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
218There's a problem here, though: The page's design is hard-coded in the view. If
219you want to change the way the page looks, you'll have to edit this Python code.
220So 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
234That code loads the template called "polls/index.html" and passes it a context.
235The context is a dictionary mapping template variable names to Python objects.
236
237Reload the page. Now you'll see an error::
238
239    TemplateDoesNotExist at /polls/
240    polls/index.html
241
242Ah. There's no template yet. First, create a directory, somewhere on your
243filesystem, whose contents Django can access. (Django runs as whatever user your
244server runs.) Don't put them under your document root, though. You probably
245shouldn't make them public, just for security's sake.
246Then edit :setting:`TEMPLATE_DIRS` in your ``settings.py`` to tell Django where
247it can find templates -- just as you did in the "Customize the admin look and
248feel" section of Tutorial 2.
249
250When you've done that, create a directory ``polls`` in your template directory.
251Within 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
255Put 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
269Load the page in your Web browser, and you should see a bulleted-list
270containing the "What's up" poll from Tutorial 1. The link points to the poll's
271detail page.
272
273A shortcut: render_to_response()
274--------------------------------
275
276It'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
278template. Django provides a shortcut. Here's the full ``index()`` view,
279rewritten::
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
288Note 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
292The :func:`~django.shortcuts.render_to_response` function takes a template name
293as its first argument and a dictionary as its optional second argument. It
294returns an :class:`~django.http.HttpResponse` object of the given template
295rendered with the given context.
296
297Raising 404
298===========
299
300Now, let's tackle the poll detail view -- the page that displays the question
301for 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
312The new concept here: The view raises the :exc:`~django.http.Http404` exception
313if a poll with the requested ID doesn't exist.
314
315We'll discuss what you could put in that ``polls/detail.html`` template a bit
316later, but if you'd like to quickly get the above example working, just::
317
318    {{ poll }}
319
320will get you started for now.
321
322A shortcut: get_object_or_404()
323-------------------------------
324
325It'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
327shortcut. 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
335The :func:`~django.shortcuts.get_object_or_404` function takes a Django model
336as its first argument and an arbitrary number of keyword arguments, which it
337passes to the module's :meth:`~django.db.models.QuerySet.get` function. It
338raises :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
351There's also a :func:`~django.shortcuts.get_list_or_404` function, which works
352just 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
355the list is empty.
356
357Write a 404 (page not found) view
358=================================
359
360When you raise :exc:`~django.http.Http404` from within a view, Django will load
361a special view devoted to handling 404 errors. It finds it by looking for the
362variable ``handler404``, which is a string in Python dotted syntax -- the same
363format the normal URLconf callbacks use. A 404 view itself has nothing special:
364It's just a normal view.
365
366You normally won't have to bother with writing 404 views. By default, URLconfs
367have the following line up top::
368
369    from django.conf.urls.defaults import *
370
371That takes care of setting ``handler404`` in the current module. As you can see
372in ``django/conf/urls/defaults.py``, ``handler404`` is set to
373:func:`django.views.defaults.page_not_found` by default.
374
375Four 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
393Write a 500 (server error) view
394===============================
395
396Similarly, URLconfs may define a ``handler500``, which points to a view to call
397in case of server errors. Server errors happen when you have runtime errors in
398view code.
399
400Use the template system
401=======================
402
403Back to the ``detail()`` view for our poll application. Given the context
404variable ``poll``, here's what the "polls/detail.html" template might look
405like:
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
416The template system uses dot-lookup syntax to access variable attributes. In
417the example of ``{{ poll.question }}``, first Django does a dictionary lookup
418on the object ``poll``. Failing that, it tries an attribute lookup -- which
419works, in this case. If attribute lookup had failed, it would've tried a
420list-index lookup.
421
422Method-calling happens in the ``{% for %}`` loop: ``poll.choice_set.all`` is
423interpreted as the Python code ``poll.choice_set.all()``, which returns an
424iterable of Choice objects and is suitable for use in the ``{% for %}`` tag.
425
426See the :doc:`template guide </topics/templates>` for more about templates.
427
428Simplifying the URLconfs
429========================
430
431Take some time to play around with the views and template system. As you edit
432the 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
441Namely, ``polls.views`` is in every callback.
442
443Because this is a common case, the URLconf framework provides a shortcut for
444common prefixes. You can factor out the common prefixes and add them as the
445first 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
454This is functionally identical to the previous formatting. It's just a bit
455tidier.
456
457Since you generally don't want the prefix for one app to be applied to every
458callback in your URLconf, you can concatenate multiple
459:func:`~django.conf.urls.defaults.patterns`. Your full ``mysite/urls.py`` might
460now 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
478Decoupling the URLconfs
479=======================
480
481While we're at it, we should take the time to decouple our poll-app URLs from
482our Django project configuration. Django apps are meant to be pluggable -- that
483is, each particular app should be transferable to another Django installation
484with minimal fuss.
485
486Our poll app is pretty decoupled at this point, thanks to the strict directory
487structure that ``python manage.py startapp`` created, but one part of it is
488coupled to the Django settings: The URLconf.
489
490We've been editing the URLs in ``mysite/urls.py``, but the URL design of an
491app is specific to the app, not to the Django installation -- so let's move the
492URLs within the app directory.
493
494Copy 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.
510Note that the regular expression doesn't have a ``$`` (end-of-string match
511character) but has the trailing slash. Whenever Django encounters
512:func:`~django.conf.urls.defaults.include`, it chops off whatever part of the
513URL matched up to that point and sends the remaining string to the included
514URLconf for further processing.
515
516Here'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
524Now that we've decoupled that, we need to decouple the ``polls.urls``
525URLconf by removing the leading "polls/" from each line, and removing the
526lines registering the admin site. Your ``polls/urls.py`` file should now look like
527this::
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
538The idea behind :func:`~django.conf.urls.defaults.include` and URLconf
539decoupling is to make it easy to plug-and-play URLs. Now that polls are in their
540own URLconf, they can be placed under "/polls/", or under "/fun_polls/", or
541under "/content/polls/", or any other path root, and the app will still work.
542
543All the poll app cares about is its relative path, not its absolute path.
544
545When 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.