第三章 视图和URL配置
上一章中,我们讲解了如何配置一个DJANGO项目,如何运行DJANGO的开发版服务器。本章,你将学习用DJANGO创建动态WEB页面的基本知识。
第一个DJANGO页面:Hello World
作为第一个任务,我们现在要创建一个WEB页面,并输出消息“Hello World.“。
不用WEB框架,你也可以做同样的事情。只需要将”Hello World.”存入一个名为hello.html的文本文件中,然后把它上传到WEB服务器的某个目录中。请注意,在些过程中,你要指定这个WEB页面的两部分信息:显示的内容(“Hello World“)和它对应的URL(http://www.example.com/hello.html,如果你把它放到了某个子目录中,则路径是http://www.example.com/files/hello.html)
用DJANGO,你要做同样的两件事情,但方式却不同:页面显示的内容是由一个视图方法产生,URL是由URL配置文件指定。先来看如何写一个能输出”Hello World“的视图。
第一个视图
在上一章中,曾使用命令django-admin.py startproject在创建了目录mysite,进入此目录,创建一个名为views.py的文件。这个模块将包含本章中的所有视图。关于views.py这个名称,没有任何特殊之处,DJANGO并不关心它叫什么名字,但做为一种惯例,最好叫做views.py,也有有助于其它开发者阅读你的代码。
这个“Hello World“视图很简单,下面是完整的方法定义,加上import语句,请输入到你的views.py文件中:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world")
我们来逐行解释这些代码:
第一行,我们使用import语句引入类HttpResponse,这个类位于django.http模块中。引入这个类是因为后面代码要使用它。
第二行,定义了一个名为hello的方法,即视图方法。
请注意,每个视图方法方法都至少有一个参数,习惯称为request。当视图被触发时,这个参数包含了当前WEB请求的相关信息,它是类django.http.HttpResponse的实例。在本例中,我们没有使用它,但是它仍然要作为这个视图方法的第一个参数。
注意,不管视图方法名称,不需要以某种命名方式来让DJANGO识别。此处称为hello,完全是因为它指出了方法的主要功能,它也可以被命名为hello_wonderful_beautiful_world,或类似的其它名称。下一节,每一个URL配置中将揭示DJANGO如何找到这个方法。
方法体只有一行,它只返回了用文本“Hello World”初始化的HttpResponse实例对象。
本节主要讲解的是:一个视图就是PYTHON中的一个方法,这个方法的第一个参数是HttpRequest类的一个实例,另外这个方法返回结果需要是HttpResponse类的实例。要让PYTHON方法成为DJANGO视图方法,这是必须要做的件事。(不过也有例外,我们稍后讨论)
第一个URL配置
到现在为止,如果你运行一次python manage.py runserver,你在浏览器中看到的仍然是“Welcome to Django”,丝毫没有”Hello World”视图的影子。因为我们的项目mysite并不知道有视图hello的存在,我们需要明确的让DJANGO知道,我们要在某个指定的URL上启动这个视图。(正如之前介绍的,与发布静态HTML文件类似,我们现在只是创建了一个HTML文件,还没有将它上传到WEB服务器的某个目录中。)在DJANGO中,为了让视图和URL挂钩,就要使用URL配置了。
URL配置类似于DJANGO站点中的目录。基本上,它就是一个URL规则与视图方法之间的关系映射,那些URL规则即是要被访问的。那么如何才能让DJANGO知道,对于这个URL,要调用这个视图方法,对于那个URL,要调用那个视图方法呢?例如,当某人访问URL “/foo/“时,调用视图方法foo_view(),这个方法在模块views.py中定义。
在上一章中,当我们执行命令django-admin.py startproject时,这个命令会自动创建一个URL配置文件,名为urls.py,默认情况下,它看起来像这样:
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
# Example:
# (r'^mysite/', include('mysite.foo.urls')),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# (r'^admin/', include(admin.site.urls)),
)
默认的URL配置中包含了一些被注释了的通用的DJANGO设置,以便于在想使用这些设置时只需在适当的行取消注释即可。如果忽略这些注释,它就是以下几行:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
)
让我们来逐行解释:
第一行从django.conf.urls.defaults模块引入了所有对象,这是URL配置的基础架构,其中包括patterns这个方法调用。
第二行调用方法patterns,把结果保存到变量urlpatterns中。给patterns方法传递了一个参数:空字符串。(这个参数可以用来给方法中的后续参数提供一个公用前缀,我们暂时跳过这种高级用法。)
这需要注意的是变量urlpatterns,DJANGO会在URL配置模块中查找这个变量。这个变量就是定义了URL和处理对应URL代码的映射关系。正像我们看到的,默认情况下,这个变量为空,也就是说你的应用还只是一个空壳。(补充说明,在上一章中,DJANGO是怎么知道要显示一个“Welcome to Django”的页面?是这样的,如果URL配置中为空,DJANGO就认为你是刚启动了一个新的项目,所以就显示那条信息。)
要添加一个URL规则和视图方法到URL配置中,只需要添加一个PYTHON的元组,这个元组中有URL规则到视图方法之间的映射关系。下面就是如何将URL规则映射到视图hello:
from django.conf.urls.defaults import *
from mysite.views import hello
urlpatterns = patterns('',
('^hello/$', hello),
)
(注意,为了简短,此处移除了注释的代码,如果愿意,你也可以保留它们。)
此处我们做了两处修改:
第一是我们从mysite/views.py模块中导入了hello视图,模块mysite/views.py在PYTHON语法中被转化为mysite.views。(假设mysite/views.py在PYTHON可搜索路径中,具体稍后介绍。)
另一个是在变量urlpatterns中添加了一行:('^hello/$',hello),这行就是一个URL规则。它是一个PYTHON元组,第一个元素是规则匹配字符串(可参考正则表达式),第二个元素是规则匹配时所对应的视图方法。
一句话,我们刚刚是在告诉DJANGO,任何对/hello/的请求都由视图hello来处理。
PYTHON路径
PYTHON路径是你系统上的目录列表,在PYTHON中使用import导入模块时,PYTHON将在这些目录中进行查找。
例如,假设你的PYTHON路径被设置为['','/usr/lib/python2.4/site-packages', '/home/username/djcode']。如果你执行PYTHON语句from foo import bar,PYTHON将在当前目录中查找名为foo.py的模块。(PYTHON路径中的第一个项为串,代指当前目录)如果没找到,PYTHON将查找文件/usr/lib/python2.4/site-packages/foo.py是否存在,如果也不存在,将再试/home/username/djcode/foo.py。最后,如果仍没找到,将引发ImportError错误。
如果你有兴趣查看PYTHON路径中的值,启动PYTHON交互式解释器,输入以下两行:
>>> import sys
>>> print sys.path
一般你不用担心PYTHON路径设置,PYTHON和DJANGO会暗中自动处理。(设置PYTHON路径是脚本manage.py做的事。)
URL匹配的语法值得一说,因为它可能不太清晰直观。尽管我们想要匹配URL /hello/,但是实际写法还是有点不同。这是因为:
在检查URL匹配规则之前,DJANGO会删除进入URL前面的斜线。这就意味着URL匹配规则中不需要包括开始的斜线。(一开始,这可能不太直观,但是它只简不繁。例如在一个URL配置中可以再包含其它的URL配置,这会在第八章讲解)
匹配规则中包含一个脱字符(^)和一个美元符号($),这两个都是正则表达式中的字符,有特殊含义:脱字符指的是从字符串开始进行匹配,美元符表示匹配字符串的结尾。
通过例子可以最好的来解释这些概念。如果我们用模式'^hello/'(尾部不带美元符号)来替代'^hello/$',那么任何以/hello/开始的URL都会被匹配,例如/hello/foo和/hello/bar,而不只是/hello/会被匹配。同样,如果我们移除开始的脱字符,改为'hello/$',那么DJANGO会匹配任何以hello/结尾的URL,例如/foo/bar/hello/。如果再修改一下,变为'hello/',没有了开始的脱字符和结尾的美元符,那么任何包含hello/的URL都会被匹配,例如/foo/hello/bar。因此,我们使用脱字符和美元符来确保只有URL /hello/会被匹配。
许多的URL模式都是以脱字符开始,以美元符结尾,但是要完成更复杂的匹配需要有更好的灵活性。
你可能会对某人请求URL /hello(不带结尾的斜线)时会发生什么感到疑惑,因为这里的URL模式要求结尾带有斜线,那么这个URL不会被匹配。然而,默认情况下,任何尾部不带斜线的URL请求,若没有匹配的情况下,这个URL都会被在尾部加上斜线而重新请求一次。(在DJANGO中,可以通过APPEND_SLASH设置来进行调整,在附录E中会进行讲解)
如果你想要所有请求的URL都以斜线结尾(这也是DJANGO开发者所喜欢的方式),那你只需要在每个URL模式中都以斜线结尾,并将APPEND_SLASH设置为True。如果你想要请求的URL不以斜线结尾,或者想要根据每个URL来决定,那就设置APPEND_SLASH为False,并把根据需要把斜线放到URL模式中。
另外一个需要注意的是,这个URL配置中,我们把视图方法作为一个对象传递,而不是进行方法调用。这是PYTHON(包括其它的动态语言)的一个主要特性:函数是第一类对象,你可以把它们当作变量一样进行传递。酷吧?
为了测试我们对URL配置的修改,需要启动DJANGO开发服务器。像在第二章中做的一样,运行命令python manage.py runserver来启动。(如果它就一直在运行,那也没问题。开发服务器会自动检测到修改并根据需要重新载入,所以你不用在修改时进行重启。)服务器运行在http://127.0.0.1:8000/,所以打开WEB浏览器,访问http://127.0.0.1:8000/hello/。此时你应该可以看到DJANGO视图的文本输出”Hello World”。
天啊,终于完成了自己的第一个DJANGO页面!
正则表达式
正则表达式(或正则)是用文本表示指定模式的简洁方法。DJANGO的URL配置允许任意的正则以进行强大的URL匹配,实践可能只需要用到部分的正则符号。下面列出了一些常用的符号:
符号
匹配内容
.(点)
任何单个字符
/d
任何单个数字
[A-Z]
在字母A到Z之间的任意字符(大写)
[a-z]
在字母a到z之间的任意字符(小写)
[A-Za-z]
在字母A到Z之间的任意字符(忽略大小写)
+
一个或多个前一表达式的内容
[^/]+
斜线(不包括斜线)之前的多个字符
?
零个或一个前一表达式的内容
*
零个或多个前一表达式的内容
{1,3}
一到三个前一表达式的内容
要了解更多关于正则表达式的内容,请参阅:http://www.djangoproject.com/r/python/re-module/
关于404错误的简要说明
到此为止,URL配置中只定义了一个URL模式:请求URL/hello/时进行的处理。那若要是请求一个和它不同的URL会发生什么呢?
想知道结果,试着在WEB浏览器中访问地址:http://127.0.0.1:8000/goodbye/ 或 http://127.0.0.1:8000/hello/subdirectory/或 even http://127.0.0.1:8000/(站点根),是不是看到了”Page not found“的消息页面?DJANGO显示这个消息是因为你的URL配置中没有你访问的URL对应的模式定义。
此页所显示的内容超出了基本的404错误消息,它也明确的告诉你DJANGO使用的URL配置和URL配置中的每个模式。从这些信息中,你应该知道什么请求的URL会出现404的错误。
当然,这些敏感信息对作为开发者的你来说是有用的,但是如果你要把站点部署到互联网上的正式环境时,就不能把这信息暴露出来了。因为这个原因,”Page not found“只在DJANGO项目中DEBUG设置True(调试模式)的情况下显示。稍后会讲解如何禁止调试模式。现在只要知道,每个DJANGO项目在第一次被创建之后都是处于调试模式的,如果项目不是调试模式,DJANGO会输出不同的404响应。
关于站点根的简要说明
像在上一节的讲解,如果你此时访问站点的根URL:http://127.0.0.1:8000/,同样会看到404错误消息。DJANGO没有对根URL添加任何魔法,它没有任何特殊之处,完全由你决定这个URL模式,就像在URL配置中的其它项一样。
匹配站点根URL的模式有点不太直观,因此值得一提。当你决定要准备实现站点根URL对应的视图时,请使用URL模式'^$',它匹配空字符串。例如:
urlpatterns = patterns('',
('^$', my_homepage_view),
# ...
)
DJANGO如何处理请求
在继续学习下一个视图方法之前,我们先花点时间来了解一个DJANGO的工作情况。准确地说,当你通过WEB浏览器访问http://127.0.0.1:8000/hello/而看到”Hello World”的信息时,DJANGO在后台做了什么呢?
一切始于设置文件,当运行python manage.py runserver时,命令会在当前目录下查找一个名为settings.py的文件,就像找manage.py。这个文件包括了当前这个DJANGO项目的各种配置,其中的配置项都是大写:如TEMPLATE_DIRS,DATABASE_NAME等。最重要的设置项是ROOT_URLCONF,它会告诉DJANGO哪个PYTHON模块是这个WEB站点的URL配置。
还记得运行django-admin.py startproject时创建的文件settings.py和urls.py吗?Settings.py文件中包含了一个设置ROOT_URLCONF,其值指向urls.py。现在打开看一下,会有一项是ROOT_URLCONF = 'mysite.urls'。与文件mysite/urls.py相对应。
当一条请求进入时,换句话说,就是对/hello/发起请求时,DJANGO加载由ROOT_URLCONF指向的URL配置。然后顺序检查URL配置中的URL模式,依次将请求的URL与URL模式进行比较,直到找到一个相匹配的。然后调用与那个URL模式所对应的视图方法,并把一个HttpRequest类的对象作为方法的第一个参数。(稍后讲解HttpRequest的特性。)
正如我们在第一个视图示例中看到的一样,一个视图方法必须返回一个HttpResponse类的实例。一旦方法照这样做了,其余的工作就有DJANGO来完成,它把PYTHON对象转换为一个完整的WEB响应,并带有相应的头和主体(也就是WEB页面的内容。)
总结如下:
1.对/hello/的请求出现时
2.通过检查ROOT_URLCONF设置,确定根URL配置
3.DJANGO检查URL配置中的所有URL模式,找出第一个与/hello/相匹配的
4.若找到匹配的,调用相应的视图方法
5.视图方法返回一个HttpResponse实例方法
6.DJANGO将HttpResponse转换为一个完整的HTTP响应,就是一个WEB页面
现在你知道了制作一个DJANGO页面的基本概念,说实施,相当简单,只需要写个视图方法,然后通过URL配置把它映射到某个URL即可。
第二个视图:动态内容
“Hello World”视图演示了DJANGO工作的基本概念,但是它还不是一个动态页面的例子,因为页面内容总是一样。每次看视图/hello/,都会看到同样的东西,它可以被看作是一个静态HTML文件。
对于我们将要学习的第二个视图,我们会创建一个更加动态的内容页面,它显示当前日期和时间。好在这一步很简单,因为它不会调用数据库或要求任何的用户输入,只是输出服务器的内部时间。勉强比”Hello World”那个例子更吸引人,但是它将展示几个新的概念。
这个视图要做两件事:计算当前日期和时间值,返回包含这些值的HttpResponse实例。如果你有PYTHON编程经验,你应知道PYTHON包括了一个datetime模块用于处理日期。下面是使用示例:
>>> import datetime
>>> now=datetime.datetime.now()
>>> now
datetime.datetime(2009, 3, 16, 10, 3, 57, 585138)
>>> print now
2009-03-16 10:03:57.585138
这些代码足够简单,与DJANGO毫无关系,只是一些PYTHON代码。(在此我们想要强调,你应该能识别到哪些是PYTHON代码,哪些是DJANGO特定的代码。正如你学习DJANGO一样,我们想要你能够把你的知识应用在没必要使用DJANGO的其它PYTHON项目中。)
那么,要让DJANGO视图显示当前日期和时间,只需要在视图中调用datetime.datetime.now(),并且返回包含它的HttpResponse实例。下面是相应的代码:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "