Tornado入门(六)【模板和UI】
Tornado提供了一个简单,快速,灵活的模板引擎。
Tornado也可以使用其他任意的模板引擎, 尽管并没有明确规则如何在RequestHandler.render
整合进这些引擎。实际上只需要将模板渲染成字符串,然后传递给RequestHadler.write
方法即可。
配置模板
默认情况下,Tornado会在跟Python文件相同的目录下查找模板文件。如果需要将模板文件放在单独的路径,可以通过Application setting
中的template_path
进行配置,如果是要求不同处理器的模板不一样,则可以重写RequestHandler.get_tempplate_path
方法。
如果需要从非文件系统中加载模板,则可以继承tornado.template.BaseLoader
然后传递它的一个实例给application
配置的template_loader
参数。
默认会缓存编译之后的模板,如果需要取消缓存,则需要设置参数compiled_template_cached=False
和debug=True
。
模板语法
Tornado模板语言实际上就是嵌套了Python流程控制语句和表达式的HTML文本。
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ escape(item) }}</li>
{% end %}
</ul>
</body>
</html>
如果将上面的模板存储为template.html
,并与py
文件存放在同一目录,渲染过程如下:
class MainHandler(tornado.web.RequestHandler):
def get(self):
items = ["Item 1", "Item 2", "Item 3"]
self.render("template.html", title="My title", items=items)
Tornado模板支持流程控制语句和表达式,流程控制语句被{%
和%}
括起来,例如:{% if len(items) > 2 %}
。表达式被{{
和}}
括起来,例如:{{ items[0] }}
。
流程控制语法与Python类似,Tornado现在支持if
, for
, while
和try
,这些语句都以{% end %}
结尾。Tornado同样支持模板继承extends
和块block
语句。具体参考tornado.template
。
模板中的表达式可以为任意的Python表达式,包括函数调用。模板中的代码在一个命名空间中执行,这个命名空间包括了如下对象和函数。
-
escape
:tornado.escape.xhtml_escape
的别名 -
xhtml_escape
:tornado.escape.xhtml_escape
的 别名 -
url_escape
:tornado.escape.url_escape
的别名 -
json_encode
:tornado.escape.json_encode
的别名 -
squeeze
:tornado.escape.squeeze
的别名 -
linkify
:tornado.escape.linkify
的别名 -
datetime
: Pythondatetime
模块 -
handler
: 当前RequestHandler
对象 -
request
:handler.request
的别名 -
current_user
:handler.current_user
的别名 -
locale
:handler.locale
的别名 -
_
:handler.locale.translate
的别名 -
static_url
:handler.static_url
的别名 -
xsrf_form_html
:handler.xsrf_form_html
的别名 -
reverse_url
:Application.reverse_url
的别名 -
ui_methods
和ui_modules
Application
配置的所有入口 - 传递给
render
或者render_string
的任意参数
当我们创建系统应用时,需要利用到Tornado的很多特性,具体可以参考tornado.template
的文档。
Tornado模板会被编译为Python代码,所有的模板输出默认都会使用tornado.escape.xhtml_escape
转义,可以在应用的设置中通过参数autoescape=False
来关闭转义,或者在tornado.template.Loader
构造器中设置这个参数。如果是在模板文件中,可以使用{% autoescape None %}
指令。如果是表达式,则可以使用{% raw ...%}
。
Tornado提供的自动转义可以避免XSS
攻击,但是它并不能处理所有情况,例如Javascript和CSS中的表达式可能需要格外的转义。
国际化
当前用户的本地化信息保存在处理器的self.locale
变量中,在模板中可以通过locale
访问到。本地化的名称保存在locale.name
中,例如en_US
。可以使用Locale.translate
方法翻译字符串。模板中也可以使用全局函数_()
。
翻译函数有两种形式:
_("Translate this string")
基于当前的locale对象翻译字符串。
_("A person liked this", "%(num)d people liked this",
len(people)) % {"num": len(people)}
提供第三个参数,将字符串翻译成单数形式或者复数形式。
翻译过程中最为常用的还是Python的格式化字符串。
下面是一个模板示例:
<html>
<head>
<title>FriendFeed - {{ _("Sign in") }}</title>
</head>
<body>
<form action="{{ request.path }}" method="post">
<div>{{ _("Username") }} <input type="text" name="username"/></div>
<div>{{ _("Password") }} <input type="password" name="password"/></div>
<div><input type="submit" value="{{ _("Sign in") }}"/></div>
{% module xsrf_form_html() %}
</form>
</body>
</html>
默认情况下,通过检查请求体的头部字段Accept-Language
来获取用户的语言信息,如果没有找到合适的,则使用en_US
。如果想优先使用用户定义的语言信息,可以重写RequestHandler.get_user_locale
方法:
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_id = self.get_secure_cookie("user")
if not user_id: return None
return self.backend.get_user_by_id(user_id)
def get_user_locale(self):
if "locale" not in self.current_user.prefs:
# Use the Accept-Language header
return None
return self.current_user.prefs["locale"]
如果get_user_locale
返回None
,则使用Accept-Language
头部的值。
tornado.locale
模块支持以下两种格式的翻译文件:
- .mo格式文件
- .csv格式文件
在启动应用的时候,分别通过tornado.locale.load_gettext_translations
和tornado.locale.load_translations
进行加载。
通过tornado.locale.get_supported_locales()
可以获取所有支持的语言,tornado会基于用户的设置选择最匹配的语言。
UI模块
Tornado支持UI模块,以便支持标准的,可重用的UI组件。UI模块是特殊的函数,用于渲染页面的组件,而且它们可包含自己的CSS
和Javascript
文件。
例如,如果你想正在实现一个博客系统,你希望博客入口同时出现在博客主页和每篇博客的页面,这时可以创建一个Entry
模块,然后在每个页面都进行渲染,首先创建UI模块uimodules.py
:
class Entry(tornado.web.UIModule):
def render(self, entry, show_comments=False):
return self.render_string(
"module-entry.html", entry=entry, show_comments=show_comments)
需要在应用配置中告诉Tornado使用uimodules.py
。
from . import uimodules
class HomeHandler(tornado.web.RequestHandler):
def get(self):
entries = self.db.query("SELECT * FROM entries ORDER BY date DESC")
self.render("home.html", entries=entries)
class EntryHandler(tornado.web.RequestHandler):
def get(self, entry_id):
entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id)
if not entry: raise tornado.web.HTTPError(404)
self.render("entry.html", entry=entry)
settings = {
"ui_modules": uimodules,
}
application = tornado.web.Application([
(r"/", HomeHandler),
(r"/entry/([0-9]+)", EntryHandler),
], **settings)
接下来就可以在模板中使用这个模块了,通过{% module %}
语句来声明。home.html
中:
{% for entry in entries %}
{% module Entry(entry) %}
{% end %}
entry.html
中:
{% module Entry(entry, show_comments=True) %}
模块中还可以包含CSS
和Javascript
信息,重写embedded_css
,embedded_javascript
,javascript_files
或者css_files
即可。
class Entry(tornado.web.UIModule):
def embedded_css(self):
return ".entry { margin-bottom: 1em; }"
def render(self, entry, show_comments=False):
return self.render_string(
"module-entry.html", show_comments=show_comments)
模块中的CSS
和Javascript
只会加载一次,CSS
包含在<head>
中,Javascript
位于在</body>
前面。
当不需要格外的Python代码时,模板文件本身也可以作为一个模块。例如,前面的例子可以重写为module.entry
.html
{{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }}
<!-- more template html... -->
修正之后的模块调用如下:
{% module Template("module-entry.html", show_comments=True) %}
set_resources
函数只能通过{% module Template(...) %}
来调用。跟{% include %}
不一样,模板模块拥有一个独立的命名空间。