Python Tornado初学笔记之表单与模板(一)
Tornado中的表单和HTML5中的表单具有相同的用途,同样是用于内容的填写。只是不同的是Tornado中的表单需要传入到后台,然后通过后台进行对模板填充。
模板:是一个允许嵌入Python代码片段的HTML文件。
一、简单模板示例:
Python主程序:
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('roads') noun2 = self.get_argument('wood') verb = self.get_argument('made') noun3 = self.get_argument('difference') self.render('poem.html',roads=roads,wood=wood,made=made,difference=difference) 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()
template_path为页面文件存放位置,之后还有static文件夹,在static文件夹中存放静态文件如css文件、图片等。此处的template_path是告诉服务器“我的index.html和poem.html文件就放在了当前程序所在文件夹中的template文件夹中,不要去其他地方找了。”,其实在此处调用了Application的__init__函数。当在Application函数中添加一个debug=True参数时,它调用了一个便利的测试模式:tornado.autoreload模块,此时,一旦主要的Python文件被修改,Tornado将会尝试重启服务器,并且在模板改变时会进行刷新。对于快速改变和实时更新这非常棒,但不要再生产上使用它,因为它将防止Tornado缓存模板!
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="roads"></p> <p>Singular noun<br><input type="text" name="wood"></p> <p>Verb (past tense)<br><input type="text" name="made"></p> <p>Noun<br><input type="text" name="difference"></p> <input type="submit"> </form> </body> </html>
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>
注:此处的模板和Flask中的模板是很相似的。
首先先看一段简单的模板代码:
<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>
在代码中{{ }}包裹的部分为占位符,它是渲染模板时的实际值(即传入的值)。
其实在程序中最关键的部分在于主程序中的get_argument()函数,该函数可以从表单中获取用户输入的值,然后通过render函数将用户输入的值传递到模板中,然后在模板中渲染显示。
noun1 = self.get_argument('roads') noun2 = self.get_argument('wood') verb = self.get_argument('made') noun3 = self.get_argument('difference') self.render('poem.html', roads=roads, wood=wood, made=made, difference=difference)
假如用户在表单中输入的roads、wood、made、defference分别为pineapples、grandfather clock、irradiated和supernovae。那么当用户点击提交后,看到的内容便为:
Two pineapples diverged in a grandfather clock, and I— I took the one less travelled by, And that has irradiated all the supernovae.
二、填充表达式和控制流语句
在模板中使用表达式,需要将Python表达式放入{{ }}中,Tornado将插入一个包含任何表达式计算结果值的字符串到输出中。
例如:
>>> from tornado.template import Template >>> print(Template('{{ 1+2 }}').generate()) b'3' >>> print(Template("{{ 'scrambled eggs'[-4:] }}").generate()) b'eggs' >>> print(Template("{{ ','.join([str(x*x) for x in range(10)]) }}").generate()) b'0,1,4,9,16,25,36,49,64,81'
在使用python的控制流语句时,需要将控制流语句放入{% %}中,以{% end %}结尾,它同样支持Python的if、while、for、try。Tornado模板语言的一个最好的东西是在if和for语句块中可以使用的表达式没有限制,同时你也可以在你的控制语句块中间使用{% set foo='bar' %}来设置变量。
例如:
{% if a is None %}xxx{% end %}
当然在Tornado在所有模板中默认提供了一些便利的函数:
escape(s):替换字符串s中的&、为他们对应的HTML字符。
url_escape(s):使用urllib.quote_plus替换字符串s中的字符为URL编码形式。
json_encode(s):将val编码成JSON格式。(在系统底层,这是一个对json库的dumps函数的调用。)
squeeze(s):过滤字符串s,把连续的多个空白字符替换成一个空格。
注:在Tornado 2.0中,模板被默认为自动转义(并且可以在Application构造函数中使用autoscaping=None关闭)
三、模板扩展
Tornado中的块替换可以实现前端代码的重用,Tornado通过extends和block语句支持模板继承,这就让你拥有了编写能够在合适的地方复用的流体模板的控制权和灵活性。
在使用{% extends “xxx.html”%}时,实际上是从xxx.html文件中继承了xxx.html的内容,当然前提是,xxx.html文件中必须定义了相关的块内容。此语句需要在新的模板文件的头部进行提前声明。
{% block xxx %}语句压缩了一些当你扩展时可能想要改变的模板元素。在新的模板文件中,直接使用该语句,可以实现对该块内容的覆盖,并将自己需要显示的内容放入相对应的模板块中进行显示。
例如:
父模板(部分):
<header>{% block header %}{% end %}</header>
子模版(部分):
<header> {% block header %} <p>Hello World!</p> {% end %} </header>
在父模板中可以使用多个块,然后在子模版中直接进行使用就可以,节省了网站开发的时间。此处给出一个简单的header、body、footer块。
<html> <body> <header> {% block header %}{% end %} </header> <content> {% block body %}{% end %} </content> <footer> {% block footer %}{% end %} </footer> </body> </html>
注:此处容易忘记{% end %},一定要记得添加该结束语句,否则会ParseError的
当用户自定义一个Application函数时,在我们定义的__init__方法中,需要创建网站的处理类列表以及一个设置的字典,然后在初始化子类中调用中传递这些值,
tornado.web.Application.__init__(self,handlers,**settings)),说明:handlers中主要写访问页面时需要调用哪个函数,settings中主要为静态文件、网页文件路径以及其他调试的参数。当然在运行时就需要更改应用方式了。
用户自定的Application函数:
class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", xxxHandler),#xxxHandler根据自己实际的类名称进行填写 ] settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), debug=True, ) tornado.web.Application.__init__(self, handlers, **settings)
原:
http_server = tornado.web.Application(app) #app中为网站的配置
改:
http_server = tornado.web.Application(Application())#Application中为我们自定义个网站配置,此处将网站的配置写入了一个函数中。
注:此处不给出一个简单页面的代码是因为,在此处,每个人都可以按照自己的想法仿照前面的例子进行写个简单的代码,然后将上文的内容进行使用,这种方法我个人觉得是一个很好的锻炼方式。
前文说过,在tornado中会自动转义模板中的内容,把标签转换为相应的HTML实体。这样可以防止后端为数据库的网站被恶意脚本攻击。
例如设置一个邮箱链接:
{% set mailLink = "<a href="mailto:contact@burtsbooks.com">Contact Us</a>" %} {{ mailLink }}'
在tornado中会将该内容在页面源码中显示为:
<a href="mailto:contact@burtsbooks.com">Contact Us</a>
此时自动转义被运行了,很明显,这无法让人们联系该用户。
为了处理这种情况,你可以禁用自动转义,一种方法是在Application构造函数中传递autoescape=None,另一种方法是在每页的基础上修改自动转义行为,如下所示:
{% autoescape None %}
{{ mailLink }}
注:这些autoescape块不需要结束标签,并且可以设置xhtml_escape来开启自动转义(默认行为),或None来关闭
tornado.escape.linkify():
tornado.escape.linkify(text, shorten=False, extra_params='', require_protocol=False, permitted_protocols=['http', 'https'])[source] Converts plain text into HTML with links. For example: linkify("Hello http://tornadoweb.org!") would returnHello <a href="http://tornadoweb.org">http://tornadoweb.org</a>! Parameters: shorten: Long urls will be shortened for display. extra_params: Extra text to include in the link tag, or a callable taking the link as an argument and returning the extra text e.g. linkify(text, extra_params='rel="nofollow" class="external"'), or: def extra_params_cb(url): if url.startswith("http://example.com"): return 'class="internal"' else: return 'class="external" rel="nofollow"' linkify(text, extra_params=extra_params_cb) require_protocol: Only linkify urls which include a protocol. If this is False, urls such as www.facebook.com will also be linkified. permitted_protocols: List (or set) of protocols which should be linkified, e.g. linkify(text, permitted_protocols=["http", "ftp", "mailto"]). It is very unsafe to include protocols such as javascript.
四、Tornado中的UI Module
UI模块是封装模板中包含的标记、样式以及行为的可复用组件。它所定义的元素通常用于多个模板交叉复用或在同一个模板中重复使用。模块本身是一个继承自Tornado的UIModule类的简单Python类,并定义了一个render方法。当一个模板使用{% module Foo(...) %}标签引用一个模块时,Tornado的模板引擎调用模块的render方法,然后返回一个字符串来替换模板中的模块标签。UI模块也可以在渲染后的页面中嵌入自己的JavaScript和CSS文件,或指定额外包含的JavaScript或CSS文件。你可以定义可选的embedded_javascript、embedded_css、javascript_files和css_files方法来实现这一方法。
在模板中引入模块时,需要在应用的设置中进行声明,ui_moudles参数期望一个模块名为键、类为值的字典输入来渲染它们。
简单例子:
import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options import os.path from tornado.options import define, options define("port", default=8000, help="run on the given port", type=int) class HelloHandler(tornado.web.RequestHandler): def get(self): self.render('hello.html') class HelloModule(tornado.web.UIModule): def render(self): return '<h1>Hello, world!</h1>' if __name__ == '__main__': tornado.options.parse_command_line() app = tornado.web.Application( handlers=[(r'/', HelloHandler)], template_path=os.path.join(os.path.dirname(__file__), 'templates'), ui_modules={'Hello': HelloModule} ) server = tornado.httpserver.HTTPServer(app) server.listen(options.port) tornado.ioloop.IOLoop.instance().start()
当用户访问时,程序首先会先调用HelloHandler类,然后进行跳转至hello.html页面,然后再hello.html页面使用{% module Hello() %}进行显示需要显示的内容。当使用这个module时,会调用ui_modules中的Hello对应的类,然后进行内容反馈。
hello.html文件内容:
<html> <head><title>UI Module Example</title></head> <body> {% module Hello() %} </body> </html>
这个hello.html模板通过在模块标签自身的位置调用HelloModule返回的字符串进行填充
如果要用更多的模块,只需要简单地在ui_modules参数中添加映射值。因为模板可以指向任何定义在ui_modules字典中的模块。
五、嵌入JavaScript和CSS
为了给这些模块提供更高的灵活性,Tornado允许你使用embedded_css和embedded_javascript方法嵌入其他的CSS和JavaScript文件。
添加本地CSS文件:
def css_files(self): return "/static/css/newreleases.css"
添加外部的JavaScript文件:
def javascript_files(self): return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"
当一个模块需要额外的库而应用的其他地方不是必需的时候,这种方式非常有用。比如,你有一个使用JQuery UI库的模块(而在应用的其他地方都不会被使用),你可以只在这个样本模块中加载jquery-ui.min.js文件,减少那些不需要它的页面的加载时间。
因为模块的内嵌JavaScript和内嵌HTML函数的目标都是紧邻标签,html_body()、javascript_files()和embedded_javascript()都会将内容渲染后插到页面底部,那么它们出现的顺序正好是你指定它们的顺序的倒序。
如果你有一个模块如下面的代码所示:
class SampleModule(tornado.web.UIModule): def render(self, sample): return self.render_string( "modules/sample.html", sample=sample ) def html_body(self): return "<div class=\"addition\"><p>html_body()</p></div>" def embedded_javascript(self): return "document.write(\"<p>embedded_javascript()</p>\")" def embedded_css(self): return ".addition {color: #A1CAF1}" def css_files(self): return "/static/css/sample.css" def javascript_files(self): return "/static/js/sample.js"
加载方式为:html_body()最先被编写,它紧挨着出现在标签的上面。embedded_javascript()接着被渲染,最后是javascript_files()