1、Web.py Cookbook 简体中文版简体中文版简体中文版简体中文版 欢迎来到 web.py 0.3的 Cookbook。提醒您注意:某些特性在之前的版本中并不可用。当前开发版本是 0.3。 格式格式格式 格式 1. 在编排内容时,请尽量使用 cookbook格式 .如: 问题问题问题 问题: : :如何访问数据库中的数据 如何访问数据库中的数据如何访问数据库中的数据如何访问数据库中的数据? ? ? 解法解法解法 解法: : :使用如下代码 使用如下代码使用如下代码使用如下代码 . 2. 请注意,网址中不必含有 “web“。如 “/cookbook/select“,而非“/cookbook
2、/web.select“。 3. 该手册适用于 0.3版本,所以您在添加代码时,请确认代码能在新版本中工作。 基本应用基本应用基本应用基本应用 : Hello World 提供静态文件访问 理解 URL控制 跳转与重定向 使用子应用 提供 XML访问 从 post读取原始数据 高级应用高级应用高级应用高级应用 用 web.ctx获得客户端信息 应用处理器,添加钩子和卸载钩子 如何使用 web.background 自定义 NotFound信息 如何流传输大文件 对自带的 webserver日志进行操作 用 cherrypy提供 SSL支持 实时语言切换 Sessions and user st
3、ate 会话和用户状态会话和用户状态会话和用户状态会话和用户状态 : 如何使用 Session 如何在调试模式下使用 Session 在 template中使用 session 如何操作 Cookie 用户认证 一个在 postgreSQL数据库环境下的用户认证的例子 如何在子应用中操作 Session Utils 实用工具实用工具实用工具实用工具 : 如何发送邮件 如何利用 Gmail发送邮件 使用 soaplib实现 webservice Templates 模板模板模板 模板 Templetor: web.py 模板系统 使用站点布局模板 交替式风格 (未译 ) 导入函数到模板中 (未译
4、 ) 模板文件中的 i18n支持 在 web.py中使用 Mako模板引擎 在 web.py中使用 Cheetah模板引擎 在 web.py中使用 Jinja2模板引擎 如何在谷歌应用程序引擎使用模板 Testing 测试测试测试 测试 : Testing with Paste and Nose (未译 ) RESTful doctesting using an applications request method (未译 ) User input 用户输入用户输入用户输入用户输入 : 文件上传 保存上传的文件 上传文件大小限定 通过 web.input 接受用户输入 怎样使用表单 显示个别
5、表单字段 Database 数据库数据库数据库数据库 使用多数据库 Select: 查询数据 Update: 更新数据 Delete: 删除数据 Insert: 新增数据 Query: 高级数据库查询 怎样使用数据库事务 使用 sqlalchemy 整合 SQLite UDF (用户定义函数 ) 到 webpy 数据库层 使用字典动态构造 where子句 Deployment 部署部署部署 部署 : 通过 Fastcgi和 lighttpd部署 通过 Webpy和 Nginx with FastCGI搭建 Web.py CGI deployment through Apache (未译 ) c
6、ustom essay mod_python deployment through Apache (requested) 通过 Apache和 mod_wsgi部署 mod_wsgi deployment through Nginx (未译 ) Fastcgi deployment through Nginx (未译 ) Subdomains 子域名子域名子域名子域名 : Subdomains and how to access the username (requested) Hello World! 问题问题问题 问题 如何用 web.py实现 Hello World!? 解法解法解法 解
7、法 import web urls = (“/.*“, “hello“) app = web.application(urls, globals() class hello: def GET(self): return Hello, world! if _name_ = “_main_“: app.run() 提示提示提示 提示: : :要保证网址有无 要保证网址有无要保证网址有无要保证网址有无 /结尾结尾结尾 结尾, , ,都能指向同一个类 都能指向同一个类都能指向同一个类都能指向同一个类。 。 。就要多写几行代码 就要多写几行代码就要多写几行代码就要多写几行代码, , ,如下 如下如下 如
8、下: : : 在 URL开头添加代码: /(.*)/, redirect, 然后用 redirect类处理以 /结尾的网址: class redirect: def GET(self, path): web.seeother(/ + path) 提供提供提供 提供静态文件 静态文件静态文件静态文件 (诸如诸如诸如 诸如 js脚本脚本脚本 脚本 , css样式表和图象文件样式表和图象文件样式表和图象文件样式表和图象文件 ) 问题问题问题 问题 如何在 web.py自带的 web server中提供静态文件访问? 解法解法解法 解法 在当前应用的目录下,创建一个名为 static的目录,把要提供访
9、问的静态文件放在里面即可。 例如 , 网址 http:/localhost/static/logo.png 将发送 ./static/logo.png 给客户端。 理解理解理解 理解 URL控制控制控制 控制 问题 : 如何为整个网站设计一个 URL控制方案 / 调度模式 解决 : web.py的 URL控制模式是简单的、强大的、灵活的。在每个应用的最顶部,你通常会看到整个 URL调度模式被定义在元组中 : urls = ( “/tasks/?“, “signin“, “/tasks/list“, “listing“, “/tasks/post“, “post“, “/tasks/chgpas
10、s“, “chgpass“, “/tasks/act“, “actions“, “/tasks/logout“, “logout“, “/tasks/signup“, “signup“ ) 这些元组的格式是 : URL路径 , 处理类 这组定义有多少可以定义多少。如果你并不知道 URL路径和处理类之间的关系,请在阅读 cookbook之前先阅读 Hello World example,或者 快速入门 。 路径匹配 你可以利用强大的正则表达式去设计更灵活的 URL路径。比如 /(test1|test2) 可以捕捉 /test1 或 /test2。要理解这里的关键,匹配是依据 URL路径的。比如下
11、面的 URL: http:/localhost/myapp/greetings/hello?name=Joe 这个 URL的路径是 /myapp/greetings/hello。 web.py会在内部给 URL路径加上 和 $ ,这样 /tasks/ 不会匹配 /tasks/addnew。 URL匹配依赖于 “路径 ”,所以不能这样使用,如: /tasks/delete?name=(.+) ,?之后部分表示是 “查询 ”,并不会被匹配。阅读 URL组件的更多细节,请访问web.ctx。 捕捉参数 你可以捕捉 URL的参数,然后用在处理类中 : /users/list/(.+), “list_u
12、sers“ 在 list/后面的这块会被捕捉,然后作为参数被用在 GET或 POST: class list_users: def GET(self, name): return “Listing info about user: 0“.format(name) 你可以根据需要定义更多参数。同时要注意 URL查询的参数 (?后面的内容 )也可以用 web.input()取得。 开发子程序的时候注意 为了更好的控制大型 web应用, web.py支持 子程序 。在为子程序设计URL模式的时候,记住取到的路径 (web.ctx.path)是父应用剥离后的。比如,你在主程序定义了 URL“/blog
13、“跳转到 blog子程序,那没在你 blog子程序中所有 URL都是以 “/“开头的,而不是 “/blog“。查看 web.ctx取得更多信息。 跳转跳转跳转 跳转 (seeother)与重定向与重定向与重定向与重定向 (redirect) web.seeother 和和和 和 web.redirect 问题问题问题 问题 在处理完用户输入后(比方说处理完一个表单),如何跳转到其他页面? 解法解法解法 解法 class SomePage: def POST(self): # Do some application logic here, and then: raise web.seeother
14、(/someotherpage) POST方法接收到一个 post并完成处理之后,它将给浏览器发送一个303消息和新网址。接下来,浏览器就会对这个新网址发出 GET请求,从而完成跳转。 注意: web.seeother和 web.redirect不支持 0.3以下版本。 区别区别区别 区别 用 web.redirect方法似乎也能做同样的事情,但通常来说,这并太友好。因为 web.redirect发送的是 301消息这是永久重定向。因为大多数Web浏览器会缓存新的重定向,所以当我们再次执行该操作时,会自动直接访问重定向的新网址。很多时候,这不是我们所想要的结果。所以在提交表单时,尽量使用 se
15、eother。但是在下面要提到的这种场合,用redirect却是最恰当的:我们已经更改了网站的网址结构,但是仍想让用户书签 /收藏夹中的旧网址不失效。 (注:要了解 seeother和 redirect的区别,最好是看一下 http协议中不同消息码的含义。 ) 使用子应用使用子应用使用子应用使用子应用 问题问题问题 问题 如何在当前应用中包含定义在其他文件中的某个应用? 解法解法解法 解法 在 blog.py中 : import web urls = ( “, “reblog“, “/(.*)“, “blog“ ) class reblog: def GET(self): raise web.
16、seeother(/) class blog: def GET(self, path): return “blog “ + path app_blog = web.application(urls, locals() 当前的主应用 code.py: import web import blog urls = ( “/blog“, blog.app_blog, “/(.*)“, “index“ ) class index: def GET(self, path): return “hello “ + path app = web.application(urls, locals() if _na
17、me_ = “_main_“: app.run() 提供提供提供 提供 XML访问访问访问 访问 问题问题问题 问题 如何在 web.py中提供 XML访问? 如果需要为第三方应用收发数据,那么提供 xml访问是很有必要的。 解法解法解法 解法 根据要访问的 xml文件 (如 response.xml)创建一个 XML模板。如果 XML中有变量,就使用相应的模板标签进行替换。下面是一个例子: $def with (code) $code 为了提供这个 XML,需要创建一个单独的 web.py程序 (如 response.py),它要包含下面的代码。注意:要用 “web.header(Conte
18、nt-Type, text/xml)“来告知客户端正在发送的是一个 XML文件。 import web render = web.template.render(templates/, cache=False) urls = ( /(.*), index ) app = web.application(urls, globals() class index: def GET(self, code): web.header(Content-Type, text/xml) return render.index(code) web.webapi.internalerror = web.debuge
19、rror if _name_ = _main_: app.run() 从从从 从 post读取原始数据读取原始数据读取原始数据读取原始数据 介绍介绍介绍 介绍 有时候,浏览器会通过 post发送很多数据。在 webpy,你可以这样操作。 代码代码代码 代码 class RequestHandler(): def POST(): data = web.data() # 通过这个方法可以取到数据 高级应用高级应用高级应用高级应用 web.ctx 问题问题问题 问题 如何在代码中得到客户端信息?比如:来源页面 (referring page)或是客户端浏览器类型 解法解法解法 解法 使用 web.c
20、tx即可。首先讲一点架构的东西: web.ctx基于 threadeddict类,又被叫做 ThreadDict。这个类创建了一个类似字典 (dictionary-like)的对象,对象中的值都是与线程 id相对应的。这样做很妙 ,因为很多用户同时访问系统时,这个字典对象能做到仅为某一特定的 HTTP请求提供数据 (因为没有数据共享,所以对象是线程安全的 ) web.ctx保存每个 HTTP请求的特定信息,比如客户端环境变量。假设,我们想知道正在访问某页面的用户是从哪个网页跳转而来的: 例子例子例子 例子 class example: def GET(self): referer = web.
21、ctx.env.get(HTTP_REFERER, http:/) raise web.seeother(referer) 上述代码用 web.ctx.env获取 HTTP_REFERER的值。如果 HTTPREFERER不存在,就会将 做为默认值。接下来,用户就会被重定向回到之前的来源页面。 web.ctx另一个特性,是它可以被 loadhook赋值。例如:当一个请求被处理时,会话 (Session)就会被设置并保存在 web.ctx中。由于 web.ctx是线程安全的,所以我们可以象使用普通的 python对象一样,来操作会话 (Session)。 ctx中的数据成员中的数据成员中的数据成
22、员中的数据成员 Request environ 又被写做 . env 包含标准 WSGI环境变量的字典。 home 应用的 http根路径 (译注:可以理解为应用的起始网址,协议站点域名应用所在路径 )例: http:/example.org/admin homedomain 应用所在站点 (可以理解为协议域名 ) http:/example.org homepath 当前应用所在的路径,例如: /admin host 主机名(域名)用户请求的端口(如果没有的话,就是默认的 80端口),例如: example.org, example.org:8080 ip 用户的 IP地址,例如: xxx.
23、xxx.xxx.xxx method 所用的 HTTP方法,例如: GET path 用户请求路径,它是基于当前应用的相对路径。在子应用中,匹配外部应用的那部分网址将被去掉。例如:主应用在 code.py中,而子应用在 admin.py中。在 code.py中 , 我们将 /admin关联到admin.app。 在 admin.py中 , 将 /stories关联到 stories类。在stories中 , web.ctx.path就是 /stories, 而非 /admin/stories。形如: /articles/845 protocol 所用协议,例如: https query 跟在
24、? 字符后面的查询字符串。如果不存在查询参数,它就是一个空字符串。例如: ?fourlegs=good now = datetime.now from time import sleep urls = ( /, index, ) class index: backgrounder def GET(self): print “Started at %s“ % now() print “hit f5 to refresh!“ longrunning() background def longrunning(): for i in range(10): sleep(1) print “%s: %s“
25、 % (i, now() if _name_ = _main_: run(urls, globals() 在请求 http:/localhost:8080/时,将自动重定向到类似http:/localhost:8080/?_t=3080772748的网址 (t后面的数字就是background线程 id),接下来 (在点击几次刷新之后 )就会看到如下信息: Started at 2008-06-14 15:50:26.764474 hit f5 to refresh! 0: 2008-06-14 15:50:27.763813 1: 2008-06-14 15:50:28.763861 2:
26、2008-06-14 15:50:29.763844 3: 2008-06-14 15:50:30.763853 4: 2008-06-14 15:50:31.764778 5: 2008-06-14 15:50:32.763852 6: 2008-06-14 15:50:33.764338 7: 2008-06-14 15:50:34.763925 8: 2008-06-14 15:50:35.763854 9: 2008-06-14 15:50:36.763789 提示提示提示 提示 web.py在 background.threaddb字典中保存线程信息。这就很容易检查线程的状态; cl
27、ass threaddbviewer: def GET(self): for k, v in background.threaddb.items(): print “%s - %s“ % ( k, v ) web.py并不会主动去清空 threaddb词典,这使得输出 (如http:/localhost:8080/?_t=3080772748)会一直执行,直到内存被用满。 通常是在 backgrounder函式中做线程清理工作,是因为 backgrounder可以获得线程 id(通过 web.input()得到 “_t“的值,就是线程 id),从而根据线程 id来回收资源。这是因为虽然 bac
28、kground能知道自己何时结束,但它无法获得自己的线程 id,所以 background无法自己完成线程清理。 还要注意 How not to do thread local storage with Python 在 python中如何避免多线程本地存储 - 线程 ID有时会被重用 (可能会引发错误 ) 在使用 web.background时,还是那句话 “小心为上 ” 自定义自定义自定义自定义 NotFound消息消息消息 消息 问题问题问题 问题 如何定义 NotFound消息和其他消息? 解法解法解法 解法 import web urls = (.) app = web.applic
29、ation(urls, globals() def notfound(): return web.notfound(“Sorry, the page you were looking for was not found.“) # You can use template result like below, either is ok: #return web.notfound(render.notfound() #return web.notfound(str(render.notfound() app.notfound = notfound 要返回自定义的 NotFound消息,这么做即可:
30、 class example: def GET(self): raise web.notfound() 也可以用同样的方法自定义 500错误消息: def internalerror(): return web.internalerror(“Bad, bad server. No donut for you.“) app.internalerror = internalerror 如何流传输大文件如何流传输大文件如何流传输大文件如何流传输大文件 问题问题问题 问题 如何流传输大文件? 解法解法解法 解法 要流传输大文件,需要添加传输译码 (Transfer-Encoding)区块头,这样才能一
31、边下载一边显示。否则,浏览器将缓冲所有数据直到下载完毕才显示。 如果这样写:直接修改基础字符串 (例中就是 j),然后用 Yield返回是没有效果的。如果要使用 Yield,就要向对所有内容使用 yield。因为这个函式此时是一个产生器。 (注:请处请详看 Yield文档,在此不做过多论述。 ) 例子 # Simple streaming server demonstration # Uses time.sleep to emulate a large file read import web import time urls = ( “/“, “count_holder“, “/(.*)“,
32、 “count_down“, ) app = web.application(urls, globals() class count_down: def GET(self,count): # These headers make it work in browsers web.header(Content-type,text/html) web.header(Transfer-Encoding,chunked) yield Prepare for Launch! j = Liftoff in %s. yield count = int(count) for i in range(count,0
33、,-1): out = j % i time.sleep(1) yield out yield time.sleep(1) yield Lift off class count_holder: def GET(self): web.header(Content-type,text/html) web.header(Transfer-Encoding,chunked) boxes = 4 delay = 3 countdown = 10 for i in range(boxes): output = % (countdown - i) yield output time.sleep(delay)
34、 if _name_ = “_main_“: app.run() 管理自带管理自带管理自带管理自带 webserver日志日志日志 日志 问题问题问题 问题 如何操作 web.py自带的 webserver的日志? 解法解法解法 解法 我们可以用 wsgilog来操作内置的 webserver的日志,并做其为中间件加到应用中。 如下,写一个 Log类继承 wsgilog.WsgiLog,在 _init_中把参数传给基类,如 这个例子 : import sys, logging from wsgilog import WsgiLog, LogIO import config class Log(
35、WsgiLog): def _init_(self, application): WsgiLog._init_( self, application, logformat = %(message)s, tofile = True, file = config.log_file, interval = config.log_interval, backups = config.log_backups ) sys.stdout = LogIO(self.logger, logging.INFO) sys.stderr = LogIO(self.logger, logging.ERROR) 接下来,
36、当应用运行时,传递一个引用给上例中的 Log类即可 (假设上面代码是 mylog模块的一部分,代码如下 ): from mylog import Log application = web.application(urls, globals() application.run(Log) 用用用 用 cherrypy提供提供提供 提供 SSL支持支持支持 支持 问题问题问题 问题 如何用内置的 cheerypy提供 SSL支持? 解法解法解法 解法 import web from web.wsgiserver import CherryPyWSGIServer CherryPyWSGIServe
37、r.ssl_certificate = “path/to/ssl_certificate“ CherryPyWSGIServer.ssl_private_key = “path/to/ssl_private_key“ urls = (“/.*“, “hello“) app = web.application(urls, globals() class hello: def GET(self): return Hello, world! if _name_ = “_main_“: app.run() 实时语言切换实时语言切换实时语言切换实时语言切换 问题问题问题 问题 : 如何实现实时语言切换?
38、 解法解法解法 解法 : 首先你必须阅读 模板语言中的 i18n支持 , 然后尝试下面的代码。 文件 : code.py import os import sys import gettext import web # File location directory. rootdir = os.path.abspath(os.path.dirname(_file_) # i18n directory. localedir = rootdir + /i18n # Object used to store all translations. allTranslations = web.storag
39、e() def get_translations(lang=en_US): # Init translation. if allTranslations.has_key(lang): translation = allTranslationslang elif lang is None: translation = gettext.NullTranslations() else: try: translation = gettext.translation( messages, localedir, languages=lang, ) except IOError: translation =
40、 gettext.NullTranslations() return translation def load_translations(lang): “Return the translations for the locale.“ lang = str(lang) translation = allTranslations.get(lang) if translation is None: translation = get_translations(lang) allTranslationslang = translation # Delete unused translations.
41、for lk in allTranslations.keys(): if lk != lang: del allTranslationslk return translation def custom_gettext(string): “Translate a given string to the language of the application.“ translation = load_translations(session.get(lang) if translation is None: return unicode(string) return translation.uge
42、ttext(string) urls = ( /, index ) render = web.template.render(templates/, globals= _: custom_gettext, ) app = web.application(urls, globals() # Init session. session = web.session.Session(app, web.session.DiskStore(sessions), initializer= lang: en_US, ) class index: def GET(self): i = web.input() l
43、ang = i.get(lang, en_US) # Debug. print sys.stderr, Language:, lang sessionlang = lang return render.index() if _name_ = “_main_“: app.run() 模板文件 : templates/index.html. $_(Hello) 不要忘记生成必要的 po DBStore被创建要传入两个参数: db对象和 session的表名。 db = web.database(dbn=postgres, db=mydatabase, user=myname, pw=) store
44、 = web.session.DBStore(db, sessions) session = web.session.Session(app, store, initializer=count: 0) web.config中的 sessions_parameters保存着 session的相关设置,sessions_parameters本身是一个字典,可以对其修改。默认设置如下: web.config.session_parameterscookie_name = webpy_session_id web.config.session_parameterscookie_domain = Non
45、e web.config.session_parameterstimeout = 86400, #24 * 60 * 60, # 24 hours in seconds web.config.session_parametersignore_expiry = True web.config.session_parametersignore_change_ip = True web.config.session_parameterssecret_key = fLjUfxqXtfNoIldA0A0J web.config.session_parametersexpired_message = Se
46、ssion expired cookie_name - 保存 session id的 Cookie的名称 cookie_domain - 保存 session id的 Cookie的 domain信息 timeout - session的有效时间 ,以秒为单位 ignore_expiry - 如果为 True, session就永不过期 ignore_change_ip - 如果为 true,就表明只有在访问该 session的IP与创建该 session的 IP完全一致时, session才被允许访问。 secret_key - 密码种子,为 session加密提供一个字符串种子 expir
47、ed_message - session过期时显示的提示信息。 在调试模式下使用在调试模式下使用在调试模式下使用在调试模式下使用 session 问题问题问题 问题 如何在调试模式下使用 session? 解法解法解法 解法 使用 web.py自带的 webserver提供 web服务时, web.py就运行在调试模式下。当然最简单的办法就是禁用调试,只要令 web.config.debug = False即可。 import web web.config.debug = False # rest of your code 如果非要用调试模式下使用 session,可以用非主流的一些办法。哈哈
48、 因为调试模式支持模块重载入 (重载入,绝非重载。是 reload,而非override),所以 reloader会载入主模块两次,因此,就会创建两个 session对象。但我们只要把 session存储在全局的数据容器中,就能避免二次创建 session。 下面这个例子就是把 session保存在 web.config中: import web urls = (“/“, “hello“) app = web.application(urls, globals() if web.config.get(_session) is None: session = web.session.Session(app, web.session.DiskStore(sessions), count: 0) web.config._session = session else: session = web.c