在上一章节中,我们看到了如何使用tornado去创建和配置一个简单的web应用。我们学习了:handlers、http方法和tornado的整体框架结构。在这个章节,我们将要开始学习如何在web应用中使用更多更强大的功能。
和大部分web的框架一样,tornado设计的其中一个目标就是帮助你通过tornado更快的完成应用程序,实现代码的高可用和整洁。tornado非常灵活,它几乎支持所有的模板语言,它包括了一个轻量级、快速、灵活的模板。
简单的例子Poem Maker Pro
让我们通过这个名为Poem Maker Pro的例子开始吧!Poem Maker Pro 是一个web应用,它会通过一个html表格去接收用户填写的东西。并且将结果重新在网页中显示出来。
例2-1 poemmaker.py
- import os.path
- import tornado.httpserver
- import tornado.ioloop
- import tornado.options
- import tornado.web
- 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”)
- )
- http_server = tornado.httpserver.HTTPServer(app)
- http_server.listen(options.port)
- tornado.ioloop.IOLoop.instance().start()
除了poemmaker.py之外,你还需要使用子目录templates中的两个文件例2-2、例2-3。
例2-2 index.html
<!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 (past tense)<br><input type=”text” name=”verb”></p>
<p>Noun<br><input type=”text” name=”noun3″></p>
<input type=”submit”>
</form>
</body>
</html>
例2-3 poem.html
<!DOCTYPE html>
<html>
<head><title>Poem Maker Pro</title></head>
<body>
<h1>Your poem</h1>
<p>Two {{roads}} diverged in a {{wood}}, and I—<br>
I took the one less travelled by,<br>
And that has {{made}} all the {{difference}}.</p>
</body>
</html>
你可以通过下面的命令运行这个程序:
- $ python poemmaker.py –port=8000
现在我们可以在浏览器的地址填上http://localhost:8000 。当浏览器打开URL时,首先请求的是根目录(/),在tornado程序中,将会通过渲染index.htm;将首页显示出来。
这个表格包括了一些文本框(named noun1 , noun2 , 等等),文本框的内容在用户点击 Submit 按钮之后将会通过POST 的方式发送给 /poem,现在请填写这个表单并点击Submit吧。
发送POST请求之后,tornado应用将会调用poem.html,将你输入表格的数据读取到变量中,然后把结果传入到poem.html预置的接口中显示出来。
template_path变量定义了tornado将要到哪里去调用模板文件,我们将会在这一章和第三章深入的去了解模板文件的特性和语法,请记住最基本的一点是:HTML模板文件允许你嵌入python代码。上面的代码告诉python去调用tornado应用目录下模板目录中的HTML文件。
一旦我们告诉tornado哪里可以找到模板,我们就可以通过RequestHandler类中的render方法去告诉tornado,读取模板文件,将变量传入模板中的嵌入代码中执行,并将热锅发送给浏览器,在IndexHandler,对于每一个例子我们都可以找到:
Self.render(‘index.html’)
这个代码将会告诉tornado在templates目录中寻找并调用index.html,读取它里面的内容,并发送给浏览器。
传值
index.html就是一个模板,你看它只有一些基本的HTML标签。但是更多时候我们希望能够将我们程序中的参数传送给HTML模板。就像poem.html模板一样,将PoemPageHandler中的参数提取出来在客户端显示。这是一个非常好的例子,让我们来看看它是如何进行工作的。
在poem.html 你可以看到有一些使用双重大括号嵌入的字符串{{and}}在模板中,像这样:
<p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less travelled by,<br>
And that has {{made}} all the {{difference}}.</p>
这个在双重大括号中嵌入的字符串是占位符,当模板被调用的时候,我们可以用实际的变量去替换它。我们可以通过render指定哪些关键的参数会通过占位符插入到HTML中,这些关键的参数的变量名与占位符是一致的。这里是PoemPageHandler中有关变量的代码:
- 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(它的值是来通过get_argument 方法从form表单中获取的)替换模板中的roads占位符,noun2的值替换模板中的wood占位符,等等。我们假定用户分别在form表格中输入了pineapples,grandfather clock,irradiated和supernovae。那么返回的HTML结果应该是这样的:
<p>Two pineapples diverged in a grandfather clock, and I—<br>
I took the one less travelled by,<br>
And that has irradiated all the supernovae.</p>
模板的语法
现在让我们来看一个简单的例子,去了解一些模板语法的细节。模板在tornado里面用一个简单的文本标识符将python的表达式和控制语句标识出来。这些语句在tornado的模板中非常简洁,你如果熟悉Django, Liquid或similar,就会发现tornado有很多与他们相似的地方,非常容易配置。在Poem Maker Pro这个例子中,我们已经展示了如何使用在web应用中使用render方法发送HTML给浏览器,你可以尝试通过导入tornado应用的template 模块到python解释器中,直接输出模板内容了解template模块的功能。
- >>> 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-1中,我们使用大括号去嵌入了一些python的表达式到模板中,你可以使用双重大括号嵌入任意的python表达式,tornado将会自动将表达式的值插入到输出中,下面是一些例子:
- >>> 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
控制语句
你还可以在tornado模板中使用python的控制语句和循环。控制语句使用{%和%}包围起来,类似下面的例子:
{% if page is None %} 或{% if len(entries) == 3 %}
控制语句对于在大部分都和python语法相同,tornado支持 :if, for, while, try 。每一个语句都从{%开始到%}结束。
所以类似下面的模板:
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ header }}</h1>
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% end %}
</ul>
</body>
</html>
我们如果在tornado的类中可以这么处理:
- class BookHandler(tornado.web.RequestHandler):
- def get(self):
- self.render(
- ”book.html”,
- title=”Home Page”,
- header=”Books that are great”,
- books=[
- "Learning Python",
- "Programming Collective Intelligence",
- "Restful Web Services"
- ]
- )
最后处理生成的是下面的html代码
- <html>
- <head>
- <title>Home Page</title>
- </head>
- <body>
- <h1>Books that are great</h1>
- <ul>
- <li>Learning Python</li>
- <li>Programming Collective Intelligence</li>
- <li>Restful Web Services</li>
- </ul>
- </body>
- </html>
tornado的模板与其它python模板系统比起来,对使用表达式和块函数没有任何限制,所以你可以在你的模板中执行完整的python代码。
你还可以通过{% set foo = ‘bar’ %} 这样的形式在控制块中设置参数,还可以在控制块中使用更多的语句。但是在大多数情况下,我们建议你最好使用UI模块去实现复杂的功能,后面我们将会了解更多关于UI模块的细节。
在模板中插入函数
tornado对于模板默认设置了一些有用的功能,他们包括:
escape(s)
用响应的HTML实体去替换字符串中的&<>
url_escape(s)
使用urllib.quote_plus去替换URL编码中的一些特性字符
json_encode(val)
将变量通过json传递(它将会调用底层的json库去转换存储的数据,你可以去查找相关的文档了解这个函数接收的参数和返回的内容)
squeeze(s)
过滤字符串s,用一个空格替换多个空格序列。
tornado1.x的版本中的templates类没有autoescaping功能,在tornado2.0中autoescaping是自动启用的(可以通过autoescape = None关闭这个构造函数),以实现代码的向后兼容性。
一个完整的例子:The Alpha Munger
在例子2-4中,我们将会把之前所有章节学习到的东西放到一起,这个应用被称为 The Alpha Munger,用户输入两个文本:source 和 replacement 。应用将会返回一个副本,使用source将与replacement中首字母相同的单词替换掉。图2-3展示了输入的表单,图2-4显示的是生成的文本。
这个应用包括四个文件:main.py(tornado主程序),style.css(一个css样式表),index.html和munged.html(tornado模板),让我们来看一看这些代码:
例子2-4 main.py
- import os.path
- import random
- import tornado.httpserver
- import tornado.ioloop
- import tornado.options
- import tornado.web
- 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 MungedPageHandler(tornado.web.RequestHandler):
- def map_by_first_letter(self, text):
- mapped = dict()
- for line in text.split(‘\r\n’):
- for word in [x for x in line.split(' ') if len(x) > 0]:
- if word[0] not in mapped: mapped[word[0]] = []
- mapped[word[0]].append(word)
- return mapped
- def post(self):
- source_text = self.get_argument(‘source’)
- text_to_change = self.get_argument(‘change’)
- source_map = self.map_by_first_letter(source_text)
- change_lines = text_to_change.split(‘\r\n’)
- self.render(‘munged.html’, source_map=source_map, change_lines=change_lines,
- choice=random.choice)
- if __name__ == ’__main__‘:
- tornado.options.parse_command_line()
- app = tornado.web.Application(
- handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
- 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()
图片2-3
图片2-4
请注意static_path参数是如何在应用中构造的,我们将会对其做更详细的解释,现在你只需要知道,static_path参数将会指定一个静态资源(如果图片、CSS文件、JavaScript文件等等)的目录给应用程序使用。你还需要指定一个静态目录存放index.html 和munged.html(在例2-5、例2-6中)。
例子2-5 index.html
- <!DOCTYPE html>
- <html>
- <head>
- <link rel=“stylesheet” href=”{{ static_url(“style.css”) }}”>
- <title>The Alpha Munger</title>
- </head>
- <body>
- <h1>The Alpha Munger</h1>
- <p>Enter two texts below. The replacement text will have its words
- replaced by words beginning with the same letter in the source text.</p>
- <form method=“post” action=“/poem”>
- <p>Source text<br>
- <textarea rows=4 cols=55 name=“source”></textarea></p>
- <p>Text for replacement<br>
- <textarea rows=4 cols=55 name=“change”></textarea></p>
- <input type=“submit”>
- </form>
- </body>
- </html>
例子2-6 munged.html
- <!DOCTYPE html>
- <html>
- <head>
- <link rel=“stylesheet” href=”{{ static_url(“style.css”) }}”>
- <title>The Alpha Munger</title>
- </head>
- <body>
- <h1>Your text</h1>
- <p>
- {% for line in change_lines %}
- {% for word in line.split(‘ ’) %}
- {% if len(word) > 0 and word[0] in source_map %}
- <span class=“replaced”
- title=“{{word}}”>{{ choice(source_map[word[0]]) }}</span>
- {% else %}
- <span class=“unchanged” title=“unchanged”>{{word}}</span>
- {% end %}
- {% end %}
- <br>
- {% end %}
- </p>
- </body>
- </html>
最后让我们来看看例子2-7 style.css的内容,并且把它放到命名为static的子目录中(我们稍后会讨论为什么使用static作为子目录)
例子2-7 style.css
- body {
- font-family: Helvetica,Arial,sans-serif;
- width: 600px;
- margin: 0 auto;
- }
- .replaced:hover { color: #00f; }
如何运行
这个tornado应用定义了两个Handler类处理请求,IndexHandler 和MungedPageHandler, IndexHandler类知识简单地将包含一个允许用户通过post提交的表单模板index.html传送给浏览器,当用户提交的时候,将会把表单传送给后台并跳转到/poem。
MungedPageHandler类设置了一个POST类去匹配/poem,当请求到达的时候,它将会执行类中的一些语句,并将结果嵌入到调用的模板munged.html返回给浏览器。在map_by_first_letter方法中切分输入的source文本(表单中填写的值),然后按照单词的首字母去创建一个与source关联的文本(我们将会通过source_map去调用它),这个字典将会传送给munged.html模板,用户指定作为替换的文本munged(同意是从form表单中获取的),我们将会通过python的一个标准库random.choice,随机生成一些数值,将列表中的随机元素提取出来。
在munged.html,我们将会遍历每一行,如果发现当前的单词首字母与source_map中返回的元素相同,我们就会使用random.choice去获取一个随机的单词,将munged中的单词替换掉。每一个单词都会包含一个tag,用于标注这个单词是不是已经被替换了(class=”replaced”)或(class=”unchanged”),我们还会设置一个tag,当用户的鼠标移动到这个替换后的词上面时可以看到原先它是哪个单词的。你可以在图2-5中看到这个效果
图2-5
在这个例子中,你可能已经注意到 debug = True它是调用了一个方便调试的测试模式,tornado.autoreload模块,当python文件的内容发生改变时,tornado将会重启服务器,每一次更新模板都会导致服务重启,它适用于一个需要经常更新或改动的tornado应用,但是不要把它放到生产环境中,因为它会阻止tornado去生成缓存模板。
静态文件
当我们编写web应用的时候,你可能要经常使用 static content , style-sheets, JavaScript file, Images 这样的静态文件,tornado提供了一个很简洁的方式来帮助我们管理静态文件
设置静态static_path
你可以通过template_path将static_path的目录作为变量传递给应用程序,这里是Alhpa Munger 中关于设置静态目录的一小段代码:
- app = tornado.web.Application(
- handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
- template_path=os.path.join(os.path.dirname(__file__), ”templates”),
- static_path=os.path.join(os.path.dirname(__file__), ”static”),
- debug=True
- )
在这里我们设置static_path变量的子目录为static,现在应用程序接收到类似/static/filename.ext的请求时,将会通过访问static目录读取filename.ext文件的方式将文件读取并返回给客户端.
便准的静态URL static_url
在tornado的template 模块中,我们通过调用static_url的方式到静态目录static中访问文件。让我们看看index.html是怎么调用static_url的。
为什么要在模板中用static_url去取代硬编码访问静态文件呢?
这里有两个理由:
- static_url函数会基于静态目录的文件内容创建一个hash,并将文件的内容以v 参数的形式添加到查询字符串中。这个hash能够保证浏览器载入的永远都是最新的版本而不是之前缓存的版本,这样无论对于开发还是部署到生产环境都非常有用,因为用户不需要清理浏览器的缓存就可以看到你修改的静态化内容
- 当你调整你的应用程序目录的时候,不需要去改动你模板中的代码,例如,你可以配置tornado使用/s作为新的静态目录,你就可以简单的通过修改/static 成/s的形式,static_url将会自动将模板中的url更新,假如你使用的是硬编码的方式将静态文件写到你的源文件中,你就必须要手动去修改每一个模板
下一步:模板
现在你应该已经掌握了tornado中 template系统的基本功能,对于大部分简单的web应用来说,比如Alpha Munger这已经够用了,但是我们还没有将template的内容学习完,在template中组建块和模块还有好几个非常巧妙的应用,块和模块这两个特性将有效地帮助我们编写和维护更复杂的web应用,让我们在第三章了解这些特性吧。
原创翻译,首发地址:http://blog.xihuan.de/tech/web/tornado/tornado_form_and_templates.html
上一篇:翻译:introduce to tornado - a simple example
下一篇:翻译:introduce to tornado - Extending Templates
来源:oschina
链接:https://my.oschina.net/u/941832/blog/113496