Tornado 0106 - 用户指南: 模板与 UI


模板和UI

Tornado 包含一种简单,快速,灵活的模板语言。本节介绍该语言以及国际化等相关问题。

Tornado 也可以与任何其他 Python 模板语言一起使用,尽管没有明确地规定如何将这些系统集成到 RequestHandler.render 中,你只需将模板渲染为字符串并将其传递给RequestHandler.write 就可以了。

配置模板

默认情况下,Tornado 会在与引用它们的 .py 文件相同的目录中查找模板文件。 要将模板文件放在不同的目录中,请使用 template_path 应用程序设置 (如果不同的处理程序具有不同的模板路径,则覆盖 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: alias for tornado.escape.xhtml_escape
  • xhtml_escape: alias for tornado.escape.xhtml_escape
  • url_escape: alias for tornado.escape.url_escape
  • json_encode: alias for tornado.escape.json_encode
  • squeeze: alias for tornado.escape.squeeze
  • linkify: alias for tornado.escape.linkify
  • datetime: the Python datetime module
  • handler: the current RequestHandler object
  • request: alias for handler.request
  • current_user: alias for handler.current_user
  • locale: alias for handler.locale
  • _: alias for handler.locale.translate
  • static_url: alias for handler.static_url
  • xsrf_form_html: alias for handler.xsrf_form_html
  • reverse_url: alias for Application.reverse_url
  • All entries from the ui_methods and ui_modules Application settings
  • Any keyword arguments passed to render or 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,或者必须为属性使用单独的转义函数(请参阅 http://wonko.com/post/html-escaping

国际化

当前用户的区域设置(无论它们是否已登录)始终在请求处理程序中作为 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_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 模块支持以两种格式加载翻译:gettext 和相关工具使用的 .mo 格式,以及简单的 .csv 格式。 应用程序通常会在启动时调用 tornado.locale.load_translationstornado.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 始终包含在页面的 中,并且 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 … %} 指令不同,模板模块与其包含模板具有不同的命名空间 - 它们只能看到全局模板命名空间和它们自己的关键字参数。