tornado表单和模板
在第一章中,我们学习了使用tornado创建一个web应用的基础知识。包括处理函数,HTTP方法以及tornado的框架的总体结构。在这一章中,我们学习tornado的更加强大的功能—-表单和模板。
和大多数的web框架一样,tornado的一个重要的目标就是帮助你更快地编写程序,尽可能整洁地复用更多的代码。尽管tornado灵活,可以使用几乎所有python支持的模板语言,tornado自身也提供了一个轻量级,快速并且灵活的模板语言,在tornado.template模块中。
简单示例 Poem Maker Pro
poemmaker.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import os.path import tornado.httpserver import tornado.options import tornado.web import tornado.ioloop from tornado.options import define, options define("port", default=8000, help="run on the given port", type=int) class IndexHandler(tornado.web.RequestHandler): def get(self): self.render("index.html") class PoemPageHandler(tornado.web.RequestHandler): def post(self): noun1 = self.get_argument("noun1") noun2 = self.get_argument("noun2") verb = self.get_argument("verb") noun3 = self.get_argument("noun3") self.render("poem.html", roads=noun1, wood=noun2, made = verb, difference=noun3) if __name__ == "__main__": tornado.options.parse_command_line() app = tornado.web.Application(handlers=[(r"/", IndexHandler), (r"/poem", PoemPageHandler)], template_path = os.path.join(os.path.dirname(__file__), "templates"), static_path = os.path.join(os.path.dirname(__file__), "static"), debug=True) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start()
|
除了上述poemmaker.py,还需要在文件夹templates下添加以下两个文件。
/templates/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!DOCTYPE html> <html> <head><title>Poem Maker Pro</title></head> <body> <h1>Enter terms below.</h1> <form method="post" action="/poem"> <p>Plural noun <br/> <input type="text" name="noun1" /></p> <p>Singular noun<br/> <input type="text" name="noun2" /></p> <p>Verb (haha)<br /> <input type="text" name="verb" /></p> <p>Noun<br/> <input type="text" name="noun3" /> </p> <input type="submit"/> </form> </body> </html>
|
/templates/poem.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <head><title>this is the poem</title></head> <body> <h1>Your poem</h1> <p>Two {{roads}} diverged in a {{wood}}, and I—<br/> I took the one less tracelled by, <br/> and that has {{made}} all the {{difference}}.</p> </body> </html>
|
运行python文件:
此时访问8000端口会出现首页上包括多个表单等待用户输入。这个表单包括多个文本域(命名为noun1, noun2等),其中内容将在用户点击“submit”按钮时以post请求的方式发送到/poem。
当点击submit按钮之后,tornado应用跳转到poem.html,插入你在表单中填写的值。结果是显示的一首诗。
下面我们逐条讲述当前示例中的每一个涉及到的知识点:
2.1.1渲染模板
从结构上讲,poemmaker和第一章的例子很相似。我们定义了几个RequestHandler子类并把它们传递给tornado.web.Application对象。那么有什么不一样的地方呢?首先我们向Application对象的init方法传递了一个template参数。
1
| template_path = os.path.join(os.path.dirname(__file__), "templates")
|
template_path参数告诉tornado在哪里寻找模板文件。我们将在第三章中讲解其确切的用法和性质。而它的基本要点是:模板是一个允许你嵌入python代码片段的html文件。上面的代码告诉python在你的tornado应用文件相同目录下的templates文件夹下寻找模板文件。
一旦我们告诉tornado在哪里寻找模板,我们就可以使用RequestHandlser类的render方法来告诉tornado读入模板文件,插入其中的模板代码,并将结果返回给浏览器。比如,在IndexHandler中,我们可以发现语句:
1
| self.render("index.html")
|
这段代码告诉tornado载templates文件夹下找到一个名字为index.html的文件,读取其中的内容,并发送给浏览器。
2.12 填充
实际上index.html完全不能称之为模板,因为它所包含的完全是已经编写好的html标记。这可以是模板一个不错的使用方式。但是在通常情况下我们更希望html输出可以结合我们的程序传入给模板的值。模板poem.html使用PoemPageMaker渲染,它是一个模板。
我们可以看到,在poem.html中,它可以看到模板中有一些被双大括号括起来的字符串,就像这样:
1 2 3
| <p>Two {{roads}} diverged in a {{wood}}, and I—<br/> I took the one less tracelled by, <br/> and that has {{made}} all the {{difference}}.</p>
|
在双大括号中的单词是占位符,当我们渲染模板时希望以实际值代替。我们可以使用render函数中传递关键字参数的方法 指定什么值将被填充到html文件中的对应位置,其中关键字对应模板文件中占位符的名字。下面是在poemPageMaker中相应的代码部分:
1 2 3 4 5
| noun1 = self.get_argument("noun1") noun2 = self.get_argument("noun2") verb = self.get_argument("verb") noun3 = self.get_argument("noun3") self.render("poem.html", roads=noun1, wood=noun2, made = verb, difference=noun3)
|
在这里,我们告诉模板使用变量noun1作为模板中roads的值,以此类推。
2.13 设置静态路径
你可以通过向Application类的构造函数传递一个名为static_path的参数来告诉tornado从文件系统一个特定的位置提供静态文件。
在上述代码中如下:
1
| static_path = os.path.join(os.path.dirname(__file__), "static"))
|
在这里,我们设置了一个当前应用目录下名为static的子目录作为static_path的参数。应用将读取static中的文件作为静态文件
2.1.4使用static_url生成url
tornado模板提供了一个叫做static_url的函数生成static目录下文件的url.让我们来看看在index.html中static_url的调用代码的示例代码:
1
| <link rel="stylesheet" href="{{static_url("style.css")}}">
|
这个对static_url的调用生成了url的值,并渲染出类似下面的代码:
1
| <link rel="stylesheet" href="/static/style.css?v=ab12>
|
那么为什么使用static_url而不是在你的模板中硬编码呢?原因有以下两点:
第一,static_url函数创建了一个基于文件内容的hash值,并将其添加到url末尾(查询字符串的参数是v).这个hash值确保浏览器总是加载一个文件的最新版本而不是以前的缓存版本。无论是在开发阶段还是部署到生产环境,都非常有用,因为用户不必为了可到你的静态内容去清除浏览器的缓存了。
第二,你可以改变你的url结构,而不用改变模板中的代码。例如,你可以配置tornado响应来自路径/s/filename的请求时提供静态内容,而不是默认的/static路径。如果你使用static_url而不是硬编码,这个时候你不需要改变代码中的值。
debug参数
在这个例子中,你可能注意到了debug=True的使用。它调用了一个便利的测试模块:tornado.autoreload模块,此时,一旦主要的python文件被修改,tornado会尝试重启服务器,而且载模板改变的时候会自动刷新。对于快速改变和实时更新这非常棒,但不要在生产上使用它,因为他将防止tornado缓存模板。
2.2模板语法
既然我们已经看到了一个模板在实际应用中的例子,哪买让我们深入了解它们是如何工作的。tornado模板是被python表达式和控制语句标记的简单文本文件。tornado的语法非常直接。在2.1节中,我们展示了如何在一个web应用中使用render方法传送html给浏览器。你可以在tornado应用之外使用python解释器导入模块尝试模板系统,此时结果会被直接输出出来。
1 2 3 4
| >>> from tornado.template import Template >>> content = Template("<html><body><h1>{{header}}</h1></body></html>") >>> print content.generate(header="Welcome!") <html><body><h1>Welcome!</h1></body></html>
|
2.21填充表达式
上面的演示我们也可以看出,向html中填充python变量的值,我们可以使用双大括号;其实我们可以在双大括号中插入python表达式,它会自动计算出其实际值:
1 2 3 4 5 6 7
| >>> from tornado.template import Template >>> print Template("{{ 1+1 }}").generate() 2 >>> print Template("{{ 'scrambled eggs'[-4:] }}").generate() eggs >>> print Template("{{ ', '.join([str(x*x) for x in range(10)]) }}").generate() 0, 1, 4, 9, 16, 25, 36, 49, 64, 81
|
2.22 控制流语句
同样也可以在tornado模板中使用python条件和循环语句。控制与句以{ %和% }包围,并以类似下面形式使用:
1 2 3
| {% if page is None %} 或 {% if len(entrise) == 3 %}
|
控制语句的大部分就像对应的python语句一样工作,支持if,for,wihle,try。在这些情况下,语句块以{ %开始,以 % }结束
模板:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!DOCTYPE html> <html> <head><title>{{title}}</title></head> <body> <h1>{{header}}</h1> <ul> {%for book in books%} <li>{{book}}</li> {% end %} </ul> </body> </html>
|
处理函数:
1 2 3 4
| class BookHandler(tornado.web.RequestHandler): def get(self): self.render("books.html", title="Home Page", header="Books that are great",\ books=["python", "c++", "pascal"])
|
这个时候将渲染出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html> <head><title>{{title}}</title></head> <body> <h1>{{header}}</h1> <ul> <li>python</li> <li>C++</li> <li>pascal</li> {% end %} </ul> </body> </html>
|
不像其他的python模板渲染系统,tornado模板语言的一个最好的东西是在if和for语句块中可以使用的表达式没有限制.因此,你可以在你的模板中执行所有的python代码。
同样,你也可以在你的控制语句块中间使用{ % set foo = “bar” % }来设置变量的值。你还有很多可以在控制语句块中做的事情,但是,在大多数情况下,你最好使用UI模块来做更复杂的划分。我们稍后会更详细的看到这一点。
2.2.3在模板中使用函数
tornado在所有模板中默认提供了一些遍历的函数。他们包括:
1 2 3 4 5 6 7 8
| escape(s) url_escape(s) json_encode(val) squeeze(s)
|
在模板中使用一个你自己编写的函数也很简单:只需要将函数名作为模板的参数传递即可,就像其他的变量。
1 2 3 4 5 6 7 8 9
| >>> from tornado.template import Template >>> def disemvowel(s): ... return ''.join([x for x in s if x not in 'aeiou']) ... >>> disemvowel("george") 'grg' >>> print Template("my name is {{d('mortimer')}}").generate(d=disemvowel) my name is mrtmr
|