模版和 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 控制语句和内建的表达式:

  1. <html>
  2. <head>
  3. <title>{{ title }}</title>
  4. </head>
  5. <body>
  6. <ul>
  7. {% for item in items %}
  8. <li>{{ escape(item) }}</li>
  9. {% end %}
  10. </ul>
  11. </body>
  12. </html>

如果你将这个模版文件保存为 “template.html” 然后将你的 Python 文件保存在同一目录,你可以用这种方式来使用模版:

  1. class MainHandler(tornado.web.RequestHandler):
  2. def get(self):
  3. items = ["Item 1", "Item 2", "Item 3"]
  4. self.render("template.html", title="My title", items=items)

Tornado 模版支持 控制语句 (control statements)表达式 (expressions) .控制语句被 {% and %} 包裹着, 例如.,{% if len(items) > 2 %}. 表达式被 {{}} 围绕, 再例如., {{ items[0] }}.

模版中的控制语句多多少少与 Python 中的控制语句相映射. 我们支持if, for, while, 和 try, 所有这些都包含在{% %} 之中. 我们也支持 模板继承使用 extendsblock 语句, 详见 tornado.template.

表达式可以时任何的 Python 表达式, 包括函数调用.模版代码可以在以下对象和函数的命名空间中被执行.(注意这个列表可用在 RequestHandler.renderrender_string. 如果你直接在 RequestHandler 外使用tornado.template 模块, 下面许多别名是不可用的).

在引擎下, Tornado 模版被直街翻译成 Python.在你模版文件中的表达式将会被翻译成 Python 函数来代表原来的模版;我们不在模版语言中阻止任何东西; 我们创造它的目的时为了提供更灵活的特性,而不是有严格限制的模版系统.因此, 如果你在你的模版文件中随意写入了表达式, 你再执行时将会得到相依随机的错误.

默认情况下, 所有模版文件的输出将会被 tornado.escape.xhtml_escape 方法转义.这个设置可以通过给 Application 传递全局参数 autoescape=None 或者使用tornado.template.Loader 构造器进行修改, 或者在模版文件中检测到{% autoescape None %} , 或者简单的将 {{ … }} 替换成 {% raw …%} 的表达式.此外, 可以在设置这些地方的转义函数为 None 已达到相同的效果.

