Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added docs/tutorial03

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

0 notes on commit 1f4379b

Please sign in to comment.
Something went wrong with that request. Please try again.