Templates and UI

龙卷风包含一种简单,快速且灵活的模板语言. 本节介绍了该语言以及相关的问题,例如国际化.

Tornado也可以与任何其他Python模板语言一起使用,尽管没有规定将这些系统集成到RequestHandler.render . 只需将模板呈现为字符串并将其传递给RequestHandler.write

Configuring templates

默认情况下,Tornado在与引用它们的.py文件相同的目录中查找模板文件. 要将模板文件放在不同的目录中,请使用template_path Application setting (如果您为不同的处理程序使用了不同的模板路径,请覆盖RequestHandler.get_template_path ).

要从非文件系统位置加载模板,请子类tornado.template.BaseLoader并将实例作为template_loader应用程序设置传递.

默认情况下,已编译的模板被缓存; 要关闭此缓存并重新加载模板,以使对基础文件的更改始终可见,请使用应用程序设置compiled_template_cache=Falsedebug=True .

Template syntax

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 templates support 控制声明 and expressions. Control statements are surrounded by {% and %}, e.g. {% if len(items) > 2 %}. Expressions are surrounded by {{ and }}, e.g. {{ items[0] }}.

控制语句或多或少准确地映射到Python语句. 我们支持ifforwhile ,和try ,所有这一切都终止了与{% end %} . 我们还支持使用extendsblock语句来支持模板继承 ,这些声明在tornado.template的文档中进行了详细描述.

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

在构建真实的应用程序时,您将要使用Tornado模板的所有功能,尤其是模板继承. 在tornado.template部分中阅读所有有关这些功能的tornado.template (某些功能,包括UIModules ,在tornado.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 ,或者必须对属性使用单独的转义功能(请参见此博客文章 ).

Internationalization

当前用户的语言环境(不论是会员或)总是可以作为self.locale在请求处理程序和locale的模板. 语言环境的名称(例如en_US )可以作为locale.name ,并且您可以使用Locale.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"]

If get_user_locale returns None, we fall back on the Accept-Language header.

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 modules

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 %}

and 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... -->

This revised template module would be invoked with:

{% module Template("module-entry.html", show_comments=True) %}

set_resources函数仅在通过{% module Template(...) %}调用的模板中可用. 与{% include ... %}指令不同,模板模块具有与其包含的模板不同的名称空间-它们只能看到全局模板名称空间和它们自己的关键字参数.