注意, 尽管 Tornado’s 的自动转义在防止XSS 漏洞上是有帮助的, 但是不能适用于所有的情况. 出现在适当位置的表达式,例如 Javascript 或者 CSS, 可能需要额外的转义.此外, 必须要额外注意使用在 HTML 中使用双括号和 xhtml_escape 中包含一些不可信的内容,或者在属性中使用单独的转义函数 (查看示例. http://wonko.com/post/html-escaping)

国际化¶

目前用户的位置 (不论用户是否登陆) 在请求处理程序中的 self.locale 和模版中的 locale 都是可用的. 位置的名字 (例如, enUS) 在 locale.name 中是可用的,你也可以通过 Locale.translate 方法来翻译字符串.模版中也有一个全局函数叫做 () 用来翻译字符串.翻译函数有两种形式:

  1. _("翻译这段文字")

这将会根据用户的位置直接翻译, 还有:

  1. _("A person liked this", "%(num)d people liked this",
  2. len(people)) % {"num": len(people)}

可以根据第三个参数的数量来决定单复数形式. 在以上的例子中,第一个翻译将会在 len(people)1 时被激活,在其它情况下会激活第二个翻译.

大多是翻译时利用 Python 中的变量占位符 ( 前面例子中的 %(num)d )占位符在翻译时可以被替换.

这是一个正确的国际化模版:

  1. <html>
  2. <head>
  3. <title>FriendFeed - {{ _("Sign in") }}</title>
  4. </head>
  5. <body>
  6. <form action="{{ request.path }}" method="post">
  7. <div>{{ _("Username") }} <input type="text" name="username"/></div>
  8. <div>{{ _("Password") }} <input type="password" name="password"/></div>
  9. <div><input type="submit" value="{{ _("Sign in") }}"/></div>
  10. {% module xsrf_form_html() %}
  11. </form>
  12. </body>
  13. </html>

默认情况下, 我们通过用户通过浏览器发送的首部 Accept-Language 来确定语言.当我们不能找到默认的语言时我们使用 en_US 作为 Accept-Language 的值.如果你希望用户自己设定自己的位置, 你可以通过修改默认选项 RequestHandler.get_user_locale来实现:

  1. class BaseHandler(tornado.web.RequestHandler):
  2. def get_current_user(self):
  3. user_id = self.get_secure_cookie("user")
  4. if not user_id: return None
  5. return self.backend.get_user_by_id(user_id)
  6.  
  7. def get_user_locale(self):
  8. if "locale" not in self.current_user.prefs:
  9. # Use the Accept-Language header
  10. return None
  11. return self.current_user.prefs["locale"]

如果 get_user_locale 返回 None, 我们将会再使用Accept-Language 头部来确定.

tornado.locale 模块支持两种格式的翻译:一种使用 getttext 和有关工具的 .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 模块来实现它们. 首先, 创建一个 Python 模块当作一个 UI 模块,例如 uimodules.py:

  1. class Entry(tornado.web.UIModule):
  2. def render(self, entry, show_comments=False):
  3. return self.render_string(
  4. "module-entry.html", entry=entry, show_comments=show_comments)

ui_modules 设置中告诉 Tornado 使用 uimodules.py

  1. from . import uimodules
  2.  
  3. class HomeHandler(tornado.web.RequestHandler):
  4. def get(self):
  5. entries = self.db.query("SELECT * FROM entries ORDER BY date DESC")
  6. self.render("home.html", entries=entries)
  7.  
  8. class EntryHandler(tornado.web.RequestHandler):
  9. def get(self, entry_id):
  10. entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id)
  11. if not entry: raise tornado.web.HTTPError(404)
  12. self.render("entry.html", entry=entry)
  13.  
  14. settings = {
  15. "ui_modules": uimodules,
  16. }
  17. application = tornado.web.Application([
  18. (r"/", HomeHandler),
  19. (r"/entry/([0-9]+)", EntryHandler),
  20. ], **settings)

在一个模版中, 你可以利用 {% module %} 语句来调用一个模版.例如, 你可以在 home.html 中调用 Entry 模块:

  1. {% for entry in entries %}
  2. {% module Entry(entry) %}
  3. {% end %}

还有 entry.html 中:

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

模块可以通过覆盖包含定制的 CSS 和 JavaScript 方法 embedded_css, embedded_javascript, javascript_files , 或者 css_files 方法:

  1. class Entry(tornado.web.UIModule):
  2. def embedded_css(self):
  3. return ".entry { margin-bottom: 1em; }"
  4.  
  5. def render(self, entry, show_comments=False):
  6. return self.render_string(
  7. "module-entry.html", show_comments=show_comments)

CSS 和 JavaScript 模块只会被载入一次不论多少模块在页面中使用了它.CSS 总是被包含在页面的 <head> 标签中,而且 JavaScript 也总是在页面底部的 </body> 之前.

当附加的 Python 代码不需要的时候, 模版文件自己可以是一个模块.例如, 上面的例子可以在下面的 module-entry.html 中被重写:

  1. {{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }}
  2. <!-- more template html... -->

这个被修改过的模块可以这样调用

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

set_resources 方法仅在模版通过 {% module Template(…) %} 调用有效.不像 {% include … %} 指令, 模版模块在模版容器中有一个不同的命名空间 -它们只能看到全局模版的命名空间和自己的关键字参数.

原文:

https://tornado-zh-cn.readthedocs.io/zh_CN/latest/guide/templates.html