模板和用户界面

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

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

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

当您构建一个真正的应用程序时,您将希望使用Tornado模板的所有特性,尤其是模板继承。阅读中有关这些功能的所有信息 tornado.template 部分(一些功能,包括 UIModulestornado.web 模块)

在引擎盖下,Tornado模板直接转换为python。模板中包含的表达式将逐字复制到表示模板的python函数中。我们不会试图阻止模板语言中的任何东西;我们显式地创建它是为了提供其他更严格的模板系统所阻止的灵活性。因此,如果您在模板表达式中编写随机的内容,那么在执行模板时会出现随机的python错误。

默认情况下,所有模板输出都使用 tornado.escape.xhtml_escape 功能。此行为可以通过传递全局更改 autoescape=NoneApplicationtornado.template.Loader 构造函数,用于模板文件 {{% autoescape None %}} 指令,或替换为单个表达式 {{{{ ... }}}} 具有 {{% raw ...%}} . 此外,在这些地方,可以使用另一个转义函数的名称,而不是 None .

请注意,虽然Tornado的自动转义有助于避免XSS漏洞,但它在所有情况下都不够。出现在某些位置(如JavaScript或CSS)中的表达式可能需要额外的转义。此外,必须注意始终使用双引号和 xhtml_escape in HTML attributes that may contain untrusted content, or a separate escaping function must be used for attributes (see e.g. this blog post

国际化

当前用户的区域设置(无论他们是否登录)始终可用为 self.locale 在请求处理程序和as中 locale 在模板中。区域设置的名称(例如, en_US )可作为 locale.name ,您可以使用 Locale.translate method. Templates also have the global function call ``_ ()``可用于字符串转换。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 用户浏览器发送的标题。我们选择 en_US 如果我们找不到合适的 Accept-Language 价值。如果允许用户将其区域设置为首选项,则可以通过重写来重写此默认区域设置选择。 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 使用的格式 gettext 和相关的工具,以及一个简单的 .csv 格式。应用程序通常会调用 tornado.locale.load_translationstornado.locale.load_gettext_translations 一旦启动,请参阅这些方法以获取有关支持格式的更多详细信息。

您可以使用 tornado.locale.get_supported_locales() . 根据支持的区域设置,用户的区域设置被选择为最接近的匹配。例如,如果用户的区域设置为 es_GTes 支持区域设置, self.localees 为了那个请求。我们退缩 en_US 如果找不到匹配项。

用户界面模块

龙卷风支架 用户界面模块 为了便于在应用程序中支持标准的、可重用的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)

告诉龙卷风使用 uimodules.py 使用 ui_modules 应用程序中的设置:

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 %}} 语句。例如,您可以调用 Entry 来自两个模块 home.html ::

{% for entry in entries %}
  {% module Entry(entry) %}
{% end %}

entry.html ::

{% module Entry(entry, show_comments=True) %}

模块可以通过重写 embedded_cssembedded_javascriptjavascript_filescss_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 ... %}} 指令模板模块与其包含的模板具有不同的命名空间-它们只能看到全局模板命名空间及其自己的关键字参数。