模板和 UI¶
Tornado 包含一个简单、快速且灵活的模板语言。本节介绍该语言以及国际化等相关问题。
Tornado 也可以与任何其他 Python 模板语言一起使用,尽管没有规定将这些系统集成到 RequestHandler.render 中。只需将模板渲染为字符串并将其传递给 RequestHandler.write
配置模板¶
默认情况下,Tornado 在与引用它们的 .py 文件相同的目录中查找模板文件。要将模板文件放在不同的目录中,请使用 template_path Application setting(或者如果对不同的处理程序有不同的模板路径,则覆盖 RequestHandler.get_template_path)。
要从非文件系统位置加载模板,请子类化 tornado.template.BaseLoader 并将实例作为 template_loader 应用程序设置传递。
编译后的模板默认情况下会被缓存;要关闭此缓存并重新加载模板以便始终可以看到对底层文件的更改,请使用应用程序设置 compiled_template_cache=False 或 debug=True。
模板语法¶
Tornado 模板只是 HTML(或任何其他基于文本的格式),在标记中嵌入了 Python 控制序列和表达式
<html>
   <head>
      <title>{{ title }}</title>
   </head>
   <body>
     <ul>
       {% for item in items %}
         <li>{{ escape(item) }}</li>
       {% end %}
     </ul>
   </body>
 </html>
如果您将此模板保存为“template.html”并将其放在与您的 Python 文件相同的目录中,您就可以使用以下方法渲染此模板:
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 语句完全对应。我们支持 if、for、while 和 try,所有这些都以 {% end %} 结束。我们还支持使用 extends 和 block 语句的 *模板继承*,这些语句将在 tornado.template 文档中详细介绍。
表达式可以是任何 Python 表达式,包括函数调用。模板代码在包含以下对象和函数的命名空间中执行。(请注意,此列表适用于使用 RequestHandler.render 和 render_string 渲染的模板。如果您直接在 RequestHandler 之外使用 tornado.template 模块,则其中许多条目都不存在)。
- 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:Python- datetime模块
- 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 部分(一些功能,包括 UIModules 在 tornado.web 模块中实现)
在幕后,Tornado 模板直接转换为 Python。您在模板中包含的表达式将逐字复制到表示您模板的 Python 函数中。我们不会尝试阻止模板语言中的任何内容;我们明确创建它是为了提供其他更严格的模板系统所阻止的灵活性。因此,如果您在模板表达式中写入随机内容,当您执行模板时,您将获得随机的 Python 错误。
默认情况下,所有模板输出都会被转义,使用 tornado.escape.xhtml_escape 函数。此行为可以通过将 autoescape=None 传递给 Application 或 tornado.template.Loader 构造函数、对于具有 {% autoescape None %} 指令的模板文件或对于单个表达式(通过用 {% raw ...%} 替换 {{ ... }})来全局更改。此外,在这些地方中的每一个地方,可以使用替代转义函数的名称来代替 None。
请注意,虽然 Tornado 的自动转义功能有助于避免 XSS 漏洞,但它在所有情况下都不够。出现在某些位置的表达式,例如 JavaScript 或 CSS 中的表达式,可能需要额外的转义。此外,要么必须注意始终在可能包含不受信任内容的 HTML 属性中使用双引号和 xhtml_escape,要么必须为属性使用单独的转义函数(例如,请参阅 这篇博文)。
国际化¶
当前用户的区域设置(无论他们是否登录)始终在请求处理程序中作为 self.locale 和模板中作为 locale 可用。区域设置的名称(例如,en_US)在 locale.name 中可用,可以使用 Locale.translate 方法翻译字符串。模板也具有全局函数调用 _() 可用于字符串翻译。translate 函数有两种形式
_("Translate this string")
它根据当前区域设置直接翻译字符串,以及
_("A person liked this", "%(num)d people liked this",
  len(people)) % {"num": len(people)}
它翻译一个可以根据第三个参数的值是单数还是复数的字符串。在上面的示例中,如果 len(people) 为 1,则返回第一个字符串的翻译,否则返回第二个字符串的翻译。
翻译最常见的模式是使用 Python 命名占位符来表示变量(如上面的示例中的 %(num)d),因为占位符可以在翻译时移动。
这是一个正确国际化的模板
<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 标头来检测用户的区域设置。如果我们找不到合适的 Accept-Language 值,我们会选择 en_US。如果您允许用户将他们的区域设置设置为首选项,可以通过覆盖 RequestHandler.get_user_locale 来覆盖此默认区域设置选择
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        user_id = self.get_signed_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 模块支持加载两种格式的翻译:gettext 及其相关工具使用的 .mo 格式和简单的 .csv 格式。应用程序通常会在启动时调用一次 tornado.locale.load_translations 或 tornado.locale.load_gettext_translations;有关受支持格式的更多详细信息,请参阅这些方法。
您可以使用 tornado.locale.get_supported_locales() 获取应用程序中支持的区域设置列表。用户的区域设置将被选择为与支持的区域设置最接近的匹配。例如,如果用户的区域设置是 es_GT,并且支持 es 区域设置,则对于该请求,self.locale 将为 es。如果找不到紧密的匹配,我们会回退到 en_US。
UI 模块¶
Tornado 支持 *UI 模块*,以便轻松支持应用程序中的标准、可重用的 UI 小部件。UI 模块类似于渲染页面组件的特殊函数调用,并且可以与自己的 CSS 和 JavaScript 捆绑在一起。
例如,如果您正在实现一个博客,并且您希望博客条目出现在博客主页和每个博客条目页面上,那么您可以创建一个 Entry 模块,以便在两个页面上渲染它们。首先,为您的 UI 模块创建一个 Python 模块,例如 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)
使用应用程序中的 ui_modules 设置告诉 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 中调用 Entry 模块
{% for entry in entries %}
  {% module Entry(entry) %}
{% end %}
以及 entry.html
{% module Entry(entry, show_comments=True) %}
模块可以通过覆盖 embedded_css、embedded_javascript、javascript_files 或 css_files 方法来包含自定义 CSS 和 JavaScript 函数
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 ... %} 指令不同,模板模块与其包含的模板具有不同的命名空间 - 它们只能看到全局模板命名空间和它们自己的关键字参数。