Skip to main content

模板和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=Falsedebug=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模板支持 控制语句expressions。控制语句由 {%%} (例如,{% if len(items) > 2 %})包围。表达被 {{}} (例如 {{ items[0] }})包围。

控制语句或多或少地映射到Python语句。我们支持 ifforwhiletry,所有这些都以 {% end %} 终止。我们还支持使用 extendsblock 语句的 模板继承,这些语句在 tornado.template 的文档中有详细描述。

表达式可以是任何Python表达式,包括函数调用。模板代码在包含以下对象和函数的命名空间中执行(请注意,此列表适用于使用 RequestHandler.renderrender_string 呈现的模板。如果您直接在 RequestHandler 外部使用 tornado.template 模块,则不存在这些条目)。

当你构建一个真正的应用程序时,你将要使用Tornado模板的所有功能,特别是模板继承。在 tornado.template 部分阅读所有关于这些功能(一些功能,包括 UIModulestornado.web 模块中实现)

在内部,Tornado模板直接转换为Python。您在模板中包含的表达式将逐字地复制到表示模板的Python函数中。我们不试图阻止模板语言中的任何东西;我们明确地创建它提供其他,更严格的模板系统防止的灵活性。因此,如果您在模板表达式中写入随机内容,则在执行模板时会出现随机的Python错误。

默认情况下,使用 tornado.escape.xhtml_escape 函数转义所有模板输出。通过将 autoescape=None 传递给 Applicationtornado.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)

告诉Tornado在您的应用程序中使用 ui_modules 设置来使用 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_cssembedded_javascriptjavascript_filescss_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 ... %} 指令不同,模板模块与其包含模板具有不同的命名空间 - 它们只能查看全局模板命名空间和它们自己的关键字参数。