diff options
Diffstat (limited to 'docs/tutorial03.txt')
| -rw-r--r-- | docs/tutorial03.txt | 125 |
1 files changed, 63 insertions, 62 deletions
diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt index c5367270ab..6433831a73 100644 --- a/docs/tutorial03.txt +++ b/docs/tutorial03.txt @@ -2,8 +2,6 @@ Writing your first Django app, part 3 ===================================== -By Adrian Holovaty <holovaty@gmail.com> - This tutorial begins where `Tutorial 2`_ left off. We're continuing the Web-poll application and will focus on creating the public interface -- "views." @@ -62,44 +60,46 @@ arguments from the dictionary (an optional third item in the tuple). For more on ``HTTPRequest`` objects, see the `request and response documentation`_. For more details on URLconfs, see the `URLconf documentation`_. -When you ran ``python manage.py startproject myproject`` at the beginning of -Tutorial 1, it created a default URLconf in ``myproject/urls.py``. It also +When you ran ``python manage.py startproject mysite`` at the beginning of +Tutorial 1, it created a default URLconf in ``mysite/urls.py``. It also automatically set your ``ROOT_URLCONF`` setting to point at that file:: - ROOT_URLCONF = 'myproject.urls' + ROOT_URLCONF = 'mysite.urls' -Time for an example. Edit ``myproject/urls.py`` so it looks like this:: +Time for an example. Edit ``mysite/urls.py`` so it looks like this:: from django.conf.urls.defaults import * urlpatterns = patterns('', - (r'^polls/$', 'myproject.polls.views.index'), - (r'^polls/(\d+)/$', 'myproject.polls.views.detail'), - (r'^polls/(\d+)/results/$', 'myproject.polls.views.results'), - (r'^polls/(\d+)/vote/$', 'myproject.polls.views.vote'), + (r'^polls/$', 'mysite.polls.views.index'), + (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'), + (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'), + (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'), ) This is worth a review. When somebody requests a page from your Web site -- say, "/polls/23/", Django will load this Python module, because it's pointed to by the ``ROOT_URLCONF`` setting. It finds the variable named ``urlpatterns`` and traverses the regular expressions in order. When it finds a regular -expression that matches -- ``r'^polls/(\d+)/$'`` -- it loads the -associated Python package/module: ``myproject.polls.views.detail``. That -corresponds to the function ``detail()`` in ``myproject/polls/views.py``. +expression that matches -- ``r'^polls/(?P<poll_id>\d+)/$'`` -- it loads the +associated Python package/module: ``mysite.polls.views.detail``. That +corresponds to the function ``detail()`` in ``mysite/polls/views.py``. Finally, it calls that ``detail()`` function like so:: detail(request=<HttpRequest object>, poll_id='23') -The ``poll_id='23'`` part comes from ``(\d+)``. Using parenthesis around a +The ``poll_id='23'`` part comes from ``(?P<poll_id>\d+)``. Using parenthesis around a pattern "captures" the text matched by that pattern and sends it as an argument -to the view function. +to the view function; the ``?P<poll_id>`` defines the name that will be used to +identify the matched pattern; and \d+ is a regular experession to match a sequence of +digits (i.e., a number). Because the URL patterns are regular expressions, there really is no limit on what you can do with them. And there's no need to add URL cruft such as ``.php`` -- unless you have a sick sense of humor, in which case you can do something like this:: - (r'^polls/latest\.php$', 'myproject.polls.views.index'), + (r'^polls/latest\.php$', 'mysite.polls.views.index'), But, don't do that. It's silly. @@ -135,20 +135,20 @@ You should get a pleasantly-colored error page with the following message:: ViewDoesNotExist at /polls/ - Tried index in module myproject.polls.views. Error was: 'module' + Tried index in module mysite.polls.views. Error was: 'module' object has no attribute 'index' This error happened because you haven't written a function ``index()`` in the -module ``myproject/polls/views.py``. +module ``mysite/polls/views.py``. Try "/polls/23/", "/polls/23/results/" and "/polls/23/vote/". The error messages tell you which view Django tried (and failed to find, because you haven't written any views yet). -Time to write the first view. Open the file ``myproject/polls/views.py`` +Time to write the first view. Open the file ``mysite/polls/views.py`` and put the following Python code in it:: - from django.utils.httpwrappers import HttpResponse + from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the poll index.") @@ -185,11 +185,11 @@ in Tutorial 1. Here's one stab at the ``index()`` view, which displays the latest 5 poll questions in the system, separated by commas, according to publication date:: - from django.models.polls import polls - from django.utils.httpwrappers import HttpResponse + from mysite.polls.models import Poll + from django.http import HttpResponse def index(request): - latest_poll_list = polls.get_list(order_by=['-pub_date'], limit=5) + latest_poll_list = Poll.objects.all().order_by('-pub_date') output = ', '.join([p.question for p in latest_poll_list]) return HttpResponse(output) @@ -197,13 +197,13 @@ There's a problem here, though: The page's design is hard-coded in the view. If you want to change the way the page looks, you'll have to edit this Python code. So let's use Django's template system to separate the design from Python:: - from django.core.template import Context, loader - from django.models.polls import polls - from django.utils.httpwrappers import HttpResponse + from django.template import Context, loader + from mysite.polls.models import Poll + from django.http import HttpResponse def index(request): - latest_poll_list = polls.get_list(order_by=['-pub_date'], limit=5) - t = loader.get_template('polls/index') + latest_poll_list = Poll.objects.all().order_by('-pub_date') + t = loader.get_template('polls/index.html') c = Context({ 'latest_poll_list': latest_poll_list, }) @@ -227,9 +227,8 @@ find templates -- just as you did in the "Customize the admin look and feel" section of Tutorial 2. When you've done that, create a directory ``polls`` in your template directory. -Within that, create a file called ``index.html``. Django requires that -templates have ".html" extension. Note that our -``loader.get_template('polls/index')`` code from above maps to +Within that, create a file called ``index.html``. Note that our +``loader.get_template('polls/index.html')`` code from above maps to "[template_directory]/polls/index.html" on the filesystem. Put the following code in that template:: @@ -254,12 +253,12 @@ It's a very common idiom to load a template, fill a context and return an ``HttpResponse`` object with the result of the rendered template. Django provides a shortcut. Here's the full ``index()`` view, rewritten:: - from django.core.extensions import render_to_response - from django.models.polls import polls + from django.shortcuts import render_to_response + from mysite.polls.models import Poll def index(request): - latest_poll_list = polls.get_list(order_by=['-pub_date'], limit=5) - return render_to_response('polls/index', {'latest_poll_list': latest_poll_list}) + latest_poll_list = Poll.objects.all().order_by('-pub_date') + return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list}) Note that we no longer need to import ``loader``, ``Context`` or ``HttpResponse``. @@ -274,15 +273,16 @@ Raising 404 Now, let's tackle the poll detail view -- the page that displays the question for a given poll. Here's the view:: - from django.core.exceptions import Http404 + from django.http import Http404 + # ... def detail(request, poll_id): try: - p = polls.get_object(pk=poll_id) - except polls.PollDoesNotExist: + p = Poll.objects.get(pk=poll_id) + except Poll.DoesNotExist: raise Http404 - return render_to_response('polls/detail', {'poll': p}) + return render_to_response('polls/detail.html', {'poll': p}) -The new concept here: The view raises the ``django.core.exceptions.Http404`` +The new concept here: The view raises the ``django.http.Http404`` exception if a poll with the requested ID doesn't exist. A shortcut: get_object_or_404() @@ -292,10 +292,11 @@ It's a very common idiom to use ``get_object()`` and raise ``Http404`` if the object doesn't exist. Django provides a shortcut. Here's the ``detail()`` view, rewritten:: - from django.core.extensions import get_object_or_404 + from django.shortcuts import render_to_response, get_object_or_404 + # ... def detail(request, poll_id): - p = get_object_or_404(polls, pk=poll_id) - return render_to_response('polls/detail', {'poll': p}) + p = get_object_or_404(Poll, pk=poll_id) + return render_to_response('polls/detail.html', {'poll': p}) The ``get_object_or_404()`` function takes a Django model module as its first argument and an arbitrary number of keyword arguments, which it passes to the @@ -305,8 +306,8 @@ exist. .. admonition:: Philosophy Why do we use a helper function ``get_object_or_404()`` instead of - automatically catching the ``*DoesNotExist`` exceptions at a higher level, - or having the model API raise ``Http404`` instead of ``*DoesNotExist``? + automatically catching the ``DoesNotExist`` exceptions at a higher level, + or having the model API raise ``Http404`` instead of ``DoesNotExist``? Because that would couple the model layer to the view layer. One of the foremost design goals of Django is to maintain loose coupling. @@ -359,7 +360,7 @@ what the template might look like:: <h1>{{ poll.question }}</h1> <ul> - {% for choice in poll.get_choice_list %} + {% for choice in poll.choice_set.all %} <li>{{ choice.choice }}</li> {% endfor %} </ul> @@ -370,9 +371,9 @@ on the object ``poll``. Failing that, it tries attribute lookup -- which works, in this case. If attribute lookup had failed, it would've tried calling the method ``question()`` on the poll object. -Method-calling happens in the ``{% for %}`` loop: ``poll.get_choice_list`` is -interpreted as the Python code ``poll.get_choice_list()``, which returns a list -of Choice objects and is suitable for iteration via the ``{% for %}`` tag. +Method-calling happens in the ``{% for %}`` loop: ``poll.choice_set.all`` is +interpreted as the Python code ``poll.choice_set.all()``, which returns an +iterable of Choice objects and is suitable for use in the ``{% for %}`` tag. See the `template guide`_ for full details on how templates work. @@ -385,19 +386,19 @@ Take some time to play around with the views and template system. As you edit the URLconf, you may notice there's a fair bit of redundancy in it:: urlpatterns = patterns('', - (r'^polls/$', 'myproject.polls.views.index'), - (r'^polls/(?P<poll_id>\d+)/$', 'myproject.polls.views.detail'), - (r'^polls/(?P<poll_id>\d+)/results/$', 'myproject.polls.views.results'), - (r'^polls/(?P<poll_id>\d+)/vote/$', 'myproject.polls.views.vote'), + (r'^polls/$', 'mysite.polls.views.index'), + (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'), + (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'), + (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'), ) -Namely, ``myproject.polls.views`` is in every callback. +Namely, ``mysite.polls.views`` is in every callback. Because this is a common case, the URLconf framework provides a shortcut for common prefixes. You can factor out the common prefixes and add them as the first argument to ``patterns()``, like so:: - urlpatterns = patterns('myproject.polls.views', + urlpatterns = patterns('mysite.polls.views', (r'^polls/$', 'index'), (r'^polls/(?P<poll_id>\d+)/$', 'detail'), (r'^polls/(?P<poll_id>\d+)/results/$', 'results'), @@ -419,15 +420,15 @@ Our poll app is pretty decoupled at this point, thanks to the strict directory structure that ``python manage.py startapp`` created, but one part of it is coupled to the Django settings: The URLconf. -We've been editing the URLs in ``myproject/urls.py``, but the URL design of an +We've been editing the URLs in ``mysite/urls.py``, but the URL design of an app is specific to the app, not to the Django installation -- so let's move the URLs within the app directory. -Copy the file ``myproject/urls.py`` to ``myproject/polls/urls.py``. Then, -change ``myproject/urls.py`` to remove the poll-specific URLs and insert an +Copy the file ``mysite/urls.py`` to ``mysite/polls/urls.py``. Then, +change ``mysite/urls.py`` to remove the poll-specific URLs and insert an ``include()``:: - (r'^polls/', include('myproject.polls.urls')), + (r'^polls/', include('mysite.polls.urls')), ``include()``, simply, references another URLconf. Note that the regular expression doesn't have a ``$`` (end-of-string match character) but has the @@ -439,14 +440,14 @@ Here's what happens if a user goes to "/polls/34/" in this system: * Django will find the match at ``'^polls/'`` * It will strip off the matching text (``"polls/"``) and send the remaining - text -- ``"34/"`` -- to the 'myproject.polls.urls' urlconf for + text -- ``"34/"`` -- to the 'mysite.polls.urls' urlconf for further processing. Now that we've decoupled that, we need to decouple the -'myproject.polls.urls' urlconf by removing the leading "polls/" from each +'mysite.polls.urls' urlconf by removing the leading "polls/" from each line:: - urlpatterns = patterns('myproject.polls.views', + urlpatterns = patterns('mysite.polls.views', (r'^$', 'index'), (r'^(?P<poll_id>\d+)/$', 'detail'), (r'^(?P<poll_id>\d+)/results/$', 'results'), |
