在开始之前,说点提外话,随着对Pylons了解的深入,你可能时不时需要看看相关组件/软件包是否有更新出来,方法也不复杂,通过"easy_install -U [组件名]"即可,在学习或者是开发过程中,最好是保持环境相对较新,直到出现相对大的release或者即将进入产品部署阶段。
继续介绍Pylons组件,先看个例子。首先用"paster controller hello"增加一个controller,路径中会增加出以下两个文件:
controllers/hello.py
tests/functional/test_hello.py
tests/functional/test_hello.py
分别对应新增的controller类HelloController和功能测试类TestHelloController,它们分别继承自WSGIController->BaseController和TestCase->TestController。
我们主要看hello.py,默认内容如下:
1
import
logging
2
3 from pylons import request, response, session, tmpl_context as c
4 from pylons.controllers.util import abort, redirect_to
5
6 from newapp.lib.base import BaseController, render
7 # from newapp import model
8
9 log = logging.getLogger( __name__ )
10
11 class HelloController(BaseController):
12
13 def index(self):
14 # Return a rendered template
15 # return render('/template.mako')
16 # or, Return a response
17 return ' Hello World '
2
3 from pylons import request, response, session, tmpl_context as c
4 from pylons.controllers.util import abort, redirect_to
5
6 from newapp.lib.base import BaseController, render
7 # from newapp import model
8
9 log = logging.getLogger( __name__ )
10
11 class HelloController(BaseController):
12
13 def index(self):
14 # Return a rendered template
15 # return render('/template.mako')
16 # or, Return a response
17 return ' Hello World '
如果你的服务器没有Ctrl-C停掉,那么这个时候你已经可以通过
http://127.0.0.1:5000/hello/index
看到该controller的处理结果了(Hello World)。
简单改造一下17行:
from
pylons
import
config
return ' <br/> ' .join(config.keys())
return ' <br/> ' .join(config.keys())
我们就可以在返回页面上显示出所有可以通过pylons.config访问到的参数列表。出了返回文本,也可以通过render()方法交给页面模板引擎生成页面,也可以通过redirect_to()跳转到其他URL。
Pylons是如何找到该请求应该由HelloController的index方法来处理的呢?这背后发生了什么?答案就是Routes。
Routes的作者是Ben Bangert,是Pylons框架三个主要作者/维护者之一,早期的版本主要是仿照Ruby on Rails的routes.rb开发的,有RoR经验的朋友可能一眼就能发现它们之间的相似之处。目前Routes的最新版是1.10.2。
Pylons应用中,routing的配置在config/routing.py,默认生成的内容如下:
1
"""
Routes configuration
2
3 The more specific and detailed routes should be defined first so they
4 may take precedent over the more generic routes. For more information
5 refer to the routes manual at http://routes.groovie.org/docs/
6 """
7 from pylons import config
8 from routes import Mapper
9
10 def make_map():
11 """ Create, configure and return the routes Mapper """
12 map = Mapper(directory = config[ ' pylons.paths ' ][ ' controllers ' ],
13 always_scan = config[ ' debug ' ])
14 map.minimization = False
15
16 # The ErrorController route (handles 404/500 error pages); it should
17 # likely stay at the top, ensuring it can always be resolved
18 map.connect( ' /error/{action} ' , controller = ' error ' )
19 map.connect( ' /error/{action}/{id} ' , controller = ' error ' )
20
21 # CUSTOM ROUTES HERE
22
23 map.connect( ' /{controller}/{action} ' )
24 map.connect( ' /{controller}/{action}/{id} ' )
25
26 return map
2
3 The more specific and detailed routes should be defined first so they
4 may take precedent over the more generic routes. For more information
5 refer to the routes manual at http://routes.groovie.org/docs/
6 """
7 from pylons import config
8 from routes import Mapper
9
10 def make_map():
11 """ Create, configure and return the routes Mapper """
12 map = Mapper(directory = config[ ' pylons.paths ' ][ ' controllers ' ],
13 always_scan = config[ ' debug ' ])
14 map.minimization = False
15
16 # The ErrorController route (handles 404/500 error pages); it should
17 # likely stay at the top, ensuring it can always be resolved
18 map.connect( ' /error/{action} ' , controller = ' error ' )
19 map.connect( ' /error/{action}/{id} ' , controller = ' error ' )
20
21 # CUSTOM ROUTES HERE
22
23 map.connect( ' /{controller}/{action} ' )
24 map.connect( ' /{controller}/{action}/{id} ' )
25
26 return map
在这个配置中,对我们刚才的实例起到决定性作用的是第23行,我们的输入URL为"http://127.0.0.1:5000/hello/index",其中"/hello/index"通过"/{controller}/{action}"这个表达式match出controller为hello而action为index的解析结果,从而在controllers目录找到hello.py,和其中HelloController的index方法,进行调用。
map.connect()在上面代码中体现出两种用法:
map.connect('pattern', key=value) - 指定默认的controller、action、id等
map.connect('pattern') - 直接指定pattern
pattern字符串允许通配符,通常在最后一个元素上,比如'/{controller}/{action}/{*url}',将后面的整个URL片段交给前面指定的controller/action处理。除此以外,map.connect()还支持
1- "路径别名",如:
map.connect('name', 'pattern', [_static=True])
如果_static设为"True",表示为"静态命名路径"。
2- 额外的匹配条件,如:
map.connect('/{controller}/{action}/{id}', requirements={'year': '\d+',})
map.connect('/{controller}/{action}/{id}', conditions=dict(method=['GET','POST']))
所有的route优先级为从上到下。Routes除了提供解析进来的URL的逻辑,在我们的controller和template代码中,我们还可以方便的通过WebHelpers的url_for()方法计算相应的URL。
Routes 1.x中的有一些仿routes.rb功能将会在2.0中被去掉,包括Route Minimization、Route Memory、Implicit Defaults等。如果有兴趣的话,可以参考一下官方文档,这里就不一一介绍了。为什么要去掉?当然主要的动机还是减少歧义,避免一些不必要的混淆。至于深层次的原因么,可以参考Tim Peters《The Zen of Python》中的一句经典的Python哲学:Explicit is better than implicit。什么?没有听说过?打开python命令行,输入"import this"后回车,慢慢体会其中的道理吧。:)