本教程上接 教程 2。我们将继续开发网页投票应用,主要讲 如何创建一个对用户开放的界面 —— “视图”。
视图是 Django 应用中的一“类”网页,它通常使用一个特定的函数提供服务,并且具有一个特定的模板。 例如,在博客应用中,可能有以下视图:
在我们的投票应用中,我们将建立下面的四个视图:
在 Django 中,网站的页面和其他内容都是由视图来传递的。每个视图都是由一个简单的 Python 函数(或者是基于类的视图方法)表示。Django 通过对比请求的 URL(确切地说, 是域名后面的部分URL)来选择对应的视图。
平时您上网的时候可能会遇到像 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” 这种如此美丽的 URL。 但您会很高兴地知道 Django 允许我们使用更加优雅的 URL 模式。
URL 模式就是 URL 的一种简单通用格式 —— 比如: /newsarchive/<year>/<month>/。
Django 通过 “URLconfs” 从 URL 获取到视图。而 URLconf 是将 URL 模式(由正则表达式来描述的) 映射到视图。
本教程中介绍了使用 URLconfs 的基本指令,您可以查阅 django.urls 来获取更多信息。
现在,让我们在 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)
通过添加下列 url() 调用,将这些新的视图加入到 polls.urls 模块中:
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'),
]
现在去浏览器中访问“/polls/34/”。它将运行 detail() 方法,然后在页面中显示您在
url里提供的ID。尝试访问“/polls/34/results/”和“/polls/34/vote/” —— 它们将分别
显示预设结果和投票页面。
当有人访问网站的一个页面 —— 如“/polls/34/”时,Django会加载 mysite.urls Python
模块,因为它通过 ROOT_URLCONF 配置指定。它找到 urlpatterns 变量,
按顺序对各项进行正则匹配。当它匹配到了 '^polls/' ,就剥离出匹配到的文本
("polls/"),然后将剩下的文本 "34/",传递给 “polls.urls” URLconf 进行
下一步的处理。在polls.urls,又匹配到了 r'^(?P<question_id>[0-9]+)/$',
最终结果就是调用该模式对应的 detail() 视图,如:
detail(request=<HttpRequest object>, question_id='34')
question_id='34' 的部分来自 (?P<question_id>[0-9]+)。使用模式周围的括号“捕获”
该模式匹配到的文本,并将其作为参数发送到视图函数; ?P<question_id> 定义用于标识匹配到的
模式的名字; [0-9]+ 是匹配一串数字的正则表达。
因为 URL 模式是正则表达式,您如何使用它们没有什么限制。不需要添加像 .html 这样繁琐的URL
—— 除非您执意这么做,在这种情况下您可以这样做:
url(r'^polls/latest\.html$', views.index),
但是,不要这样做。这比较愚蠢。
每个视图函数只负责处理两件事中的一件:返回一个包含所请求页面内容的 HttpResponse
对象,或者抛出一个诸如 Http404 的异常。该如何去做这两件事,就看您自己的想法了。
您的视图可以从数据库读取记录,也可以不读取。它可以使用模板系统,如 Django 的或第三方Python模板系统, 或着不使用。它可以使用任何您想要的Python库,生成 PDF 文件,输出 XML,即时创建 ZIP 文件,和任何您想要的。
Django只要求返回的是 HttpResponse,或者是个异常。
为了方便,让我们使用 Django 自身的数据库 API,在 教程 2
有所提及。下面是一个新的 index() 视图,它显示系统中最新发布的5条投票问题记录,用逗号分隔:
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)
# 保持其他的视图(detail, results, vote)不变
这里有一个问题:页面的设计被硬编码在视图中。 如果您想更改页面的外观,就得编辑这段 Python 代码。 因此,让我们来使用 Django 的模板系统,通过创建一个可被视图使用的模板,将页面的设计从 Python 中分离出来。
首先,在您的 polls 目录下创建一个叫做 templates 的目录。Django将在这里查找模板。
项目中的 TEMPLATES 配置描述了 Django 如何加载和渲染模板。默认的配置文件配置
DjangoTemplates,其 APP_DIRS 被设置为True。
为了方便, DjangoTemplates 会在 INSTALLED_APPS 所包含的每个应用的目录下
查找名为“templates”的子目录。
在刚刚创建的 templates 目录中,创建另一个名为 polls 的目录,并在里面创建一个名为
index.html 的文件。换句话说,您的模板应该是 polls/templates/polls/index.html。
由于 app_directories 模板加载器如上所述工作,因此您可以在 Django 中简单地引用此模板为
polls/index.html。
模板命名空间
现在我们 可能 侥幸地把模板直接放在 polls/templates 中(而不是创建另一个
polls 子目录),但它实际上是一个坏主意。Django 将选择名字匹配到的第一个模板,
如果您在 不同 的应用程序中有一个相同名称的模板,Django 将无法区分它们。
我们需要能够让 Django 指向正确的一个,确保这一点最简单的方法是为它们命名。
也就是说,将这些模板放在为应用程序本身命名的另一个目录中。
将以下的代码放入模板中:
{% 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 %}
现在让我们更新 polls/views.py 中的 index 视图来使用模板:
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))
该代码加载名为 polls/index.html 的模板,并传给它一个 context。Context 是一个字典,
将模板变量的名字映射到 Python 对象。
通过将浏览器指向“/polls/”来加载页面,您应该看到来自于 教程 2 的 包含“What’s up”问题的项目符号列表。该链接指向问题的详细页面。
render()¶常见的习惯是载入一个模板,填充一个 context,然后返回一个含有模板渲染结果的
HttpResponse 对象。Django 为此提供一个快捷方式。
下面是重写后的 index() 视图:
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)
注意,一旦我们在所有这些视图中完成这个操作,我们不再需要 import loader
和 HttpResponse``(如果您仍然有detail, results 和 vote方法,您将需要保留 ``HttpResponse)。
render() 函数接受 request 对象作为其第一个参数,模板名称作为其第二个参数,
字典作为其可选的第三个参数。它返回一个使用给定 context渲染后的的模板的
HttpResponse 对象。
现在,让我们处理问题详细页面的视图 —— 显示给定投票的问题内容的页面:
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})
这里的新概念:如果请求的问题ID不存在,则该视图引发 Http404 异常。
我们将在以后讨论可以在 polls/detail.html 模板中放些什么,但如果您想快点运行上面的例子,
仅仅需要:
{{ question }}
现在可以运行了。
get_object_or_404()¶一种常见的习惯是使用 get() 并在对象不存在时抛出
Http404。Django 为此提供一个快捷方式。下面是重写后的 detail() 视图:
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})
get_object_or_404() 函数将一个Django模型作为它的第一个参数,
把任意数量的关键字参数作为它的第二个参数,它会把这些关键字参数传递给模型管理器中的
get() 函数。如果对象不存在,它就抛出一个
Http404 异常。
哲学
为什么我们要使用一个辅助函数 get_object_or_404(),而不是
在更高层自动捕获 ObjectDoesNotExist 异常,或者让模型
API 抛出 Http404 而不是
ObjectDoesNotExist?
因为那样做将会使模型层与视图层耦合在一起。Django最重要的一个设计目标就是保持松耦合。
一些可控的耦合将会在 django.shortcuts 模块中介绍。
还有一个 get_list_or_404() 函数,它的工作方式类似
get_object_or_404() —— 差别在于它使用
filter() 而不是
get()。如果列表为空则抛出
Http404。
回到我们投票应用的 detail() 视图。给定上下文变量 question,下面是
polls/detail.html 模板可能的样子:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
模板系统使用点查找语法访问变量属性。在 {{ question.question_text }} 的示例中,
首先Django对对象 question 进行字典查找。如果查找失败,它尝试属性查找 ——
这种情况在示例中生效。如果属性查找失败,它将尝试列表索引查找。
方法调用发生在 {% for %} 循环中: question.choice_set.all 被解释为
Python 代码 question.choice_set.all(),它返回一个由 Choice 对象组成的可迭代对象,
并将其用于 {% for %} 标签。
阅读 模板指南 来了解更多关于模板的信息。
记住,我们在 polls/index.html 模板中编写一个指向问题的链接时,链接中一部分是硬编码的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
这种硬编码、紧耦合的方法有一个问题,就是如果我们想在拥有许多模板文件的项目中修改URLs,
会变得非常麻烦。但是,因为您在 polls.urls 模块的 url() 函数中
定义了命名参数,您就可以通过使用 {% url %} 模板标签来移除在 URL 配置中定义的特定的
URL 路径的依赖:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
它的工作原理是在 polls.urls 模块里查找指定的 URL 的定义。您可以看到名为“detail”的 URL
的准确定义:
... # the ‘name’ value as called by the {% url %} template tag url(r’^(?P<question_id>[0-9]+)/$’, views.detail, name=’detail’), ...
如果您想把投票详细视图中的 URL 改成其它样子,比如 polls/specifics/12/,那么不必在该模板
(或者多个模板)中修改它,而只需修改 polls/urls.py:
...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
教程中的这个项目只有一个应用 polls。在真实的 Django 项目中,可能会有五个、十个、二十个
或者更多的应用。 Django 如何区分它们的 URL 名称呢? 例如,polls 应用具有一个 detail
视图,相同项目中的博客应用可能也有这样一个视图。当使用模板标签 {% url %} 时,人们该如何做
才能使得 Django 知道为一个 URL 对应哪个应用的视图?
答案是在 URLconf 中添加命名空间。在 polls/urls.py 文件中,在开头添加 app_name
来设置应用的命名空间:
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'),
]
现在将 polls/index.html 模板从:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
修改为指向命名空间详细视图:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
当您掌握了编写视图,阅读 本教程的第4部分 来学习简单的表单处理和 通用视图。
9月 10, 2017