本教程上接教程第2部分。我们将继续开发网页投票这个应用,并将注意力集中在如何创建一个对外的界面 —— “视图”上。
原理
视图是Django应用中的一“类”网页,它通常使用一个特定的函数提供服务,并且具有一个特定的模板。例如,在博客应用中,可能有以下视图:
- 博客首页 —— 显示最新发表的博客。
- 博客“详细”页面 —— 单篇博客的固定链接页面。
- 基于年份的归档页面 —— 显示某给定年份里所有月份发表过的博客。
- 基于月份的归档页面 —— 显示在给定月份中发表过博客的所有日期。
- 基于日期的归档页面 —— 显示在给定日期中发表过的所有博客名称。
- 评论 —— 对给定的博客发表评论
在我们的投票应用中,将有以下四个视图:
- Question首页 —— 显示最新发布的几个Question。
- Question“详细”页面 —— 显示单个Question的具体内容,不显示该议题的当前投票结果,而是提供一个投票的表单。
- Question“结果”页面 —— 显示特定的Question的投票结果。
- 投票功能 —— 处理对Question中Choice的投票。
在Django中,网页的页面和其他内容都是由视图来传递的(视图对WEB请求进行回应)。 每个视图都是由一个简单的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模式匹配到视图。
编写你的第一个视图
让我们来编写第一个视图。 打开polls/views.py文件并将以下Python代码写入:
这可能是Django中最简单的视图。 为了能够调用这个视图,我们需要将这个视图映射到URL上 —— 利用一个URLconf。
为了在投票应用目录内部创建URLconf,需要创建一个urls.py文件。你的应用的目录现在看起来应该像这样:
在polls/urls.py文件中键入如下代码:
下一步,让主URLconf可以链接到polls.urls模块。在mysite/urls.py中插入一个include():
什么时候使用include()
你应该总是使用
include()
当您包括其他URL patterns。admin.site.urls
是唯一的例外。
现在我们已经将一个index视图关联到URLconf中。在你的浏览器中浏览 http://localhost:8000/polls/ , 你会看到 “Hello, world. You’re at the polls index.”, 正如你在index 视图中定义的那样.
url()函数具有四个参数:两个必需的regex和 view,以及两个可选的kwargs和name。 到这里,就可以研究下这些参数的含义了:
url() 参数:regex
术语“regex”是“regular expression(正则表达式)”的常用的一个缩写,是一种用来匹配字符串中模式的语法,在这里是URL模式。 Django从第一个正则表达式开始,依次将请求的URL与每个正则表达式进行匹配,直到找到匹配的那个为止。
请注意,这些正则表达式不会检索URL中GET和POST的参数以及域名。 例如,对于http://www.example.com/myapp/请求,URLconf 将查找myapp/。对于http://www.example.com/myapp/?page=3请求,URLconf 也将查找myapp/。
如果你需要正则表达式方面的帮助,参见Wikipedia 条目 和re 模块的文档。另外,O’Reilly出版Jeffrey Friedl编写的“Mastering Regular Expressions”是一本极好的书。 然而实际上,你不需要成为一个正则表达式的专家,只需要知道如何捕获简单的URL模式就好了。 事实上,复杂的正则表达式的性能并不是太好,所以你并不应该太依赖于正则表达式。
最后,性能方面的一个注意点:这些正则表达式会在URLconf模块第一次载入的时候被编译。 它们超级快(只要这些正则表达式不像上面提醒的那样过于复杂)。
url() 参数:view
当Django找到一个匹配的正则表达式时,它就会调用view参数指定的视图函数,并将HttpRequest对象作为第一个参数,从正则表达式中“捕获”的其他值作为其他参数,传入到该视图函数中。如果正则表达式使用简单的捕获方式,值将作为位置参数传递; 如果使用命名的捕获方式,值将作为关键字参数传递。一会儿,我们将给出一个例子。
url() 参数:kwargs
任何关键字参数都可以以字典形式传递给目标视图。 我们在这个教程里不用Django的这个功能。
url() 参数:name
命名你的URL。 这样就可以在Django的其它地方尤其是模板中,通过名称来明确地引用这个URL。 这个强大的特性可以使你仅仅修改一个文件就可以改变全局的URL模式。
编写更多的视图
现在让我们给polls/views.py添加一些更多的视图。这些视图和之前的略有不同,因为它们另带了一个参数:
通过下面的url() 调用将这些新的视图和polls.urls模块关联起来:
看看你的浏览器,输入“/polls/34/”它将运行detail()方法并显示你在URL中提供的ID。 再试一下“/polls/34/results/”和“/polls/34/vote/” —— 它们将显示出对应的结果界面和投票界面。
当有人请求你的网站的一个页面时 —— 比如“/polls/34/”,Django将加载mysite.urls Python 模块,因为ROOT_URLCONF设置指定了它。它寻找名为urlpatterns 的变量并按顺序匹配其中的正则表达式。我们使用的include()函数来只是对其它URLconfs的简单引用。注意include()函数的正则表达式的末尾没有$(字符串结束符号)而是一个斜线。每当Django遇到include()时,它会截取与该点匹配的URL的前面部分,并将剩余的字符串发送到包含的URLconf以进行进一步处理。
include()背后的想法是使URL变得即插即用。 因为投票应用拥有它自己的URLconf (polls/urls.py),所以它可以被放在“/polls/”路径下,或者“/fun_polls/”路径下,或者“/content/polls/”路径下,或者其他别的什么路径下,该应用仍然能够工作。
以下是如果一个使用者访问“/polls/34/”,系统中将会发生的事:
- Django发现匹配到了正则表达式’^polls/’
- 然后,Django将去掉匹配到的文本(”polls/”)并将剩下的文本 —— “34/” —— 发送给‘polls.urls’ URLconf 做进一步处理,这时将匹配
r'^(?P<question_id>[0-9]+)/$'
并导致像下面这样调用detail()视图:
detail(request=<HttpRequest object>, question_id='34')
question_id=’34’部分来自(?P[0-9]+)。使用模式周围的括号“捕获”该模式匹配的文本,并将其作为参数发送到视图函数;?P<question_id>
定义一个名字,它将用于标识匹配的模式;[0-9]+是匹配一串数字的正则表达式。
编写拥有实际功能的视图
每个视图函数只负责处理两件事中的一件:返回一个包含所请求页面内容的 HttpResponse对象,或抛出一个诸如Http404异常。该如何去做这两件事,就看你自己的想法了。
你的视图可以从数据库中读取记录,或者不读取数据库。 你还可以动态地生成一个PDF文件、输出XML文件、创建一个ZIP文件或者使用你想用的Python 库生成任何想要的形式。
Django只要求返回的是一个HttpResponse。 或者抛出一个异常。
因为方便,让我们使用教程 1中介绍的Django自己的数据库API。下面是一个新的index()视图,它显示系统中最新发布的5条questions记录,并用逗号分隔:
这里有一个问题:页面的设计被硬编码在视图中。 如果你想更改页面的外观,就得编辑这段Python代码。 因此,让我们使用Django的模板系统,通过创建一个视图能够调用的模板,将页面的设计从Python中分离出来。
首先,在你的polls目录下创建一个叫做 templates的目录。Django将在这里查找模板。
你项目的TEMPLATES设置描述了Django将如何加载并渲染模板。默认的设置文件settings.py配置了一个DjangoTemplates后端,其中将APP_DIRS选项设置为True。按照惯例,DjangoTemplates在 INSTALLED_APPS所包含的每个应用的目录下查找名为”templates”子目录。因此即使我们不像教程 2.中那样去修改DIRS,Django也可以找到应用的模版。
组织模板
我们可以将我们所有的模板聚在一起,放在一个大的模板目录下,且可以运行地很好。然而,我们的这个模板属于投票应用,不像我们在先前教程中创建的管理站点模板,我们将把它们放在应用的模板目录下(polls/templates)而不是项目模板目录下(templates)。我们将在可重用的应用教程中详细讨论为什么我们这样做。
在你刚刚创建的templates目录中,创建另外一个目录polls,并在其中创建一个文件index.html。换句话讲,你的模板应该位于 polls/templates/polls/index.html。由于app_directories 模板加载器按照上面描述的方式工作,在Django中你可以简单地用polls/index.html引用这个模板。
模板命名空间
现在,我们可以直接将我们的模板放在polls/templates中(而不用创建另外一个polls子目录),但实际上这是个坏主意。Django将选择它找到的名字匹配的第一个模板文件,如果你在不同 的应用有相同名字的模板文件,Django将不能区分它们。我们需要将Django指向正确的模板,最简单的方式是使用命名空间。具体实现方式是,将这些模板文件放在以应用的名字来命名的另一个目录下。
将以下的代码放入模板文件:
现在让我们更新polls/views.py中的index视图来使用模板:
以上的代码载入polls/index.html模板,并传给它一个context。Context是一个字典,将模板变量的名字映射到Python 对象。
将你的浏览器指向“/polls/”来加载这个页面,你应该看到一个列表,包含来自教程1中”What’s up” Question。其链接指向Question的detail页面。
快捷方式:render()
常见的习惯是载入一个模板、填充一个context 然后返回一个含有模板渲染结果的HttpResponse对象。Django为此提供一个快捷方式。 下面是重写后的index()视图:
注意,一旦我们在所有的视图上都应用这个快捷函数,我们将不再需要导入 loader、RequestContext和HttpResponse (如果你没有改变先前的detail、results和 vote方法,你将需要在导入中保留HttpResponse)。
render()函数将请求对象作为它的第一个参数,模板的名字作为它的第二个参数,一个字典作为它可选的第三个参数。 它返回一个HttpResponse对象,含有用给定的context 渲染后的模板。
引发一个404错误
现在,让我们处理Question 详细页面的视图 —— 显示Question内容的页面: 下面是该视图:
这里有一个新概念:如果没有找到所请求ID的Question,这个视图引发一个Http404异常。
我们将在以后讨论你可以在polls/detail.html模板文件里放些什么代码,但如果你想快点运行上面的例子,仅仅包含:
快捷方式:get_object_or_404()
一种常见的习惯是使用get()并在对象不存在时引发Http404。Django为此提供一个快捷方式。 下面是重写后的detail()视图:
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()视图。 根据context 变量question,下面是polls/detail.html模板可能的样子:
模板系统使用点号查找语法来访问变量的属性。 在{{ question.question_text }}
这个例子中,Django首先在question对象上做字典查询。如果失败,Django会接着尝试属性查询 —— 在这个例子中,属性查询会成功。如果属性查询也失败,Django将尝试列表索引查询。
方法调用发生在{% for %}循环中:question.choice_set.all
被解释为Python的代码question.choice_set.all()
,它返回一个由Choice对象组成的可迭代对象,并将其用于{% for %}标签。
移除模板中硬编码的URLs
请记住,当我们在polls/index.html模板中编写一个指向Question的链接时,链接中一部分是硬编码的:
它的工作原理是在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'), ...
如果你想把polls应用中detail视图的URL改成其它样子比如 polls/specifics/12/,就可以不必在该模板(或者多个模板)中修改它,只需要修改 polls/urls.py:
…
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail,> name='detail'),
…
带命名空间的URL名字
教程中的这个项目只有一个应用polls。在真实的Django项目中,可能会有五个、十个、二十个或者更多的应用。 Django如何区分它们URL的名字呢? 例如,polls 应用具有一个detail 视图,相同项目中的博客应用可能也有这样一个视图。当使用模板标签{% url %}时,人们该如何做才能使得Django知道为一个URL创建哪个应用的视图?
答案是在你的主URLconf下添加命名空间。 在mysite/urls.py文件中,添加命名空间将它修改成:
现在将你的模板polls/index.html由: