(前身为Gluon) 由Massimo Di Pierro 创建 由limodou翻译
也许你已经听过说web2py,它是Web开发框架中的新成员。web2py使用Python进行编写,所 以它很可靠并且比Ruby on Rails快。web2py本身也是一个web应用,所以你可以通过浏览 器对你的应用程序进行所有的开发、部署和维护,而这种方式使得它比其它任何框架都易 于使用。除此之外,web2py被打成一个完整的包(可用于Windows, Mac或Unix/Linux),同 时包含了开发所需要的一切(包括Python, SQLite3, 和多线程web服务器). [译注: 现在 是cherrypy]
你可以从这里得到web2py: http://www.web2py.com 。 这篇文档在设计时有意模仿了 http://onlamp.com/pub/a/onlamp/2005/01/20/rails.html 。 这样你就可以同Rails进行比较了。
Python是一种面向对象的编程语言,被设计得超级容易教学,并且在功能上没有任何打折。 绝大部分Java算法都可以用Python来重写,而长度仅为原来的二十分之一。Python自带了 一整套可移植的库,包括对许多标准互联网协议(http, xml, smtp, pop, 和imap, 只提到了几个)的支持和对操作系统API的支持。
web2py是使用Python编写的一个开源web框架,并可以使用Python进行数据库驱动的web 应用方面的快速编程。如今有许多的web框架,包括Ruby on Rails, Django, Pylons和 Turbo Gears,所以为什么又开发一个呢?我是在心中带着下面的目标进行web2py的开发的:
尽可能象Rails, 但是用Python来开发,这样可以更稳定和更高效。
一体化的包,不需要安装、无配置和不需要shell脚本。
超级容易教学(我的工作是教学)。所以我把web2py本身也做成了一个web应用程序。
从上到下的设计,这样web2py的API从头一天开始就是稳定的。
web2py编程象Rails编程一样容易,但如果你既不会Python也不会Ruby,web2py学起来要比Rails容易多了。
最重要的是,与同等功能的J2EE或PHP相比,web2py所需的代码量要少,同时它强迫你使用一种非常好并且安全的编程习惯。
web2py阻止目录遍历,SQL注入攻击(SQL injection),跨站脚本执行(cross site scripting),和回复攻击弱点(reply attack vulnerability)。
web2py替你对session,cookie和应用错误进行管理。所有应用错误都会生成一个ticket发送给用户,并且会为管理员生成一条日志项。
web2py会为你编写所有的SQL。它甚至可以创建表并决定何时执行一个数据库迁移的动作。
试一下吧。
访问 http://mdp.cti.depaul.edu/examples 并下载Windows, Mac或Unix文件。
如果你选择使用Windows或Mac版本,你只需要执行:unzip文件,然后分别点击web2py.exe或wweb2py.app。
如果你选择使用Unix版本,你需要安装Python解释器(版本2.4或更高)和SQLite3数据库。 [译注:2.4与2.5有些区别。在2.5中使用sha512的摘要算法,而2.4只使用sha。同时2.5内置了sqlite,因此不需要安装。如果有加密数据,则2.4与2.5下的版本处理可能有不同,需要注意。] 有了这些之后,unzip web2py然后运行
python web2py.py
在生产配置中,你应该使用PostgreSQL或MySQL而不是SQLite3。从web2py的角度来看,修改配置象修改程序中的一行代码一样简单,不过在这里不讨论它,因为开发中你不需要关心它。
在启动时,web2py会问一个问题: “choose the administrator password”,选择一个。 在那之后,web2py会替你打开一个web浏览器(记住未曾输入过命令!),同时显示这个 欢迎页面
点击 “administrative interface”
然后输入在启动时选择的口令。你应该被重定向到管理界面的”site”页面:
这里你可以:
安装和反安装应用程序
创建和设计(编程)你的应用程序
清理错误日志和session
以字节码方式编译应用程序用来分发和快速执行
web2py自带了三个应用程序: admin (管理界面本身), examples (交互文档), 和 welcome (可用来创建其它应用程序的基础模板)。
我们将创建一个在线的协作方式的 cookbook ,用来保存和分享菜谱。我们想让我们的 食谱书有以下功能:
显示所有菜谱的清单。
创建新菜谱和编辑存在的菜谱。
给菜谱设定 category (象”dessert”(餐后甜点)或”soup”(汤))。
如果需要,你可以下载完整的web2py Cookbook例子并且跟着做。
为创建一个新的应用,在合适的字段 [译注:create new application] 上输入一个名字 (在本例中为cookbook),然后点击Submit按钮:
一个新的web2py应用程序不是空的,它是welcome应用程序的克隆。它包含单个controller(控制器), 单个view(视图),基本layout(布局),通用view和称为appadmin(不要与admin混了,admin 是整个站点的管理界面)的数据库管理界面。
你已经运行了web2py web服务器,所以其实没有什么可以测试的。不管怎么样,在 cookbook/design 上点击,你会看到
在这里你可以查看/创建/编辑你的应用程序组件。在 Controllers 下,有一个叫做 default.py 的文件,带有 “exposes index”。如果你在 index 上点击,你创建 的新应用程序会 “welcome you”
任何web2py应用程序由以下内容组成:
Models: 用来描述你的应用程序的数据存储的文件。例如,你的数据库表中的字段、 它们的关系和要求。web2py会告诉你在每个model文件已经定义了哪些表。 [译注: model.tables?]
Controllers: 包含你的应用程序处理逻辑的文件。每个URL唯一映射到一个controller 文件的一个函数上。这个函数可以生成页面、委派一个view来渲染一个页面、重定向 到另一个URL或引发一个异常(根据异常的不同,有可能会产生一个ticket或出现在一个 HTTP错误页面中)。web2py会告诉你每个controller文件所暴露出来的函数。 [译注: 如果在controller中的函数使用``__``开始,如``def __init()``,它将是一个私有 函数,不会被暴露出来,符合Python定义的习惯。]
Views: 包含HTML和特殊的 {{ }} 标签的文件。这些标签可以在由controller中返回的 变量中进行渲染。这是你的应用程序的展现层。web2py会告诉你何时一个view从其它的 view中进行扩展(extend)或导入。
Languages: 包含所有你想要支持为其它任一语言的字符串的翻译列表的文件。这些字 符串需要你明确标识为语言依赖。 [译注:在需要翻译的字符串使用T()函数进行封装。 不过目前好象只能用在controller, view, model中。对于其它的模块好象还不支持。]
Static files: 其它的所有文件,包括图片、CSS、JavaScript,等等。
注意,你即不需要一个编辑器,也不需要知道web2py的目录结构,因为你可以通过design 页面来创建和编辑。 [译注:web2py自带的web编辑器可以很好的支持语法高亮,包括 Python。不过对于某些静态文件,如JavaScript不能进行修改,希望在以后可以改进。]
还要注意,为每个controller函数(在Rails中叫action)给定一个view是一种好的习惯,不 过不一定非要如此,因为当没有指定view( [译注:可以简单理解为模板] )时,web2py 会总是使用 generic.html view来渲染任何页面。
这张图展示了web2py核心功能的通用结构
一个象
http://hostname/cookbook/default/index/bla/bla/bla?variable=value
的链接会产生一个对cookbook应用程序的 default.py controller中的 index() 函数的一个调用。
“bla”, “bla”, 和 “bla” 将被传递为 request.args[0:3] ,而”value”将被保存在 request.vars.variable 中。
controller函数应该象下面一样返回一个dict(字典)
return dict(name=value, othername=othervalue)
这样变量 name 和 othername 将被传到相应的view中。
现在试一试,从 cookbook/design 来创建一个 test.py controller(只需要输入名字并点击Submit),编辑 test.py 然后创建你自已的 index 函数。 [译注:如果文件名不带.py扩展名则web2py会自动添加,其它的也类似。]
回到 cookbook/design 并在 test.py 所暴露出来的 index 函数上点击。
web2py使用了 generic.html view(它是从基础的 layout.html 扩展来的)来对你的 index() 函数返回的变量进行渲染。
回到 cookbook/design ,然后创建一个叫 db.py 的新model(只需要在适当的字段上输入 db 然后点击Submit)。model定义在这里与Rails有些不同。在web2py中,一个model是一个包含了在每个数据库中 所有表 的定义文件。
编辑则才创建的 db.py model,然后输入下面的代码:
这个model定义了两个表 category 和 recipe 。**recipe** 有一个 category 字段,它是一个对 db.category 的引用,并且 date 字段缺省为今天。每个字段有一些要求(这是可选的), categore.name 要求一个新值要满足 IS_NOT_IN_DB(字段必须唯一), recipe.category 要求这个字段满足 IS_IN_DB(引用是有效的), recipe.date 要求它要包含一个有效的日期。 [译注:如果你同时希望它可以为空,或不为空时需要为有效的日期格式,可以使用 IS_NULL_OR(IS_DATETIME())]
这些要求将被强制使用在任一入口的表单中,无论它是管理界面部分或用户生成的部分。 [译注:web2py会提供象SQLFORM这样的东西进行记录的录入,用户也可以使用它。而SQLFORM会对字段的约束项进行检查]
回到 cookbook/design ,在model下,你会看到两个新的链接,分别为 database administration 和 sql.log 。在前者上点击,如果不存在拼写错误时你会看到:
这是你的数据库管理界面。试着插入一个新的category记录:
和一些新的菜谱:
这难道不比Rails简单吗?甚至都不需要和PHP、JSP、ASP、J2EE等相比。
是谁创建的这些表呢?web2py干的!web2py会查找一个叫做db.db的数据库( [译注:在SQLDB中定义的文件名,因为使用的是SQLite3数据库。同时使用SQLite3你还可以使用绝对路径。否则它会在你的应用程序目录下的databases子目录下创建这个数据库文件。]),如果找不到,那么它会创建这个数据库和你刚才定义的表。如果你修改了一个表的定义,web2py会替你修改表结构。如果你定义了另一张表,它会被创建。你可以看一下由web2py为这种迁移所生成的SQL,通过点击 sql.log 。
随便探索管理界面,插入几条记录,并试着列出它们。
这张表可以通过点击表头进行排序,并且当记录超过100条时会进行分页。试一下JOIN(表的联接操作),在SQL FILTER字段上输入 recipe.category=category.id 。
字段 id 是从哪来的?在web2py中的每张表都有一个唯一的整数键叫做 id 。如果你在表的 id 值上点击,你就可以单独修改这条记录。
注意 appadmin.py 是你的cookbook应用程序的一部分,所以你可以对它进行阅读和修改。在这个教程中,我们不这样做,而宁愿从头编写一个新的controller。这样会起到更好的宣传作用。
在 cookbook/design 中,编辑 test.py controller 并加入如下代码:
def recipes():
records=db().select(db.recipe.ALL,orderby=db.recipe.title)
return dict(records=SQLTABLE(records))
现在回到design,在 recipes 上点击,你会看到
注意,传给view的变量 records 是一个 SQLTABLE 对象,它知道如何把自身渲染为CSS友好的HTML。变量 records 是由 generic.html view来渲染的。
让我们再改一下。修改controller为:
注意:
recipes 现在返回记录列表,而不是一个SQLTABLE,而且它会从表的 category 字段生成一个选择 表单 .
show 接受 rquest.vars.id ,并且执行选择,一旦失败,它会重定向到 recipes 。
new_recipe 返回一个SQLFORM对象,它可以根据一个表(db.recipe)的定义创建一个HTML表单。 form.accepts() 执行对表单的校验(根据模型中的需求),用错误信息来更新表单,如果检验成功,它插入新记录到数据库中。
URL(r=request,f='function') 可以在当前的应用程序和controller中生成 “function” 的url,根据HTTP请求。
这块代码使用通用view已经可以完全运行,但是我们将在下面的布局层执行额外的客户化处理。
注意,一些检验器(validator),象用于’datetime’字段上的 IS_DATETIME() ,缺省是自动设置的。 [译注:如果你不想使用缺省的validator可以设置字段的 ``field.requires=[]`` ]
现在为 recipes 创建一个view。这个view叫做 test/recipes.html (在适当的字段上输入带路径的名字然后点击Submit)。
编辑新创建的文件
现在再试着调用 recipes
注意在 {{ }} 标签中的代码是Python代码,需要注意:
不需要缩近,一个代码块以末尾为冒号的行开始,到以开始为pass的行结束(例如:def :return, if:elif:else:pass和try:except:pass)。
view可以看到在model中定义的所有东西加上由controller返回的变量。
{{=something}} 将把 something 渲染成为HTML,之后对特殊字符进行转义。
注意
{{=A(message,_href=link)}}
它是一个html辅助函数。会简单地替你输出
<a href= "link">message</a>
标签。
创建 test/show.html ,包含:
看上去象这样:
最后创建一个 test/new_recipe.html ,包含:
看上去象这样:
注意web2py在表格中是如何把字段名首字母大写的,并且根据指定的需求为category字段生成了一个 SELECT/OPTION 字段。
如果你不喜欢 [web2py]cookbook 的广告条或它的CSS,你可以在 layout.html 文件中编辑它们。
如果你试图提交一个不满足需求(例如试图提交一个空的菜谱)的表单,web2py会通知你相关的提示信息。
我们已经编写了一个可工作的web2py应用程序,仅仅通过浏览器,几个点击和总共53行代码。我们还得到了一个自由使用的数据库管理界面,它可以让你插入、选择、更新和删除单条记录或记录集。
web2py也包括了容易使用的函数来导入和导出CSV格式的表数据,生成RSS feed和RTF文件(与MS Word兼容),处理JSON用于AJAX。
如果想要了解更多关于web2py,请访问网页: http://mdp.cti.depaul.edu
如果你有问题,请加入Google用户组: http://groups.google.com/group/web2py?hl=en
连接到sqlite3数据库文件test.db
>>> db=SQLDB("sqlite://test.db")
或连接到MySQL数据库
>>> db=SQLDB("mysql://username:password@host:port/dbname")
或连接到PostgreSQL数据库
>>> db=SQLDB("postgres://username:password@host:port/dbname")
可用字段类型
>>> tmp=db.define_table('users',\
SQLField('stringf','string',length=32,required=True),\
SQLField('booleanf','boolean',default=False),\
SQLField('passwordf','password'),\
SQLField('textf','text'),\
SQLField('blobf','blob'),\
SQLField('uploadf','upload'),\
SQLField('integerf','integer'),\
SQLField('doublef','double'),\
SQLField('datef','date',default=datetime.date.today()),\
SQLField('timef','time'),\
SQLField('datetimef','datetime'),\
migrate='test_user.table')
一个字段就是一个SQLField类型的对象
>>> SQLField('fieldname', 'fieldtype', length=32,\
default=None,required=False,requires=[])
删除表
>>> db.users.drop()
插入、选择、更新、删除的例子
>>> tmp=db.define_table('person',\
SQLField('name'), \
SQLField('birth','date'),\
migrate='test_person.table')
>>> person_id=db.person.insert(name="Marco",birth='2005-06-22')
>>> person_id=db.person.insert(name="Massimo",birth='1971-12-21')
>>> rows=db().select(db.person.ALL)
>>> for row in rows: print row.name
Marco
Massimo
>>> me=db(db.person.id==person_id).select()[0]
>>> me.name
'Massimo'
>>> db(db.person.name=='Massimo').update(name='massimo')
>>> db(db.person.name=='Marco').delete() # test delete
更新单个记录
>>> me.update_record(name="Max")
>>> me.name
'Max'
复杂搜索条件
>>> rows=db((db.person.name=='Max')&\
(db.person.birth<'2003-01-01')).select()
>>> rows=db((db.person.name=='Max')| \
(db.person.birth<'2003-01-01')).select()
>>> me=db(db.person.id==person_id).select(db.person.name)[0]
>>> me.name
'Max'
>>> rows=db(db.person.birth.month()==12).select()
>>> rows=db(db.person.birth.year()>1900).select()
>>> rows=db(db.person.birth==None).select()
>>> rows=db(db.person.birth!=None).select()
>>> rows=db(db.person.name.upper()=='MAX').select()
>>> rows=db(db.person.name.like('%ax')).select()
>>> rows=db(db.person.name.upper().like('%AX')).select()
>>> rows=db(~db.person.name.upper().like('%AX')).select()
orderby, groupby 和 limitby 的使用
>>> people=db().select(db.person.name,orderby=db.person.name)
>>> order=db.person.name|~db.person.birth
>>> people=db().select(db.person.name,orderby=order)
>>> people=db().select(db.person.name,orderby=order,\
groupby=db.person.name)
>>> people=db().select(db.person.name,orderby=order,limitby=(0,100))
一对多关系的例子
>>> tmp=db.define_table('dog', \
SQLField('name'), \
SQLField('birth','date'), \
SQLField('owner',db.person),\
migrate='test_dog.table')
>>> dog_id=db.dog.insert(name='Snoopy',birth=None,owner=person_id)
简单JOIN(连接)
>>> rows=db(db.dog.owner==db.person.id).select()
>>> for row in rows: print row.person.name,row.dog.name
Max Snoopy
多对多关系的例子
>>> tmp=db.define_table('author',SQLField('name'),\
migrate='test_author.table')
>>> tmp=db.define_table('paper',SQLField('title'),\
migrate='test_paper.table')
>>> tmp=db.define_table('authorship',\
SQLField('author_id',db.author),\
SQLField('paper_id',db.paper),\
migrate='test_authorship.table')
>>> aid=db.author.insert(name='Massimo')
>>> pid=db.paper.insert(title='QCD')
>>> tmp=db.authorship.insert(author_id=aid,paper_id=pid)
SQLSet
>>> authored_papers=db((db.author.id==db.authorship.author_id)&\
(db.paper.id==db.authorship.paper_id))
>>> rows=authored_papers.select(db.author.name,db.paper.title)
>>> for row in rows: print row.author.name, row.paper.title
Massimo QCD
用belongs进行搜索
>>> set=(1,2,3)
>>> rows=db(db.paper.id.belongs(set)).select(db.paper.ALL)
>>> print rows[0].title
QCD
嵌套选择
>>> nested_select=db()._select(db.authorship.paper_id)
>>> rows=db(db.paper.id.belongs(nested_select)).select(db.paper.ALL)
>>> print rows[0].title
QCD
嵌套选择
>>> nested_select=db()._select(db.authorship.paper_id)
>>> rows=db(db.paper.id.belongs(nested_select)).select(db.paper.ALL)
>>> print rows[0].title
QCD
关于本评注系统
本站使用上下文关联的评注系统来收集反馈信息。不同于一般对整章做评注的做法, 我们允许你对每一个独立的“文本块”做评注。一个“文本块”看起来是这样的:
一个“文本块”是一个段落,一个列表项,一段代码,或者其他一小段内容。 你选中它会高亮度显示:
要对文本块做评注,你只需要点击它旁边的标识块:
我们会仔细阅读每个评论,如果可能的话我们也会把评注考虑到未来的版本中去:
如果你愿意你的评注被采用,请确保留下你的全名 (注意不是昵称或简称)
Many, many thanks to Jack Slocum; the inspiration and much of the code for the comment system comes from Jack's blog, and this site couldn't have been built without his wonderful
YAHOO.extlibrary. Thanks also to Yahoo for YUI itself.