Fixed #18715 - Refactored tutorial 3. Thank-you Daniel Greenfeld!
This commit is contained in:
parent
08286ca5d9
commit
07abb7a6b7
|
@ -440,20 +440,30 @@ Open your settings file (``mysite/settings.py``, remember) and look at the
|
||||||
filesystem directories to check when loading Django templates. It's a search
|
filesystem directories to check when loading Django templates. It's a search
|
||||||
path.
|
path.
|
||||||
|
|
||||||
|
Create a ``mytemplates`` directory in your project directory. Templates can
|
||||||
|
live anywhere on your filesystem that Django can access. (Django runs as
|
||||||
|
whatever user your server runs.) However, keeping your templates within the
|
||||||
|
project is a good convention to follow.
|
||||||
|
|
||||||
|
When you’ve done that, create a directory polls in your template directory.
|
||||||
|
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.
|
||||||
|
|
||||||
By default, :setting:`TEMPLATE_DIRS` is empty. So, let's add a line to it, to
|
By default, :setting:`TEMPLATE_DIRS` is empty. So, let's add a line to it, to
|
||||||
tell Django where our templates live::
|
tell Django where our templates live::
|
||||||
|
|
||||||
TEMPLATE_DIRS = (
|
TEMPLATE_DIRS = (
|
||||||
'/home/my_username/mytemplates', # Change this to your own directory.
|
'/path/to/mysite/mytemplates', # Change this to your own directory.
|
||||||
)
|
)
|
||||||
|
|
||||||
Now copy the template ``admin/base_site.html`` from within the default Django
|
Now copy the template ``admin/base_site.html`` from within the default Django
|
||||||
admin template directory in the source code of Django itself
|
admin template directory in the source code of Django itself
|
||||||
(``django/contrib/admin/templates``) into an ``admin`` subdirectory of
|
(``django/contrib/admin/templates``) into an ``admin`` subdirectory of
|
||||||
whichever directory you're using in :setting:`TEMPLATE_DIRS`. For example, if
|
whichever directory you're using in :setting:`TEMPLATE_DIRS`. For example, if
|
||||||
your :setting:`TEMPLATE_DIRS` includes ``'/home/my_username/mytemplates'``, as
|
your :setting:`TEMPLATE_DIRS` includes ``'/path/to/mysite/mytemplates'``, as
|
||||||
above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to
|
above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to
|
||||||
``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that
|
``/path/to/mysite/mytemplates/admin/base_site.html``. Don't forget that
|
||||||
``admin`` subdirectory.
|
``admin`` subdirectory.
|
||||||
|
|
||||||
.. admonition:: Where are the Django source files?
|
.. admonition:: Where are the Django source files?
|
||||||
|
|
|
@ -10,7 +10,7 @@ Philosophy
|
||||||
==========
|
==========
|
||||||
|
|
||||||
A view is a "type" of Web page in your Django application that generally serves
|
A view is a "type" of Web page in your Django application that generally serves
|
||||||
a specific function and has a specific template. For example, in a Weblog
|
a specific function and has a specific template. For example, in a blog
|
||||||
application, you might have the following views:
|
application, you might have the following views:
|
||||||
|
|
||||||
* Blog homepage -- displays the latest few entries.
|
* Blog homepage -- displays the latest few entries.
|
||||||
|
@ -41,42 +41,55 @@ In our poll application, we'll have the following four views:
|
||||||
|
|
||||||
In Django, each view is represented by a simple Python function.
|
In Django, each view is represented by a simple Python function.
|
||||||
|
|
||||||
Design your URLs
|
Write your first view
|
||||||
================
|
=====================
|
||||||
|
|
||||||
The first step of writing views is to design your URL structure. You do this by
|
Let's write the first view. Open the file ``polls/views.py``
|
||||||
creating a Python module, called a URLconf. URLconfs are how Django associates
|
and put the following Python code in it::
|
||||||
a given URL with given Python code.
|
|
||||||
|
|
||||||
When a user requests a Django-powered page, the system looks at the
|
from django.http import HttpResponse
|
||||||
:setting:`ROOT_URLCONF` setting, which contains a string in Python dotted
|
|
||||||
syntax. Django loads that module and looks for a module-level variable called
|
|
||||||
``urlpatterns``, which is a sequence of tuples in the following format::
|
|
||||||
|
|
||||||
(regular expression, Python callback function [, optional dictionary])
|
def index(request):
|
||||||
|
return HttpResponse("Hello, world. You're at the poll index.")
|
||||||
|
|
||||||
Django starts at the first regular expression and makes its way down the list,
|
This is the simplest view possible in Django. Now we have a problem, how does
|
||||||
comparing the requested URL against each regular expression until it finds one
|
this view get called? For that we need to map it to a URL, in Django this is
|
||||||
that matches.
|
done in a configuration file called a URLconf.
|
||||||
|
|
||||||
When it finds a match, Django calls the Python callback function, with an
|
.. admonition:: What is a URLconf?
|
||||||
:class:`~django.http.HttpRequest` object as the first argument, any "captured"
|
|
||||||
values from the regular expression as keyword arguments, and, optionally,
|
|
||||||
arbitrary keyword arguments from the dictionary (an optional third item in the
|
|
||||||
tuple).
|
|
||||||
|
|
||||||
For more on :class:`~django.http.HttpRequest` objects, see the
|
In Django, web pages and other content are delivered by views and
|
||||||
:doc:`/ref/request-response`. For more details on URLconfs, see the
|
determining which view is called is done by Python modules informally
|
||||||
:doc:`/topics/http/urls`.
|
titled 'URLconfs'. These modules are pure Python code and are a simple
|
||||||
|
mapping between URL patterns (as simple regular expressions) to Python
|
||||||
|
callback functions (your views). This tutorial provides basic instruction
|
||||||
|
in their use, and you can refer to :mod:`django.core.urlresolvers` for
|
||||||
|
more information.
|
||||||
|
|
||||||
When you ran ``django-admin.py startproject mysite`` at the beginning of
|
To create a URLconf in the polls directory, create a file called ``urls.py``.
|
||||||
Tutorial 1, it created a default URLconf in ``mysite/urls.py``. It also
|
Your app directory should now look like::
|
||||||
automatically set your :setting:`ROOT_URLCONF` setting (in ``settings.py``) to
|
|
||||||
point at that file::
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'mysite.urls'
|
polls/
|
||||||
|
__init__.py
|
||||||
|
admin.py
|
||||||
|
models.py
|
||||||
|
tests.py
|
||||||
|
urls.py
|
||||||
|
views.py
|
||||||
|
|
||||||
Time for an example. Edit ``mysite/urls.py`` so it looks like this::
|
In the ``polls/urls.py`` file include the following code::
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, url
|
||||||
|
|
||||||
|
from polls import views
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$', views.index, name='index')
|
||||||
|
)
|
||||||
|
|
||||||
|
The next step is to point the root URLconf at the ``polls.urls`` module. In
|
||||||
|
``mysite/urls.py`` insert an :func:`~django.conf.urls.include`, leaving you
|
||||||
|
with::
|
||||||
|
|
||||||
from django.conf.urls import patterns, include, url
|
from django.conf.urls import patterns, include, url
|
||||||
|
|
||||||
|
@ -84,27 +97,141 @@ Time for an example. Edit ``mysite/urls.py`` so it looks like this::
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^polls/$', 'polls.views.index'),
|
url(r'^polls/', include('polls.urls')),
|
||||||
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
)
|
)
|
||||||
|
|
||||||
This is worth a review. When somebody requests a page from your Web site -- say,
|
You have now wired an `index` view into the URLconf. Go to
|
||||||
"/polls/23/", Django will load this Python module, because it's pointed to by
|
http://localhost:8000/polls/ in your browser, and you should see the text
|
||||||
the :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
|
"*Hello, world. You're at the poll index.*", which you defined in the
|
||||||
and traverses the regular expressions in order. When it finds a regular
|
``index`` view.
|
||||||
expression that matches -- ``r'^polls/(?P<poll_id>\d+)/$'`` -- it loads the
|
|
||||||
function ``detail()`` from ``polls/views.py``. Finally, it calls that
|
|
||||||
``detail()`` function like so::
|
|
||||||
|
|
||||||
detail(request=<HttpRequest object>, poll_id='23')
|
The :func:`~django.conf.urls.url` function is passed four arguments, two
|
||||||
|
required: ``regex`` and ``view``, and two optional: ``kwargs``, and ``name``.
|
||||||
|
At this point, it's worth reviewing what these arguments are for.
|
||||||
|
|
||||||
The ``poll_id='23'`` part comes from ``(?P<poll_id>\d+)``. Using parentheses
|
:func:`~django.conf.urls.url` argument: regex
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
The term `regex` is a commonly used short form meaning `regular expression`,
|
||||||
|
which is a syntax for matching patterns in strings, or in this case, url
|
||||||
|
patterns. Django starts at the first regular expression and makes its way down
|
||||||
|
the list, comparing the requested URL against each regular expression until it
|
||||||
|
finds one that matches.
|
||||||
|
|
||||||
|
Note that these regular expressions do not search GET and POST parameters, or
|
||||||
|
the domain name. For example, in a request to
|
||||||
|
``http://www.example.com/myapp/``, the URLconf will look for ``myapp/``. In a
|
||||||
|
request to ``http://www.example.com/myapp/?page=3``, the URLconf will also
|
||||||
|
look for ``myapp/``.
|
||||||
|
|
||||||
|
If you need help with regular expressions, see `Wikipedia's entry`_ and the
|
||||||
|
documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering
|
||||||
|
Regular Expressions" by Jeffrey Friedl is fantastic. In practice, however,
|
||||||
|
you don't need to be an expert on regular expressions, as you really only need
|
||||||
|
to know how to capture simple patterns. In fact, complex regexes can have poor
|
||||||
|
lookup performance, so you probably shouldn't rely on the full power of regexes.
|
||||||
|
|
||||||
|
Finally, a performance note: these regular expressions are compiled the first
|
||||||
|
time the URLconf module is loaded. They're super fast (as long as the lookups
|
||||||
|
aren't too complex as noted above).
|
||||||
|
|
||||||
|
.. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
|
||||||
|
|
||||||
|
:func:`~django.conf.urls.url` argument: view
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
When Django finds a regular expression match, Django calls the specified view
|
||||||
|
function, with an :class:`~django.http.HttpRequest` object as the first
|
||||||
|
argument and any “captured” values from the regular expression as other
|
||||||
|
arguments. If the regex uses simple captures, values are passed as positional
|
||||||
|
arguments; if it uses named captures, values are passed as keyword arguments.
|
||||||
|
We'll give an example of this in a bit.
|
||||||
|
|
||||||
|
:func:`~django.conf.urls.url` argument: kwargs
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
Arbitrary keyword arguments can be passed in a dictionary to the target view. We
|
||||||
|
aren't going to use this feature of Django in the tutorial.
|
||||||
|
|
||||||
|
:func:`~django.conf.urls.url` argument: name
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
Naming your URL lets you refer to it unambiguously from elsewhere in Django
|
||||||
|
especially templates. This powerful feature allows you to make global changes
|
||||||
|
to the url patterns of your project while only touching a single file.
|
||||||
|
|
||||||
|
Writing more views
|
||||||
|
==================
|
||||||
|
|
||||||
|
Now let's add a few more views to ``polls/views.py``. These views are
|
||||||
|
slightly different, because they take an argument::
|
||||||
|
|
||||||
|
def detail(request, poll_id):
|
||||||
|
return HttpResponse("You're looking at poll %s." % poll_id)
|
||||||
|
|
||||||
|
def results(request, poll_id):
|
||||||
|
return HttpResponse("You're looking at the results of poll %s." % poll_id)
|
||||||
|
|
||||||
|
def vote(request, poll_id):
|
||||||
|
return HttpResponse("You're voting on poll %s." % poll_id)
|
||||||
|
|
||||||
|
Wire these news views into the ``polls.urls`` module by adding the following
|
||||||
|
:func:`~django.conf.urls.url` calls::
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, url
|
||||||
|
|
||||||
|
from polls import views
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
# ex: /polls/
|
||||||
|
url(r'^$', views.index, name='index'),
|
||||||
|
# ex: /polls/5/
|
||||||
|
url(r'^(?P<poll_id>\d+)/$', views.detail, name='detail'),
|
||||||
|
# ex: /polls/5/results/
|
||||||
|
url(r'^(?P<poll_id>\d+)/results/$', views.results, name='results'),
|
||||||
|
# ex: /polls/5/vote/
|
||||||
|
url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
|
||||||
|
)
|
||||||
|
|
||||||
|
Take a look in your browser, at "/polls/34/". It'll run the ``detail()``
|
||||||
|
method and display whatever ID you provide in the URL. Try
|
||||||
|
"/polls/34/results/" and "/polls/34/vote/" too -- these will display the
|
||||||
|
placeholder results and voting pages.
|
||||||
|
|
||||||
|
When somebody requests a page from your Web site -- say, "/polls/34/", Django
|
||||||
|
will load the ``mysite.urls`` Python module because it's pointed to by the
|
||||||
|
:setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
|
||||||
|
and traverses the regular expressions in order. The
|
||||||
|
:func:`~django.conf.urls.include` functions we are using simply reference
|
||||||
|
other URLconfs. Note that the regular expressions for the
|
||||||
|
:func:`~django.conf.urls.include` functions don't have a ``$`` (end-of-string
|
||||||
|
match character) but rather a trailing slash. Whenever Django encounters
|
||||||
|
:func:`~django.conf.urls.include`, it chops off whatever part of the URL
|
||||||
|
matched up to that point and sends the remaining string to the included
|
||||||
|
URLconf for further processing.
|
||||||
|
|
||||||
|
The idea behind :func:`~django.conf.urls.include` is to make it easy to
|
||||||
|
plug-and-play URLs. Since polls are in their own URLconf
|
||||||
|
(``polls/urls.py``), they can be placed under "/polls/", or under
|
||||||
|
"/fun_polls/", or under "/content/polls/", or any other path root, and the
|
||||||
|
app will still work.
|
||||||
|
|
||||||
|
Here's what happens if a user goes to "/polls/34/" in this system:
|
||||||
|
|
||||||
|
* Django will find the match at ``'^polls/'``
|
||||||
|
|
||||||
|
* Then, Django will strip off the matching text (``"polls/"``) and send the
|
||||||
|
remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for
|
||||||
|
further processing which matches ``r'^(?P<poll_id>\d+)/$'`` resulting in a
|
||||||
|
call to the ``detail()`` view like so::
|
||||||
|
|
||||||
|
detail(request=<HttpRequest object>, poll_id='34')
|
||||||
|
|
||||||
|
The ``poll_id='34'`` part comes from ``(?P<poll_id>\d+)``. Using parentheses
|
||||||
around a pattern "captures" the text matched by that pattern and sends it as an
|
around a pattern "captures" the text matched by that pattern and sends it as an
|
||||||
argument to the view function; the ``?P<poll_id>`` defines the name that will be
|
argument to the view function; ``?P<poll_id>`` defines the name that will
|
||||||
used to identify the matched pattern; and ``\d+`` is a regular expression to
|
be used to identify the matched pattern; and ``\d+`` is a regular expression to
|
||||||
match a sequence of digits (i.e., a number).
|
match a sequence of digits (i.e., a number).
|
||||||
|
|
||||||
Because the URL patterns are regular expressions, there really is no limit on
|
Because the URL patterns are regular expressions, there really is no limit on
|
||||||
|
@ -116,79 +243,10 @@ like this::
|
||||||
|
|
||||||
But, don't do that. It's silly.
|
But, don't do that. It's silly.
|
||||||
|
|
||||||
Note that these regular expressions do not search GET and POST parameters, or
|
|
||||||
the domain name. For example, in a request to ``http://www.example.com/myapp/``,
|
|
||||||
the URLconf will look for ``myapp/``. In a request to
|
|
||||||
``http://www.example.com/myapp/?page=3``, the URLconf will look for ``myapp/``.
|
|
||||||
|
|
||||||
If you need help with regular expressions, see `Wikipedia's entry`_ and the
|
|
||||||
documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering
|
|
||||||
Regular Expressions" by Jeffrey Friedl is fantastic.
|
|
||||||
|
|
||||||
Finally, a performance note: these regular expressions are compiled the first
|
|
||||||
time the URLconf module is loaded. They're super fast.
|
|
||||||
|
|
||||||
.. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
|
|
||||||
|
|
||||||
Write your first view
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Well, we haven't created any views yet -- we just have the URLconf. But let's
|
|
||||||
make sure Django is following the URLconf properly.
|
|
||||||
|
|
||||||
Fire up the Django development Web server:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
python manage.py runserver
|
|
||||||
|
|
||||||
Now go to "http://localhost:8000/polls/" on your domain in your Web browser.
|
|
||||||
You should get a pleasantly-colored error page with the following message::
|
|
||||||
|
|
||||||
ViewDoesNotExist at /polls/
|
|
||||||
|
|
||||||
Could not import polls.views.index. View does not exist in module polls.views.
|
|
||||||
|
|
||||||
This error happened because you haven't written a function ``index()`` in the
|
|
||||||
module ``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 ``polls/views.py``
|
|
||||||
and put the following Python code in it::
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse("Hello, world. You're at the poll index.")
|
|
||||||
|
|
||||||
This is the simplest view possible. Go to "/polls/" in your browser, and you
|
|
||||||
should see your text.
|
|
||||||
|
|
||||||
Now lets add a few more views. These views are slightly different, because
|
|
||||||
they take an argument (which, remember, is passed in from whatever was
|
|
||||||
captured by the regular expression in the URLconf)::
|
|
||||||
|
|
||||||
def detail(request, poll_id):
|
|
||||||
return HttpResponse("You're looking at poll %s." % poll_id)
|
|
||||||
|
|
||||||
def results(request, poll_id):
|
|
||||||
return HttpResponse("You're looking at the results of poll %s." % poll_id)
|
|
||||||
|
|
||||||
def vote(request, poll_id):
|
|
||||||
return HttpResponse("You're voting on poll %s." % poll_id)
|
|
||||||
|
|
||||||
Take a look in your browser, at "/polls/34/". It'll run the `detail()` method
|
|
||||||
and display whatever ID you provide in the URL. Try "/polls/34/results/" and
|
|
||||||
"/polls/34/vote/" too -- these will display the placeholder results and voting
|
|
||||||
pages.
|
|
||||||
|
|
||||||
Write views that actually do something
|
Write views that actually do something
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
Each view is responsible for doing one of two things: Returning an
|
Each view is responsible for doing one of two things: returning an
|
||||||
:class:`~django.http.HttpResponse` object containing the content for the
|
:class:`~django.http.HttpResponse` object containing the content for the
|
||||||
requested page, or raising an exception such as :exc:`~django.http.Http404`. The
|
requested page, or raising an exception such as :exc:`~django.http.Http404`. The
|
||||||
rest is up to you.
|
rest is up to you.
|
||||||
|
@ -205,51 +263,21 @@ in :doc:`Tutorial 1 </intro/tutorial01>`. Here's one stab at the ``index()``
|
||||||
view, which displays the latest 5 poll questions in the system, separated by
|
view, which displays the latest 5 poll questions in the system, separated by
|
||||||
commas, according to publication date::
|
commas, according to publication date::
|
||||||
|
|
||||||
from polls.models import Poll
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from polls.models import Poll
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
|
latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
|
||||||
output = ', '.join([p.question for p in latest_poll_list])
|
output = ', '.join([p.question for p in latest_poll_list])
|
||||||
return HttpResponse(output)
|
return HttpResponse(output)
|
||||||
|
|
||||||
There's a problem here, though: The page's design is hard-coded in the view. If
|
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.
|
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::
|
So let's use Django's template system to separate the design from Python.
|
||||||
|
|
||||||
from django.template import Context, loader
|
|
||||||
from polls.models import Poll
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
|
|
||||||
t = loader.get_template('polls/index.html')
|
|
||||||
c = Context({
|
|
||||||
'latest_poll_list': latest_poll_list,
|
|
||||||
})
|
|
||||||
return HttpResponse(t.render(c))
|
|
||||||
|
|
||||||
That code loads the template called "polls/index.html" and passes it a context.
|
|
||||||
The context is a dictionary mapping template variable names to Python objects.
|
|
||||||
|
|
||||||
Reload the page. Now you'll see an error::
|
|
||||||
|
|
||||||
TemplateDoesNotExist at /polls/
|
|
||||||
polls/index.html
|
|
||||||
|
|
||||||
Ah. There's no template yet. First, create a directory, somewhere on your
|
|
||||||
filesystem, whose contents Django can access. (Django runs as whatever user your
|
|
||||||
server runs.) Don't put them under your document root, though. You probably
|
|
||||||
shouldn't make them public, just for security's sake.
|
|
||||||
Then edit :setting:`TEMPLATE_DIRS` in your ``settings.py`` to tell Django where
|
|
||||||
it can 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``. Note that our
|
|
||||||
``loader.get_template('polls/index.html')`` code from above maps to
|
|
||||||
"[template_directory]/polls/index.html" on the filesystem.
|
|
||||||
|
|
||||||
|
First, create a directory ``polls`` in your template directory you specified
|
||||||
|
in setting:`TEMPLATE_DIRS`. Within that, create a file called ``index.html``.
|
||||||
Put the following code in that template:
|
Put the following code in that template:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
@ -264,36 +292,58 @@ Put the following code in that template:
|
||||||
<p>No polls are available.</p>
|
<p>No polls are available.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
Now let's use that html template in our index view::
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.template import Context, loader
|
||||||
|
|
||||||
|
from polls.models import Poll
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
|
||||||
|
template = loader.get_template('polls/index.html')
|
||||||
|
context = Context({
|
||||||
|
'latest_poll_list': latest_poll_list,
|
||||||
|
})
|
||||||
|
return HttpResponse(template.render(context))
|
||||||
|
|
||||||
|
That code loads the template called ``polls/index.html`` and passes it a
|
||||||
|
context. The context is a dictionary mapping template variable names to Python
|
||||||
|
objects.
|
||||||
|
|
||||||
Load the page in your Web browser, and you should see a bulleted-list
|
Load the page in your Web browser, and you should see a bulleted-list
|
||||||
containing the "What's up" poll from Tutorial 1. The link points to the poll's
|
containing the "What's up" poll from Tutorial 1. The link points to the poll's
|
||||||
detail page.
|
detail page.
|
||||||
|
|
||||||
A shortcut: render_to_response()
|
A shortcut: :func:`~django.shortcuts.render`
|
||||||
--------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
It's a very common idiom to load a template, fill a context and return an
|
It's a very common idiom to load a template, fill a context and return an
|
||||||
:class:`~django.http.HttpResponse` object with the result of the rendered
|
:class:`~django.http.HttpResponse` object with the result of the rendered
|
||||||
template. Django provides a shortcut. Here's the full ``index()`` view,
|
template. Django provides a shortcut. Here's the full ``index()`` view,
|
||||||
rewritten::
|
rewritten::
|
||||||
|
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render
|
||||||
|
|
||||||
from polls.models import Poll
|
from polls.models import Poll
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
|
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
|
||||||
return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
|
context = {'latest_poll_list': latest_poll_list}
|
||||||
|
return render(request, 'polls/index.html', context)
|
||||||
|
|
||||||
Note that once we've done this in all these views, we no longer need to import
|
Note that once we've done this in all these views, we no longer need to import
|
||||||
:mod:`~django.template.loader`, :class:`~django.template.Context` and
|
:mod:`~django.template.loader`, :class:`~django.template.Context` and
|
||||||
:class:`~django.http.HttpResponse`.
|
:class:`~django.http.HttpResponse` (you'll want to keep ``HttpResponse`` if you
|
||||||
|
still have the stub methods for ``detail``, ``results``, and ``vote``).
|
||||||
|
|
||||||
The :func:`~django.shortcuts.render_to_response` function takes a template name
|
The :func:`~django.shortcuts.render` function takes the request object as its
|
||||||
as its first argument and a dictionary as its optional second argument. It
|
first argument, a template name as its second argument and a dictionary as its
|
||||||
returns an :class:`~django.http.HttpResponse` object of the given template
|
optional third argument. It returns an :class:`~django.http.HttpResponse`
|
||||||
rendered with the given context.
|
object of the given template rendered with the given context.
|
||||||
|
|
||||||
Raising 404
|
Raising a 404 error
|
||||||
===========
|
===================
|
||||||
|
|
||||||
Now, let's tackle the poll detail view -- the page that displays the question
|
Now, let's tackle the poll detail view -- the page that displays the question
|
||||||
for a given poll. Here's the view::
|
for a given poll. Here's the view::
|
||||||
|
@ -302,10 +352,10 @@ for a given poll. Here's the view::
|
||||||
# ...
|
# ...
|
||||||
def detail(request, poll_id):
|
def detail(request, poll_id):
|
||||||
try:
|
try:
|
||||||
p = Poll.objects.get(pk=poll_id)
|
poll = Poll.objects.get(pk=poll_id)
|
||||||
except Poll.DoesNotExist:
|
except Poll.DoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
return render_to_response('polls/detail.html', {'poll': p})
|
return render(request, 'polls/detail.html', {'poll': poll})
|
||||||
|
|
||||||
The new concept here: The view raises the :exc:`~django.http.Http404` exception
|
The new concept here: The view raises the :exc:`~django.http.Http404` exception
|
||||||
if a poll with the requested ID doesn't exist.
|
if a poll with the requested ID doesn't exist.
|
||||||
|
@ -317,18 +367,18 @@ later, but if you'd like to quickly get the above example working, just::
|
||||||
|
|
||||||
will get you started for now.
|
will get you started for now.
|
||||||
|
|
||||||
A shortcut: get_object_or_404()
|
A shortcut: :func:`~django.shortcuts.get_object_or_404`
|
||||||
-------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
It's a very common idiom to use :meth:`~django.db.models.query.QuerySet.get`
|
It's a very common idiom to use :meth:`~django.db.models.query.QuerySet.get`
|
||||||
and raise :exc:`~django.http.Http404` if the object doesn't exist. Django
|
and raise :exc:`~django.http.Http404` if the object doesn't exist. Django
|
||||||
provides a shortcut. Here's the ``detail()`` view, rewritten::
|
provides a shortcut. Here's the ``detail()`` view, rewritten::
|
||||||
|
|
||||||
from django.shortcuts import render_to_response, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
# ...
|
# ...
|
||||||
def detail(request, poll_id):
|
def detail(request, poll_id):
|
||||||
p = get_object_or_404(Poll, pk=poll_id)
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
return render_to_response('polls/detail.html', {'poll': p})
|
return render(request, 'polls/detail.html', {'poll': poll})
|
||||||
|
|
||||||
The :func:`~django.shortcuts.get_object_or_404` function takes a Django model
|
The :func:`~django.shortcuts.get_object_or_404` function takes a Django model
|
||||||
as its first argument and an arbitrary number of keyword arguments, which it
|
as its first argument and an arbitrary number of keyword arguments, which it
|
||||||
|
@ -345,7 +395,8 @@ exist.
|
||||||
:exc:`~django.core.exceptions.ObjectDoesNotExist`?
|
:exc:`~django.core.exceptions.ObjectDoesNotExist`?
|
||||||
|
|
||||||
Because that would couple the model layer to the view layer. One of the
|
Because that would couple the model layer to the view layer. One of the
|
||||||
foremost design goals of Django is to maintain loose coupling.
|
foremost design goals of Django is to maintain loose coupling. Some
|
||||||
|
controlled coupling is introduced in the :mod:`django.shortcuts` module.
|
||||||
|
|
||||||
There's also a :func:`~django.shortcuts.get_list_or_404` function, which works
|
There's also a :func:`~django.shortcuts.get_list_or_404` function, which works
|
||||||
just as :func:`~django.shortcuts.get_object_or_404` -- except using
|
just as :func:`~django.shortcuts.get_object_or_404` -- except using
|
||||||
|
@ -369,7 +420,8 @@ You normally won't have to bother with writing 404 views. If you don't set
|
||||||
is used by default. Optionally, you can create a ``404.html`` template
|
is used by default. Optionally, you can create a ``404.html`` template
|
||||||
in the root of your template directory. The default 404 view will then use that
|
in the root of your template directory. The default 404 view will then use that
|
||||||
template for all 404 errors when :setting:`DEBUG` is set to ``False`` (in your
|
template for all 404 errors when :setting:`DEBUG` is set to ``False`` (in your
|
||||||
settings module).
|
settings module). If you do create the template, add at least some dummy
|
||||||
|
content like "Page not found".
|
||||||
|
|
||||||
A couple more things to note about 404 views:
|
A couple more things to note about 404 views:
|
||||||
|
|
||||||
|
@ -387,11 +439,14 @@ Similarly, your root URLconf may define a ``handler500``, which points
|
||||||
to a view to call in case of server errors. Server errors happen when
|
to a view to call in case of server errors. Server errors happen when
|
||||||
you have runtime errors in view code.
|
you have runtime errors in view code.
|
||||||
|
|
||||||
|
Likewise, you should create a ``500.html`` template at the root of your
|
||||||
|
template directory and add some content like "Something went wrong".
|
||||||
|
|
||||||
Use the template system
|
Use the template system
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
Back to the ``detail()`` view for our poll application. Given the context
|
Back to the ``detail()`` view for our poll application. Given the context
|
||||||
variable ``poll``, here's what the "polls/detail.html" template might look
|
variable ``poll``, here's what the ``polls/detail.html`` template might look
|
||||||
like:
|
like:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
@ -416,150 +471,89 @@ suitable for use in the :ttag:`{% for %}<for>` tag.
|
||||||
|
|
||||||
See the :doc:`template guide </topics/templates>` for more about templates.
|
See the :doc:`template guide </topics/templates>` for more about templates.
|
||||||
|
|
||||||
Simplifying the URLconfs
|
|
||||||
========================
|
|
||||||
|
|
||||||
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('',
|
|
||||||
url(r'^polls/$', 'polls.views.index'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
|
||||||
)
|
|
||||||
|
|
||||||
Namely, ``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 :func:`~django.conf.urls.patterns`, like so::
|
|
||||||
|
|
||||||
urlpatterns = patterns('polls.views',
|
|
||||||
url(r'^polls/$', 'index'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
|
|
||||||
)
|
|
||||||
|
|
||||||
This is functionally identical to the previous formatting. It's just a bit
|
|
||||||
tidier.
|
|
||||||
|
|
||||||
Since you generally don't want the prefix for one app to be applied to every
|
|
||||||
callback in your URLconf, you can concatenate multiple
|
|
||||||
:func:`~django.conf.urls.patterns`. Your full ``mysite/urls.py`` might
|
|
||||||
now look like this::
|
|
||||||
|
|
||||||
from django.conf.urls import patterns, include, url
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
admin.autodiscover()
|
|
||||||
|
|
||||||
urlpatterns = patterns('polls.views',
|
|
||||||
url(r'^polls/$', 'index'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
|
|
||||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
|
|
||||||
)
|
|
||||||
|
|
||||||
urlpatterns += patterns('',
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
|
||||||
)
|
|
||||||
|
|
||||||
Decoupling the URLconfs
|
|
||||||
=======================
|
|
||||||
|
|
||||||
While we're at it, we should take the time to decouple our poll-app URLs from
|
|
||||||
our Django project configuration. Django apps are meant to be pluggable -- that
|
|
||||||
is, each particular app should be transferable to another Django installation
|
|
||||||
with minimal fuss.
|
|
||||||
|
|
||||||
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 ``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 ``mysite/urls.py`` to ``polls/urls.py``. Then, change
|
|
||||||
``mysite/urls.py`` to remove the poll-specific URLs and insert an
|
|
||||||
:func:`~django.conf.urls.include`, leaving you with::
|
|
||||||
|
|
||||||
from django.conf.urls import patterns, include, url
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
admin.autodiscover()
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
|
||||||
url(r'^polls/', include('polls.urls')),
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
|
||||||
)
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.include` simply references another URLconf.
|
|
||||||
Note that the regular expression doesn't have a ``$`` (end-of-string match
|
|
||||||
character) but has the trailing slash. Whenever Django encounters
|
|
||||||
:func:`~django.conf.urls.include`, it chops off whatever part of the
|
|
||||||
URL matched up to that point and sends the remaining string to the included
|
|
||||||
URLconf for further processing.
|
|
||||||
|
|
||||||
Here's what happens if a user goes to "/polls/34/" in this system:
|
|
||||||
|
|
||||||
* Django will find the match at ``'^polls/'``
|
|
||||||
|
|
||||||
* Then, Django will strip off the matching text (``"polls/"``) and send the
|
|
||||||
remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for
|
|
||||||
further processing.
|
|
||||||
|
|
||||||
Now that we've decoupled that, we need to decouple the ``polls.urls``
|
|
||||||
URLconf by removing the leading "polls/" from each line, removing the
|
|
||||||
lines registering the admin site, and removing the ``include`` import which
|
|
||||||
is no longer used. Your ``polls/urls.py`` file should now look like
|
|
||||||
this::
|
|
||||||
|
|
||||||
from django.conf.urls import patterns, url
|
|
||||||
|
|
||||||
urlpatterns = patterns('polls.views',
|
|
||||||
url(r'^$', 'index'),
|
|
||||||
url(r'^(?P<poll_id>\d+)/$', 'detail'),
|
|
||||||
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
|
||||||
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
|
||||||
)
|
|
||||||
|
|
||||||
The idea behind :func:`~django.conf.urls.include` and URLconf
|
|
||||||
decoupling is to make it easy to plug-and-play URLs. Now that polls are in their
|
|
||||||
own URLconf, they can be placed under "/polls/", or under "/fun_polls/", or
|
|
||||||
under "/content/polls/", or any other path root, and the app will still work.
|
|
||||||
|
|
||||||
All the poll app cares about is its relative path, not its absolute path.
|
|
||||||
|
|
||||||
Removing hardcoded URLs in templates
|
Removing hardcoded URLs in templates
|
||||||
------------------------------------
|
====================================
|
||||||
|
|
||||||
Remember, when we wrote the link to a poll in our template, the link was
|
Remember, when we wrote the link to a poll in the ``polls/index.html``
|
||||||
partially hardcoded like this:
|
template, the link was partially hardcoded like this:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
|
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
|
||||||
|
|
||||||
To use the decoupled URLs we've just introduced, replace the hardcoded link
|
The problem with this hardcoded, tightly-coupled approach is that it becomes
|
||||||
with the :ttag:`url` template tag:
|
challenging to change URLs on projects with a lot of templates. However, since
|
||||||
|
you defined the name argument in the :func:`~django.conf.urls.url` functions in
|
||||||
|
the ``polls.urls`` module, you can remove a reliance on specific URL paths
|
||||||
|
defined in your url configurations by using the ``{% url %}`` template tag:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
<li><a href="{% url 'polls.views.detail' poll.id %}">{{ poll.question }}</a></li>
|
<li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If ``{% url 'polls.views.detail' poll.id %}`` (with quotes) doesn't work,
|
If ``{% url 'detail' poll.id %}`` (with quotes) doesn't work, but
|
||||||
but ``{% url polls.views.detail poll.id %}`` (without quotes) does, that
|
``{% url detail poll.id %}`` (without quotes) does, that means you're
|
||||||
means you're using a version of Django < 1.5. In this case, add the
|
using a version of Django < 1.5. In this case, add the following
|
||||||
following declaration at the top of your template:
|
declaration at the top of your template:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
|
The way this works is by looking up the URL definition as specified in the
|
||||||
|
``polls.urls`` module. You can see exactly where the URL name of 'detail' is
|
||||||
|
defined below::
|
||||||
|
|
||||||
|
...
|
||||||
|
# the 'name' value as called by the {% url %} template tag
|
||||||
|
url(r'^(?P<poll_id>\d+)/$', views.detail, name='detail'),
|
||||||
|
...
|
||||||
|
|
||||||
|
If you want to change the URL of the polls detail view to something else,
|
||||||
|
perhaps to something like ``polls/specifics/12/`` instead of doing it in the
|
||||||
|
template (or templates) you would change it in ``polls/urls.py``::
|
||||||
|
|
||||||
|
...
|
||||||
|
# added the word 'specifics'
|
||||||
|
url(r'^specifics/(?P<poll_id>\d+)/$', views.detail, name='detail'),
|
||||||
|
...
|
||||||
|
|
||||||
|
Namespacing URL names
|
||||||
|
======================
|
||||||
|
|
||||||
|
The tutorial project has just one app, ``polls``. In real Django projects,
|
||||||
|
there might be five, ten, twenty apps or more. How does Django differentiate
|
||||||
|
the URL names between them? For example, the ``polls`` app has a ``detail``
|
||||||
|
view, and so might an app on the same project that is for a blog. How does one
|
||||||
|
make it so that Django knows which app view to create for a url when using the
|
||||||
|
``{% url %}`` template tag?
|
||||||
|
|
||||||
|
The answer is to add namespaces to your root URLconf. In the
|
||||||
|
``mysite/urls.py`` file, go ahead and change it to include namespacing::
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, include, url
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
admin.autodiscover()
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^polls/', include('polls.urls', namespace="polls")),
|
||||||
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
)
|
||||||
|
|
||||||
|
Now change your ``polls/index.html`` template from:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
<li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
|
||||||
|
|
||||||
|
to point at the namespaced detail view:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
<li><a href="{% url 'polls:detail' poll.id %}">{{ poll.question }}</a></li>
|
||||||
|
|
||||||
When you're comfortable with writing views, read :doc:`part 4 of this tutorial
|
When you're comfortable with writing views, read :doc:`part 4 of this tutorial
|
||||||
</intro/tutorial04>` to learn about simple form processing and generic views.
|
</intro/tutorial04>` to learn about simple form processing and generic views.
|
||||||
|
|
|
@ -18,7 +18,7 @@ tutorial, so that the template contains an HTML ``<form>`` element:
|
||||||
|
|
||||||
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
|
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
|
||||||
|
|
||||||
<form action="{% url 'polls.views.vote' poll.id %}" method="post">
|
<form action="{% url 'polls:vote' poll.id %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for choice in poll.choice_set.all %}
|
{% for choice in poll.choice_set.all %}
|
||||||
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
|
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
|
||||||
|
@ -35,7 +35,7 @@ A quick rundown:
|
||||||
selects one of the radio buttons and submits the form, it'll send the
|
selects one of the radio buttons and submits the form, it'll send the
|
||||||
POST data ``choice=3``. This is HTML Forms 101.
|
POST data ``choice=3``. This is HTML Forms 101.
|
||||||
|
|
||||||
* We set the form's ``action`` to ``{% url 'polls.views.vote' poll.id %}``, and we
|
* We set the form's ``action`` to ``{% url 'polls:vote' poll.id %}``, and we
|
||||||
set ``method="post"``. Using ``method="post"`` (as opposed to
|
set ``method="post"``. Using ``method="post"`` (as opposed to
|
||||||
``method="get"``) is very important, because the act of submitting this
|
``method="get"``) is very important, because the act of submitting this
|
||||||
form will alter data server-side. Whenever you create a form that alters
|
form will alter data server-side. Whenever you create a form that alters
|
||||||
|
@ -52,34 +52,18 @@ A quick rundown:
|
||||||
forms that are targeted at internal URLs should use the
|
forms that are targeted at internal URLs should use the
|
||||||
:ttag:`{% csrf_token %}<csrf_token>` template tag.
|
:ttag:`{% csrf_token %}<csrf_token>` template tag.
|
||||||
|
|
||||||
The :ttag:`{% csrf_token %}<csrf_token>` tag requires information from the
|
|
||||||
request object, which is not normally accessible from within the template
|
|
||||||
context. To fix this, a small adjustment needs to be made to the ``detail``
|
|
||||||
view, so that it looks like the following::
|
|
||||||
|
|
||||||
from django.template import RequestContext
|
|
||||||
# ...
|
|
||||||
def detail(request, poll_id):
|
|
||||||
p = get_object_or_404(Poll, pk=poll_id)
|
|
||||||
return render_to_response('polls/detail.html', {'poll': p},
|
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
The details of how this works are explained in the documentation for
|
|
||||||
:ref:`RequestContext <subclassing-context-requestcontext>`.
|
|
||||||
|
|
||||||
Now, let's create a Django view that handles the submitted data and does
|
Now, let's create a Django view that handles the submitted data and does
|
||||||
something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
|
something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
|
||||||
created a URLconf for the polls application that includes this line::
|
created a URLconf for the polls application that includes this line::
|
||||||
|
|
||||||
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
|
||||||
|
|
||||||
We also created a dummy implementation of the ``vote()`` function. Let's
|
We also created a dummy implementation of the ``vote()`` function. Let's
|
||||||
create a real version. Add the following to ``polls/views.py``::
|
create a real version. Add the following to ``polls/views.py``::
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404, render_to_response
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.template import RequestContext
|
|
||||||
from polls.models import Choice, Poll
|
from polls.models import Choice, Poll
|
||||||
# ...
|
# ...
|
||||||
def vote(request, poll_id):
|
def vote(request, poll_id):
|
||||||
|
@ -88,17 +72,17 @@ create a real version. Add the following to ``polls/views.py``::
|
||||||
selected_choice = p.choice_set.get(pk=request.POST['choice'])
|
selected_choice = p.choice_set.get(pk=request.POST['choice'])
|
||||||
except (KeyError, Choice.DoesNotExist):
|
except (KeyError, Choice.DoesNotExist):
|
||||||
# Redisplay the poll voting form.
|
# Redisplay the poll voting form.
|
||||||
return render_to_response('polls/detail.html', {
|
return render(request, 'polls/detail.html', {
|
||||||
'poll': p,
|
'poll': p,
|
||||||
'error_message': "You didn't select a choice.",
|
'error_message': "You didn't select a choice.",
|
||||||
}, context_instance=RequestContext(request))
|
})
|
||||||
else:
|
else:
|
||||||
selected_choice.votes += 1
|
selected_choice.votes += 1
|
||||||
selected_choice.save()
|
selected_choice.save()
|
||||||
# Always return an HttpResponseRedirect after successfully dealing
|
# Always return an HttpResponseRedirect after successfully dealing
|
||||||
# with POST data. This prevents data from being posted twice if a
|
# with POST data. This prevents data from being posted twice if a
|
||||||
# user hits the Back button.
|
# user hits the Back button.
|
||||||
return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
|
return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
|
||||||
|
|
||||||
This code includes a few things we haven't covered yet in this tutorial:
|
This code includes a few things we haven't covered yet in this tutorial:
|
||||||
|
|
||||||
|
@ -142,8 +126,7 @@ This code includes a few things we haven't covered yet in this tutorial:
|
||||||
'/polls/3/results/'
|
'/polls/3/results/'
|
||||||
|
|
||||||
... where the ``3`` is the value of ``p.id``. This redirected URL will
|
... where the ``3`` is the value of ``p.id``. This redirected URL will
|
||||||
then call the ``'results'`` view to display the final page. Note that you
|
then call the ``'results'`` view to display the final page.
|
||||||
need to use the full name of the view here (including the prefix).
|
|
||||||
|
|
||||||
As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
|
As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
|
||||||
object. For more on :class:`~django.http.HttpRequest` objects, see the
|
object. For more on :class:`~django.http.HttpRequest` objects, see the
|
||||||
|
@ -153,14 +136,14 @@ After somebody votes in a poll, the ``vote()`` view redirects to the results
|
||||||
page for the poll. Let's write that view::
|
page for the poll. Let's write that view::
|
||||||
|
|
||||||
def results(request, poll_id):
|
def results(request, poll_id):
|
||||||
p = get_object_or_404(Poll, pk=poll_id)
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
return render_to_response('polls/results.html', {'poll': p})
|
return render(request, 'polls/results.html', {'poll': poll})
|
||||||
|
|
||||||
This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
|
This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
|
||||||
</intro/tutorial03>`. The only difference is the template name. We'll fix this
|
</intro/tutorial03>`. The only difference is the template name. We'll fix this
|
||||||
redundancy later.
|
redundancy later.
|
||||||
|
|
||||||
Now, create a ``results.html`` template:
|
Now, create a ``polls/results.html`` template:
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
@ -172,7 +155,7 @@ Now, create a ``results.html`` template:
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="{% url 'polls.views.detail' poll.id %}">Vote again?</a>
|
<a href="{% url 'polls:detail' poll.id %}">Vote again?</a>
|
||||||
|
|
||||||
Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a
|
Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a
|
||||||
results page that gets updated each time you vote. If you submit the form
|
results page that gets updated each time you vote. If you submit the form
|
||||||
|
@ -215,19 +198,7 @@ Read on for details.
|
||||||
|
|
||||||
You should know basic math before you start using a calculator.
|
You should know basic math before you start using a calculator.
|
||||||
|
|
||||||
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
|
First, open the ``polls/urls.py`` URLconf and change it like so::
|
||||||
tutorial so far::
|
|
||||||
|
|
||||||
from django.conf.urls import patterns, url
|
|
||||||
|
|
||||||
urlpatterns = patterns('polls.views',
|
|
||||||
url(r'^$', 'index'),
|
|
||||||
url(r'^(?P<poll_id>\d+)/$', 'detail'),
|
|
||||||
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
|
||||||
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
|
||||||
)
|
|
||||||
|
|
||||||
Change it like so::
|
|
||||||
|
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
@ -239,18 +210,18 @@ Change it like so::
|
||||||
queryset=Poll.objects.order_by('-pub_date')[:5],
|
queryset=Poll.objects.order_by('-pub_date')[:5],
|
||||||
context_object_name='latest_poll_list',
|
context_object_name='latest_poll_list',
|
||||||
template_name='polls/index.html'),
|
template_name='polls/index.html'),
|
||||||
name='poll_index'),
|
name='index'),
|
||||||
url(r'^(?P<pk>\d+)/$',
|
url(r'^(?P<pk>\d+)/$',
|
||||||
DetailView.as_view(
|
DetailView.as_view(
|
||||||
model=Poll,
|
model=Poll,
|
||||||
template_name='polls/detail.html'),
|
template_name='polls/detail.html'),
|
||||||
name='poll_detail'),
|
name='detail'),
|
||||||
url(r'^(?P<pk>\d+)/results/$',
|
url(r'^(?P<pk>\d+)/results/$',
|
||||||
DetailView.as_view(
|
DetailView.as_view(
|
||||||
model=Poll,
|
model=Poll,
|
||||||
template_name='polls/results.html'),
|
template_name='polls/results.html'),
|
||||||
name='poll_results'),
|
name='results'),
|
||||||
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote', name='vote'),
|
||||||
)
|
)
|
||||||
|
|
||||||
We're using two generic views here:
|
We're using two generic views here:
|
||||||
|
@ -267,15 +238,6 @@ two views abstract the concepts of "display a list of objects" and
|
||||||
``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic
|
``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic
|
||||||
views.
|
views.
|
||||||
|
|
||||||
* We've added the ``name`` argument to the views (e.g. ``name='poll_results'``)
|
|
||||||
so that we have a way to refer to their URL later on (see the
|
|
||||||
documentation about :ref:`naming URL patterns
|
|
||||||
<naming-url-patterns>` for information). We're also using the
|
|
||||||
:func:`~django.conf.urls.url` function from
|
|
||||||
:mod:`django.conf.urls` here. It's a good habit to use
|
|
||||||
:func:`~django.conf.urls.url` when you are providing a
|
|
||||||
pattern name like this.
|
|
||||||
|
|
||||||
By default, the :class:`~django.views.generic.list.DetailView` generic
|
By default, the :class:`~django.views.generic.list.DetailView` generic
|
||||||
view uses a template called ``<app name>/<model name>_detail.html``.
|
view uses a template called ``<app name>/<model name>_detail.html``.
|
||||||
In our case, it'll use the template ``"polls/poll_detail.html"``. The
|
In our case, it'll use the template ``"polls/poll_detail.html"``. The
|
||||||
|
@ -308,41 +270,13 @@ You can now delete the ``index()``, ``detail()`` and ``results()``
|
||||||
views from ``polls/views.py``. We don't need them anymore -- they have
|
views from ``polls/views.py``. We don't need them anymore -- they have
|
||||||
been replaced by generic views.
|
been replaced by generic views.
|
||||||
|
|
||||||
The last thing to do is fix the URL handling to account for the use of
|
|
||||||
generic views. In the vote view above, we used the
|
|
||||||
:func:`~django.core.urlresolvers.reverse` function to avoid
|
|
||||||
hard-coding our URLs. Now that we've switched to a generic view, we'll
|
|
||||||
need to change the :func:`~django.core.urlresolvers.reverse` call to
|
|
||||||
point back to our new generic view. We can't simply use the view
|
|
||||||
function anymore -- generic views can be (and are) used multiple times
|
|
||||||
-- but we can use the name we've given::
|
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
|
|
||||||
|
|
||||||
The same rule apply for the :ttag:`url` template tag. For example in the
|
|
||||||
``results.html`` template:
|
|
||||||
|
|
||||||
.. code-block:: html+django
|
|
||||||
|
|
||||||
<a href="{% url 'poll_detail' poll.id %}">Vote again?</a>
|
|
||||||
|
|
||||||
Run the server, and use your new polling app based on generic views.
|
Run the server, and use your new polling app based on generic views.
|
||||||
|
|
||||||
For full details on generic views, see the :doc:`generic views documentation
|
For full details on generic views, see the :doc:`generic views documentation
|
||||||
</topics/class-based-views/index>`.
|
</topics/class-based-views/index>`.
|
||||||
|
|
||||||
Coming soon
|
What's next?
|
||||||
===========
|
============
|
||||||
|
|
||||||
The tutorial ends here for the time being. Future installments of the tutorial
|
The tutorial ends here for the time being. In the meantime, you might want to
|
||||||
will cover:
|
check out some pointers on :doc:`where to go from here </intro/whatsnext>`.
|
||||||
|
|
||||||
* Advanced form processing
|
|
||||||
* Using the RSS framework
|
|
||||||
* Using the cache framework
|
|
||||||
* Using the comments framework
|
|
||||||
* Advanced admin features: Permissions
|
|
||||||
* Advanced admin features: Custom JavaScript
|
|
||||||
|
|
||||||
In the meantime, you might want to check out some pointers on :doc:`where to go
|
|
||||||
from here </intro/whatsnext>`
|
|
||||||
|
|
Loading…
Reference in New Issue