TurboGears快速入门
在这篇文章当中主要讲解入门教程和关于信息。这篇文章将成为一个很好的入门文档。
这篇文章界于入门文档和手册之间。入门文档覆盖特定知识点,提供快速入门。工作手册提供了具体的工作细节文档。
这份快速入门指南提供了在不需要注意细节情况下的背景知识。
本文档没有包含安装指南,到下载页面会提及相关方法。本指南也没有包含Python的方方面面,可以从很多书籍和资源中得到Python的相关知识。
tg-admin
TurboGears的最终应用是以代码文件的形式存在的。正因为如此,代码是描述编程工具的好方法。生成代码最快的方式是使用tg-admin工具。
tg-admin是TurboGears的命令行工具。依靠其子命令可以创建新工程、处理(manipulate)数据库或者以面向对象的特性分析数据库。
tg-admin手册提供了命令细节。如果不提供命令来运行tg-admin,可以得到可用命令列表。
现在,我们使用"quickstart"命令,所以应该使用如下命令:
tg-admin quickstart
如果使用"--help"选项,将会得到命令的帮助信息和选项信息。对快速开始,可以使用命令行或者提示符下设置信息。你不需要经常运行quickstart,因为其实它很简单。
这里使用"gs"作为项目名称和包名称,开始我们的项目:
Enter project name: gs
Enter package name [gs]:
creating directory gs
generating gs/dev.cfg
generating gs/prod.cfg
generating gs/gs-start.py
generating gs/setup.py
creating directory gs/gs
generating gs/gs/__init__.py
generating gs/gs/controllers.py
generating gs/gs/model.py
creating directory gs/gs/sqlobject-history
creating directory gs/gs/static/css
creating directory gs/gs/static/image
creating directory gs/gs/static/javascript
creating directory gs/gs/templates
generating gs/gs/templates/__init__.py
generating gs/gs/templates/master.kid
generating gs/gs/templates/welcome.kid
creating directory gs/gs.egg-info
generating gs/gs.egg-info/sqlobject.txt
running egg_info
writing requirements to ./gs.egg-info/requires.txt
writing ./gs.egg-info/PKG-INFO
writing top-level name to ./gs.egg-info/top_level.txt
这个命令提供给我们一个WEB应用的模板,可以准备填入实际代码。
Starting up the server
当你运行quickstart时,你已经可以随时准备启动CherryPy的内置服务器了。运行开发服务器使用如下步骤:
1、选择新建工程的顶层目录,本例为"cd gs"
2、运行"python 项目名-start.py",本例为"python gs-start.py"
这时将可以看到服务器启动日志。缺省时使用8080端口,所以可以在"http://localhost:8080"得到欢迎页面。如果在MAC系统下,可以在Bonjour书签看到开发站点。
服务器会自动监视文件更改并自动重启。可以在任何时候按下Control+C来停止服务器。
Configuration
快速开始的例子创建了两个配置文件:dev.cfg和prod.cfg,启动脚本查找setup.py中的当前目录,如果当前在开发模式则使用dev.cfg配置,否则使用prod.cfg配置文件。可以在启动脚本的命令行中指定使用的配置文件。
The Big Picture
这幅图片包含大量的方框和箭头,但是并没有看起来那么复杂。这幅图展示了简历TurboGears应用所需的所有部分。而且大部分是内含在TurboGears当中的。你的应用程序只需要包含亮紫色的盒子。
-
你的model(模型)对象描述(represent)应用程序使用的书籍。
-
你的controller(控制)得到用户浏览器的信息,并使用这些信息更新模型中的信息,并准备好用于显示的视图。当然,控制器也选择需要用于返回数据的视图。
-
在TurboGears里,你的view(视图)就是用于显示controller提供的信息的模板(template)。
你只需要在应用中提供这三个部分即可,其余部分由TurboGears来代劳。
CherryPy可以方便的产生控制器来从网站上获得用户提交的信息。SQLObject可以方便的定义和使用模型。Kid可以产生优秀的HTML页面供给浏览器的显示。MochiKit可以很好的实现浏览器端的复杂行为(behavior),也可以提过Kid生成AJAX格式的请求。
CherryPy Exposed
在CherryPy中,你的网站被描述成一个按照树形组织的对象和他们的方法。按照CherryPy的方式,可以方便的使请求到达目的地。CherryPy指定的请求根路径是cherrypy.root。
如果查看gs-start.py脚本,可以看到如下的代码:
from gs.controllers import Root
cherrypy.root=Root()
按照习俗(convertion),根控制器类叫做"Root",并且从"controllers.py"模块导入。在很小的应用当中,只要一个控制器模块即可。在大型应用中,(by gashero)你将会希望将应用分为多个包。你还可以使用功能分割(divisions)。如果你希望的话,也可以分别导入不同的包到同一处。
当你确定了控制器包之后,Root就是你的WEB应用(webapp)的根对象。
看看下面的代码来理解URL查找和遍历工作:
import turbogears
class HardCodedAddition:
def cantgohere(self):
pass
@turbogears.expose()
def twoplustwo(self):
return "four"
@turbogears.expose()
def fortyplustwo(self):
return "forty-two"
class AlwaysSeven:
@turbogears.expose()
def index(def):
return "7"
class Root:
seven=AlwaysSeven()
add=HardCodedAddition()
@turbogears.expose()
def index(self):
return "Welcome"
@turbogears.expose()
def fun(self):
return "... and yet startlingly productive"
@turbogears.expose()
def default(self,*args):
return "The next parts were %s"%str(args)
按照这种设置,我们可以看到TurboGears的URL遍历工作。这些规则用样作用于CherryPy而并非来自TurboGears。
URL路径 | 返回结果 | 注释 |
---|---|---|
/ | Welcome | 如果没有指定,则运行index方法 |
/fun | ... and yet startlingly | 如果指定了方法,则直接运行此方法 |
/seven | 重定向到/seven/ | 子对象按照目录来对待,所以最终被重定向。这是一个很好的特性,可以确保你的相对URL工作正常。 |
/seven/ | 7 | 运行子对象的index方法 |
/add/cantgohere | 错误 | 这个对象没有被暴露(exposed)。CherryPy只导航到暴露的方法 |
/add/twoplustwo | four | 运行子对象的方法 |
/foobar | The next parts were('foobar') | 如果没有找到匹配的URL,则执行default方法 |
/foobar/baz/bork | The next parts were('foobar','baz','bork') | 余下部分的URL被分隔发送到default方法的参数。 |
在构建对象树时,最好还是设置一个default方法,这样可以很好的探测点击的URL。
I wanted an argument!
CherryPy的一个很有趣的特性(当然被TurboGears所使用)是从WEB请求得到的参数会自动转换为调用方法的参数。这将会导致处理WEB请求相当的自然。
可以把root设置成如下样式:
class Root:
@turbogears.expose()
def index(self,name="Arthur"):
return "I am %s, King of the britons"%name
你将会诧异于访问"/"时返回了"I am Arthur, King of the britons"。缺省的参数在未指定时起效。实际上现在就可以传入参数了,并且按照你期望的方式运行。一个形如"/?name=Lancelot"的请求将会返回"I am Lancelot, King of the britons"
有如Python,调用"/?foobar=baz"将会抛出错误,(by gashero)因为这样将调用root.index(foobar="baz")。所以可以使用如下方式避免:
class Root:
@turbogears.expose()
def index(self,name="Arthur",**kw):
return "I am %s, King of the britons"%name
在Python中kw可以接受剩余参数的词典。
Testing, 1... 2... 3...
测试驱动的开发是一种好的开发方式。在编码前先写好测试代码,将会让你明确自己所需要的功能,并提供更好的结构。
如果你不提前写测试,使用自动测试将是更改时的救命者。
当你安装了TurboGears时,可以免费得到TestGears。TestGears是Python的unittest单元测试模块的简单插件,可以免去手动书写测试程序的麻烦。
一份单独(separate)的文档可以覆盖测试的每一个细节。如下是示例控制器代码controllers.py:
import turbogears
class Root:
@turbogears.expose(html="gs.templates.welcome")
def index(self,value="0"):
value=int(value)
return dict(newvalue=value*2)
TestGears遍历(look for)模块并启动test_。按惯例(by convertion),测试模块使用单独的包叫做"tests",并放在需要测试的包之下,尽管TestGears并不一定需要这样。如下是简单的测试模块:
from turbogears.tests import util
from gs.controllers import Root
import cherrypy
def test withtemplate():
"测试模板输出"
cherrypy.root=Root()
util.createRequest("/?value=27")
assert "The new value is 54." in
cherrypy.response.body[0]
def test directall():
"测试不使用模板时的方法输出"
d=util.call(cherrypy.root.index,"5")
assert d["newvalue"]==10
注意,这些只是函数测试,并非unittest.TestCases的测试用例。你仍然可以使用unittest.TestCases的测试用例。测试函数必须以"test"开头。docstring提供了测试程序的正常运行输出。
模板测试使用了受欢迎的模板形式,形如"The new value is ${value}."。运行tests,如下:
python setup.py testgears
模板测试按照CherryPy的请求处理机制,所以可以测试各种URL和过滤模板处理。
Validating and converting arguments
TurboGears提供了从表单编码到特定Python类型的验证和简单转换方法,如下是示例:
import turbogears
from turbogears import validators
class Root:
@turbogears.expose(html="gs.templates.welcome",
validators=("value":validators.Int()))
def index(self,value=0):
return dict(newvalue=value*2)
def validation error(self,funcname,kw,errors):
#做一些出错时的处理工作
这个传递给turbogaers.expose的字典告知value参数使用Int类型的验证器。这可以确保调用index函数时,value的值是整数类型。
如果验证失败,则validation_error函数被调用,来替换原始目标方法。第一个参数是字符串型,表示验证失败的方法。可以使用一个专用(appropriate)的模板来报警。第二个参数是传递给原方法的参数字典。最后一个参数是抛出的异常列表(list)。
验证失败异常对象提供了用户友好的错误信息。这些对象和原始参数允许你重现(repopulate)一个验证失败的表单入口。
validation_error方法应该做什么呢?他可以做与原方法相同的事情,可以返回字典或字符串,也可以抛出cherrypy.HTTPRedirect来重定向到另外一个页面。这是很好理解的。
所有的验证器(validator)都已经被导入到了turbogears.validators模块中了。可以看到验证器在"FormEncode validators module"和"TurboGears validators module"中是有效的。
可以在FormEncode验证器框架下做很多更加高级的事情。验证器参数可以传递轮廓(schema?)或字典。更多关于轮廓(schema)的信息,参照"FormEncode Validator"文档。
How a view template is chosen
上面已经看过一些使用turbogears.expose中的html参数的引用。这里将会继续讲解。
如果单独使用CherryPy,可以返回一个字符串,并且此字符串被发送到客户端浏览器。但是更多时候,需要返回一个WEB页面。使用TurboGears可以使用Kid模板来返回存在字典中的变量,并传递的html参数到turbogears.expose。
html参数给出了获得Kid模板的完整Python包路径。在项目中,位置应该是"项目名.templates"。模板文件以.kid做扩展名,但只要指定html参数,可以不用在乎这些。所以如果模板叫做"Welcome",则应该使用html="项目名.templates.welcome"。
返回的字典中应该填入需要传递给模板的任何变量。例如返回dict(newvalue=5),则模板中$(newvalue)被替换为5。
有时可能会发现需要一些其他的模板。如果返回的字典包含"tg_template"键,则会自动换用模板系统。返回dict(tg_template="项目名.templates.lancelot")将会使用lancelot模板系统,而不是使用html参数。
Returning XML instead of HTML
最初的Kid是一种基于XML的模板语言。配合HTML串行化器,可以很好的处理HTML。但是使用其他串行化器时,也可以产生XHTML或其他XML格式。
expose(暴露)方法允许指定格式(可以是"json"或Kid的其他串行化器)和内容类型(content_type)。例如,如果你想返回RSS,则可以按照如下配置expose方法:
@turbogears.expose(template="project.templates.rss",
format="xml",content_type="text/xml+rss")
expose方法缺省生成HTML代码,但是更改类型也很容易。注意expose方法要求传递模板到"template"参数代替"html"参数。
Brief introduction to Kid templates
Kid模板系统可以是任何的XML文档,但是需要包含Kid可以处理的命名空间(namespace)。下面的例子使用了XHTML文档,转换为有效的HTML文档。
这个例子演示了Kid模板:
<?python title="A Kid Test Document"
fruits=["apple","orange","kiwi","M&M"]
from platform import system?>
<html xmlns:py="http://purl.org/kid/ns#">
<head>
<title py:content="title">This is replaced.</title>
</head>
<body>
<p>These are some of my favorite fruits:</p>
<ul>
<li py:for="fruit in fruits">
I like ${fruit}s
</li>
</ul>
<p py:if="system()=='Linux'">
Good for you!
</p>
</body>
</html>
Kid模板的注意事项:
不要忘记定义py这个XML命名空间,这是用来让模板识别出有效XML的关键。
${}用于简单的变量替换,随处可用。
$foo可以替换为foo,但是并不象使用${}那么安全,因为不方便检测变量结束。
这是XHTML,所以必须关闭所有标签,例如HTML中的<br>,到了XHTML中必须写成<br/>。这是转换成HTML所必须的。
因为模板需要使用有效的XML,所以如果你的JavaScript中包含"<" 或">",则需要放在<![CDATA[]]>段中。
Kid模板包含一些特定的保留字,如write、serializer、serialize 、generate、initialize、pull、content、transform。应该避开这些保留字。
Kid一个比较好的特性是,可以在其中所有已知的Python特性。Kid模板将会被编译成Python代码来控制模板的行为。例如:py:for="fruit in fruits"行为表示Python中的"for fruit in fruits:"。
词典中定义的变量是模板的全部可用变量。所有的"py"属性将使用Python的本地变量。如py:for="fruit in fruits"的例子,"fruits"就是一种经由(via)字典传递的序列(iterable)类型。
当模板中的变量被遗弃时,Kid自动避开。你也不需要在意包含"<"的值。例如,你需要确定是否真的需要使用XHTML时。如果是,则将你包装的替换值放入XML()中。例如,我们需要一个叫做"header"的XHTML段。可以表示为${XML(header)},这样"header"将会被丢弃(drop)而自动避开(escape)。
推荐阅读Kid属性语言。
http://kid.lesscode.org/language.html#attribute-language
Being Designer-Friendly
如果使用了XHTML,可以使用FireFox浏览器直接打开Kid模板。如果对你很有用,可以这样在浏览器中查看来校准模板外观。
你在找什么?样式表,JavaScript还是变量替换。
有时,使用样式表的href时并非可以达到很好的所见(by gashero)即所得。为了避免(get around)这些,可以使用href来处理浏览器中的模板视图,并且py:attrs来处理生成页面。如:
<link rel="stylesheet" type="text/css"
href="/Path/To/File.css"
py:attrs="href='/path/on/website'"/>
当在浏览器中查看模板时,浏览器只查看href属性,所以样式表可以很好的加载。当模板将要生成最终视图(final viewing)时,Kid将会替换py:attrs中的值,所以样式表可以很好的工作。当处理JavaScript时,这里同样使用src属性来标记脚本。
Application-wide Headers and Footers
pp
Template variables you get for free
pp
Using URLs
pp
JSON outputs
pp
Brief introduction to MochiKit
pp
Defining your model
模型是应用操作的数据。TurboGears模型使用SQLObject对象。SQLObject提供了Python对象到关系数据库的桥梁。它并不是设计用来完全隐藏数据库的,未来的设计目标是可以使用Python代码而不是错误的SQL语句。
在TurboGears存取数据库之前,必须指定如何找到数据库。可以在dev.cfg或prod.cfg配置文件中设置。参数"sqlobject.dburi"控制了数据库的存储位置,并使用标准的URI风格。
当然也可以在连接URI中提供"query parameters"选项。一对(couple)有用的选项是debug和debugOutput。如果添加了"?debug=1"到URI,每个查询都会输出运行状态。如果添加了"&debugOutput=1",则可以看到查询结果。
可以在工程目录的model.py中定义数据模型。如果确实很需要将数据保存在多个模块中,也可以将其分布在多个模块中,并确保被model导入。SQLObject提供两种不同的方式定义数据库:可以直接在数据库中定义SQL语句,或在Python中定义。为了代码的简洁,在Python的model中定义更好些。如果希望一个Python类联系到数据库,可以按照如下:
from sqlobject import *
class Bookt(SQLObject):
class sqlmeta:
fromDatabase=True
注意这只在某些数据库中有效,具体参考SQLObject文档。
在Python类中定义模型需要输入Python代码,但可以避免书写SQL语句。如下是一个例子BOOK:
class Book(SQLObject):
isbn=StringCol(length=13,alternateID=True)
title=StringCol(length=100)
description=StringCol()
authors=RelatedJoin("Author")
class Authors(SQLObject):
last_name=StringCol(length=40)
first_name=StringCol(length=40)
books=RelatedJoin("Book")
创建数据库使用如下语句:
tg-admin sql create
这将会创建三个数据库表格,一个是书籍表,一个作者表,一个多对多的联系表来联系他们。
但是SQLObject不能定义所有的数据库模型,但可以在大多数情况下很好的工作。比如无法很好的定义表名、列名和连接名,来适应已有的数据库。
模型对象无需确保对数据容器的沉默。这些模型对象是完整的Python对象,可以拥有自己的方法和做一些复杂(complex)的计算。
更多相关信息详见SQLObject文档。
Using your model
SQLObject可以确保非常方便的访问数据库,有如普通的Python对象一样。SQLObject在后台执行SQL语句来实现这些东东。插入一行记录就是实例化一个对象。下例开启了调试信息(?debug=1&debugOutput=1)。
>>> Book(isbn="1234567890",title="A Fistful of Yen", description="An evocative look at ...")
1/QueryIns: INSERT INTO book (isbn,description,title) VALUES ('1234567890','An evocative look at ...','A Fistful of Yen')
1/QueryIns-> 1
1/COMMIT : auto
1/QueryOne: SELECT isbn, title, description FROM book WHERE id=1
1/QueryR: SELECT isbn, title, description FROM book WHERE id=1
1/QueryOne-> (u'1234567890',u'A Fistful of Yen','An ...')
1/COMMIT : auto
<Book 1 isbn='1234567890' title='A Fistful of Yen' description="'An ...'">
虽然有很多种办法来保留一个主键来保持记录的区别,SQLObject最好使用缺省设置的integer类型的主键。你可以使用SQLObject对象的id属性访问内置的主键。SQLObject可以确保很容易的通过ID属性获取对象。
>>> Book.get(1)
当对某一个特定的列指定了"alternateID",比如上例的"isbn", SQLObject自动创建类方法来方便搜索:
>>> Book.byIsbn("1234567890")
当然,更多的时候需要ID之外的查询方式。SQLObject提供了"select"类方法允许制定多种Python形式的查询条件。自定义类的"q"属性提供了存取真实属性的方法。例如,存取isbn列可以用Book.q.isbn。如下例:
>>> list(Book.select(AND(LIKE(Book.q.title,"%Fistfull%"), Book.q.isbn="1234567890")))
在如上例子中,使用list作用于Book.select。select类方法返回一个SelectResults对象。仅在请求数据时SelectResults返回数据,它只是一个结果的占位符。除了可以将结果转换为list之外,还可以使用SelectResults对象的count()方法得到结果记录数量。
数据更新(update)很简单,只要改变属性即可。每次改变属性,SQLObject对象都会执行UPDATE的SQL语句。有时,可能需要一次更改多个列。实例拥有set方法允许一次赋值,如下例:
>>> book.title="A Fistful of Foobar"
>>> book.set(title="A Fistful of Yen 2:...",isbn="37")
TurboGears可以方便的使用事务,通过"连接HUB"。connection hub在需要时自动连接到数据库,并给出方法供开始(begin),提交(commit) ,回滚(rollback)和结束(end)事务。如下例:
>>> book.title #"1"
>>> hub.begin()
>>> book.title="2"
>>> hub.rollback()
>>> hub.end()
>>> book.title #"2"
>>> book.sync()
>>> book.title #"1"
如上的例子中,先设置了title为1,之后回滚(rollback),并结束了事务,这样等于丢弃了修改。此时对象中的值还是2,但是数据库中的值为1。此时再次同步(sync),就将数据库中的值赋予到了对象中,则title再次变为1。
不像之前的例子,这里没有"COMMIT : auto"。因为使用了事务之后就自动关闭了自动提交,当然还是可以通过增加一个"autoCommit=0"参数到URI之中来开启这个功能。
当对象与数据库不同步时是很不好的。通过调用sync(),可以同步,就是从数据库中重新装入值。
可以通过destroySelf方法删除本记录。
完成...?