mirror of
https://github.com/django/django.git
synced 2024-12-23 17:46:27 +00:00
461 lines
18 KiB
Plaintext
461 lines
18 KiB
Plaintext
=====================================
|
|
Writing your first Django app, part 3
|
|
=====================================
|
|
|
|
This tutorial begins where :doc:`Tutorial 2 </intro/tutorial02>` left off. We're
|
|
continuing the Web-poll application and will focus on creating the public
|
|
interface -- "views."
|
|
|
|
Overview
|
|
========
|
|
|
|
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 blog
|
|
application, you might have the following views:
|
|
|
|
* Blog homepage -- displays the latest few entries.
|
|
|
|
* Entry "detail" page -- permalink page for a single entry.
|
|
|
|
* Year-based archive page -- displays all months with entries in the
|
|
given year.
|
|
|
|
* Month-based archive page -- displays all days with entries in the
|
|
given month.
|
|
|
|
* Day-based archive page -- displays all entries in the given day.
|
|
|
|
* Comment action -- handles posting comments to a given entry.
|
|
|
|
In our poll application, we'll have the following four views:
|
|
|
|
* Question "index" page -- displays the latest few questions.
|
|
|
|
* Question "detail" page -- displays a question text, with no results but
|
|
with a form to vote.
|
|
|
|
* Question "results" page -- displays results for a particular question.
|
|
|
|
* Vote action -- handles voting for a particular choice in a particular
|
|
question.
|
|
|
|
In Django, web pages and other content are delivered by views. Each view is
|
|
represented by a simple Python function (or method, in the case of class-based
|
|
views). Django will choose a view by examining the URL that's requested (to be
|
|
precise, the part of the URL after the domain name).
|
|
|
|
Now in your time on the web you may have come across such beauties as
|
|
"ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B".
|
|
You will be pleased to know that Django allows us much more elegant
|
|
*URL patterns* than that.
|
|
|
|
A URL pattern is simply the general form of a URL - for example:
|
|
``/newsarchive/<year>/<month>/``.
|
|
|
|
To get from a URL to a view, Django uses what are known as 'URLconfs'. A
|
|
URLconf maps URL patterns (described as regular expressions) to views.
|
|
|
|
This tutorial provides basic instruction in the use of URLconfs, and you can
|
|
refer to :mod:`django.urls` for more information.
|
|
|
|
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:
|
|
|
|
.. snippet::
|
|
:filename: polls/views.py
|
|
|
|
def detail(request, question_id):
|
|
return HttpResponse("You're looking at question %s." % question_id)
|
|
|
|
def results(request, question_id):
|
|
response = "You're looking at the results of question %s."
|
|
return HttpResponse(response % question_id)
|
|
|
|
def vote(request, question_id):
|
|
return HttpResponse("You're voting on question %s." % question_id)
|
|
|
|
Wire these new views into the ``polls.urls`` module by adding the following
|
|
:func:`~django.conf.urls.url` calls:
|
|
|
|
.. snippet::
|
|
:filename: polls/urls.py
|
|
|
|
from django.conf.urls import url
|
|
|
|
from . import views
|
|
|
|
urlpatterns = [
|
|
# ex: /polls/
|
|
url(r'^$', views.index, name='index'),
|
|
# ex: /polls/5/
|
|
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
|
# ex: /polls/5/results/
|
|
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
|
|
# ex: /polls/5/vote/
|
|
url(r'^(?P<question_id>[0-9]+)/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 website -- 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. After finding the match at
|
|
``'^polls/'``, it strips off the matching text (``"polls/"``) and sends the
|
|
remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further
|
|
processing. There it matches ``r'^(?P<question_id>[0-9]+)/$'``, resulting in a
|
|
call to the ``detail()`` view like so::
|
|
|
|
detail(request=<HttpRequest object>, question_id='34')
|
|
|
|
The ``question_id='34'`` part comes from ``(?P<question_id>[0-9]+)``. Using parentheses
|
|
around a pattern "captures" the text matched by that pattern and sends it as an
|
|
argument to the view function; ``?P<question_id>`` defines the name that will
|
|
be used to identify the matched pattern; and ``[0-9]+`` is a regular expression 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
|
|
``.html`` -- unless you want to, in which case you can do something like
|
|
this::
|
|
|
|
url(r'^polls/latest\.html$', views.index),
|
|
|
|
But, don't do that. It's silly.
|
|
|
|
Write views that actually do something
|
|
======================================
|
|
|
|
Each view is responsible for doing one of two things: returning an
|
|
:class:`~django.http.HttpResponse` object containing the content for the
|
|
requested page, or raising an exception such as :exc:`~django.http.Http404`. The
|
|
rest is up to you.
|
|
|
|
Your view can read records from a database, or not. It can use a template
|
|
system such as Django's -- or a third-party Python template system -- or not.
|
|
It can generate a PDF file, output XML, create a ZIP file on the fly, anything
|
|
you want, using whatever Python libraries you want.
|
|
|
|
All Django wants is that :class:`~django.http.HttpResponse`. Or an exception.
|
|
|
|
Because it's convenient, let's use Django's own database API, which we covered
|
|
in :doc:`Tutorial 2 </intro/tutorial02>`. Here's one stab at a new ``index()``
|
|
view, which displays the latest 5 poll questions in the system, separated by
|
|
commas, according to publication date:
|
|
|
|
.. snippet::
|
|
:filename: polls/views.py
|
|
|
|
from django.http import HttpResponse
|
|
|
|
from .models import Question
|
|
|
|
|
|
def index(request):
|
|
latest_question_list = Question.objects.order_by('-pub_date')[:5]
|
|
output = ', '.join([q.question_text for q in latest_question_list])
|
|
return HttpResponse(output)
|
|
|
|
# Leave the rest of the views (detail, results, vote) unchanged
|
|
|
|
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 by
|
|
creating a template that the view can use.
|
|
|
|
First, create a directory called ``templates`` in your ``polls`` directory.
|
|
Django will look for templates in there.
|
|
|
|
Your project's :setting:`TEMPLATES` setting describes how Django will load and
|
|
render templates. The default settings file configures a ``DjangoTemplates``
|
|
backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
|
|
``True``. By convention ``DjangoTemplates`` looks for a "templates"
|
|
subdirectory in each of the :setting:`INSTALLED_APPS`.
|
|
|
|
Within the ``templates`` directory you have just created, create another
|
|
directory called ``polls``, and within that create a file called
|
|
``index.html``. In other words, your template should be at
|
|
``polls/templates/polls/index.html``. Because of how the ``app_directories``
|
|
template loader works as described above, you can refer to this template within
|
|
Django simply as ``polls/index.html``.
|
|
|
|
.. admonition:: Template namespacing
|
|
|
|
Now we *might* be able to get away with putting our templates directly in
|
|
``polls/templates`` (rather than creating another ``polls`` subdirectory),
|
|
but it would actually be a bad idea. Django will choose the first template
|
|
it finds whose name matches, and if you had a template with the same name
|
|
in a *different* application, Django would be unable to distinguish between
|
|
them. We need to be able to point Django at the right one, and the easiest
|
|
way to ensure this is by *namespacing* them. That is, by putting those
|
|
templates inside *another* directory named for the application itself.
|
|
|
|
Put the following code in that template:
|
|
|
|
.. snippet:: html+django
|
|
:filename: polls/templates/polls/index.html
|
|
|
|
{% if latest_question_list %}
|
|
<ul>
|
|
{% for question in latest_question_list %}
|
|
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p>No polls are available.</p>
|
|
{% endif %}
|
|
|
|
Now let's update our ``index`` view in ``polls/views.py`` to use the template:
|
|
|
|
.. snippet::
|
|
:filename: polls/views.py
|
|
|
|
from django.http import HttpResponse
|
|
from django.template import loader
|
|
|
|
from .models import Question
|
|
|
|
|
|
def index(request):
|
|
latest_question_list = Question.objects.order_by('-pub_date')[:5]
|
|
template = loader.get_template('polls/index.html')
|
|
context = {
|
|
'latest_question_list': latest_question_list,
|
|
}
|
|
return HttpResponse(template.render(context, request))
|
|
|
|
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 by pointing your browser at "/polls/", and you should see a
|
|
bulleted-list containing the "What's up" question from :doc:`Tutorial 2
|
|
</intro/tutorial02>`. The link points to the question's detail page.
|
|
|
|
A shortcut: :func:`~django.shortcuts.render`
|
|
--------------------------------------------
|
|
|
|
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
|
|
template. Django provides a shortcut. Here's the full ``index()`` view,
|
|
rewritten:
|
|
|
|
.. snippet::
|
|
:filename: polls/views.py
|
|
|
|
from django.shortcuts import render
|
|
|
|
from .models import Question
|
|
|
|
|
|
def index(request):
|
|
latest_question_list = Question.objects.order_by('-pub_date')[:5]
|
|
context = {'latest_question_list': latest_question_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
|
|
:mod:`~django.template.loader` and :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` function takes the request object as its
|
|
first argument, a template name as its second argument and a dictionary as its
|
|
optional third argument. It returns an :class:`~django.http.HttpResponse`
|
|
object of the given template rendered with the given context.
|
|
|
|
Raising a 404 error
|
|
===================
|
|
|
|
Now, let's tackle the question detail view -- the page that displays the question text
|
|
for a given poll. Here's the view:
|
|
|
|
.. snippet::
|
|
:filename: polls/views.py
|
|
|
|
from django.http import Http404
|
|
from django.shortcuts import render
|
|
|
|
from .models import Question
|
|
# ...
|
|
def detail(request, question_id):
|
|
try:
|
|
question = Question.objects.get(pk=question_id)
|
|
except Question.DoesNotExist:
|
|
raise Http404("Question does not exist")
|
|
return render(request, 'polls/detail.html', {'question': question})
|
|
|
|
The new concept here: The view raises the :exc:`~django.http.Http404` exception
|
|
if a question with the requested ID doesn't exist.
|
|
|
|
We'll discuss what you could put in that ``polls/detail.html`` template a bit
|
|
later, but if you'd like to quickly get the above example working, a file
|
|
containing just:
|
|
|
|
.. snippet:: html+django
|
|
:filename: polls/templates/polls/detail.html
|
|
|
|
{{ question }}
|
|
|
|
will get you started for now.
|
|
|
|
A shortcut: :func:`~django.shortcuts.get_object_or_404`
|
|
-------------------------------------------------------
|
|
|
|
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
|
|
provides a shortcut. Here's the ``detail()`` view, rewritten:
|
|
|
|
.. snippet::
|
|
:filename: polls/views.py
|
|
|
|
from django.shortcuts import get_object_or_404, render
|
|
|
|
from .models import Question
|
|
# ...
|
|
def detail(request, question_id):
|
|
question = get_object_or_404(Question, pk=question_id)
|
|
return render(request, 'polls/detail.html', {'question': question})
|
|
|
|
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
|
|
passes to the :meth:`~django.db.models.query.QuerySet.get` function of the
|
|
model's manager. It raises :exc:`~django.http.Http404` if the object doesn't
|
|
exist.
|
|
|
|
.. admonition:: Philosophy
|
|
|
|
Why do we use a helper function :func:`~django.shortcuts.get_object_or_404`
|
|
instead of automatically catching the
|
|
:exc:`~django.core.exceptions.ObjectDoesNotExist` exceptions at a higher
|
|
level, or having the model API raise :exc:`~django.http.Http404` instead of
|
|
:exc:`~django.core.exceptions.ObjectDoesNotExist`?
|
|
|
|
Because that would couple the model layer to the view layer. One of the
|
|
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
|
|
just as :func:`~django.shortcuts.get_object_or_404` -- except using
|
|
:meth:`~django.db.models.query.QuerySet.filter` instead of
|
|
:meth:`~django.db.models.query.QuerySet.get`. It raises
|
|
:exc:`~django.http.Http404` if the list is empty.
|
|
|
|
Use the template system
|
|
=======================
|
|
|
|
Back to the ``detail()`` view for our poll application. Given the context
|
|
variable ``question``, here's what the ``polls/detail.html`` template might look
|
|
like:
|
|
|
|
.. snippet:: html+django
|
|
:filename: polls/templates/polls/detail.html
|
|
|
|
<h1>{{ question.question_text }}</h1>
|
|
<ul>
|
|
{% for choice in question.choice_set.all %}
|
|
<li>{{ choice.choice_text }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
|
|
The template system uses dot-lookup syntax to access variable attributes. In
|
|
the example of ``{{ question.question_text }}``, first Django does a dictionary lookup
|
|
on the object ``question``. Failing that, it tries an attribute lookup -- which
|
|
works, in this case. If attribute lookup had failed, it would've tried a
|
|
list-index lookup.
|
|
|
|
Method-calling happens in the :ttag:`{% for %}<for>` loop:
|
|
``question.choice_set.all`` is interpreted as the Python code
|
|
``question.choice_set.all()``, which returns an iterable of ``Choice`` objects and is
|
|
suitable for use in the :ttag:`{% for %}<for>` tag.
|
|
|
|
See the :doc:`template guide </topics/templates>` for more about templates.
|
|
|
|
Removing hardcoded URLs in templates
|
|
====================================
|
|
|
|
Remember, when we wrote the link to a question in the ``polls/index.html``
|
|
template, the link was partially hardcoded like this:
|
|
|
|
.. code-block:: html+django
|
|
|
|
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
|
|
|
|
The problem with this hardcoded, tightly-coupled approach is that it becomes
|
|
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
|
|
|
|
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
|
|
|
|
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<question_id>[0-9]+)/$', 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<question_id>[0-9]+)/$', 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 URLconf. In the ``polls/urls.py``
|
|
file, go ahead and add an ``app_name`` to set the application namespace:
|
|
|
|
.. snippet::
|
|
:filename: polls/urls.py
|
|
|
|
from django.conf.urls import url
|
|
|
|
from . import views
|
|
|
|
app_name = 'polls'
|
|
urlpatterns = [
|
|
url(r'^$', views.index, name='index'),
|
|
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
|
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
|
|
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
|
|
]
|
|
|
|
Now change your ``polls/index.html`` template from:
|
|
|
|
.. snippet:: html+django
|
|
:filename: polls/templates/polls/index.html
|
|
|
|
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
|
|
|
|
to point at the namespaced detail view:
|
|
|
|
.. snippet:: html+django
|
|
:filename: polls/templates/polls/index.html
|
|
|
|
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
|
|
|
|
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.
|