summaryrefslogtreecommitdiff
path: root/docs/tutorial03.txt
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2006-05-02 01:31:56 +0000
committerAdrian Holovaty <adrian@holovaty.com>2006-05-02 01:31:56 +0000
commitf69cf70ed813a8cd7e1f963a14ae39103e8d5265 (patch)
treed3b32e84cd66573b3833ddf662af020f8ef2f7a8 /docs/tutorial03.txt
parentd5dbeaa9be359a4c794885c2e9f1b5a7e5e51fb8 (diff)
MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'docs/tutorial03.txt')
-rw-r--r--docs/tutorial03.txt125
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'),