Tornado入门(六)【模板和UI】

2018-08-27 14:18:51 浏览数 (1)

Tornado提供了一个简单,快速,灵活的模板引擎。

Tornado也可以使用其他任意的模板引擎, 尽管并没有明确规则如何在RequestHandler.render整合进这些引擎。实际上只需要将模板渲染成字符串,然后传递给RequestHadler.write方法即可。

配置模板

默认情况下,Tornado会在跟Python文件相同的目录下查找模板文件。如果需要将模板文件放在单独的路径,可以通过Application setting中的template_path 进行配置,如果是要求不同处理器的模板不一样,则可以重写RequestHandler.get_tempplate_path方法。

如果需要从非文件系统中加载模板,则可以继承tornado.template.BaseLoader然后传递它的一个实例给application配置的template_loader参数。

默认会缓存编译之后的模板,如果需要取消缓存,则需要设置参数compiled_template_cached=Falsedebug=True

模板语法

Tornado模板语言实际上就是嵌套了Python流程控制语句和表达式的HTML文本。

代码语言:javascript复制
<html>
   <head>
      <title>{{ title }}</title>
   </head>
   <body>
     <ul>
       {% for item in items %}
         <li>{{ escape(item) }}</li>
       {% end %}
     </ul>
   </body>
 </html>

如果将上面的模板存储为template.html,并与py文件存放在同一目录,渲染过程如下:

代码语言:javascript复制
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类似,Tornado现在支持iffor, whiletry,这些语句都以{% end %}结尾。Tornado同样支持模板继承extends和块block语句。具体参考tornado.template

模板中的表达式可以为任意的Python表达式,包括函数调用。模板中的代码在一个命名空间中执行,这个命名空间包括了如下对象和函数。

  • escape: tornado.escape.xhtml_escape的别名
  • xhtml_escape: tornado.escape.xhtml_escape的 别名
  • url_escape: tornado.escape.url_escape的别名
  • json_encode: tornado.escape.json_encode的别名
  • squeeze: tornado.escape.squeeze 的别名
  • linkify: tornado.escape.linkify 的别名
  • datetime: Python datetime 模块
  • handler: 当前 RequestHandler 对象
  • request: handler.request的别名
  • current_user: handler.current_user的别名
  • locale: handler.locale的别名
  • _: handler.locale.translate的别名
  • static_url: handler.static_url的别名
  • xsrf_form_html: handler.xsrf_form_html的别名
  • reverse_url: Application.reverse_url的别名
  • ui_methodsui_modules Application 配置的所有入口
  • 传递给 render 或者 render_string的任意参数

当我们创建系统应用时,需要利用到Tornado的很多特性,具体可以参考tornado.template的文档。

Tornado模板会被编译为Python代码,所有的模板输出默认都会使用tornado.escape.xhtml_escape转义,可以在应用的设置中通过参数autoescape=False来关闭转义,或者在tornado.template.Loader构造器中设置这个参数。如果是在模板文件中,可以使用{% autoescape None %} 指令。如果是表达式,则可以使用{% raw ...%}

Tornado提供的自动转义可以避免XSS攻击,但是它并不能处理所有情况,例如Javascript和CSS中的表达式可能需要格外的转义。

国际化

当前用户的本地化信息保存在处理器的self.locale变量中,在模板中可以通过locale访问到。本地化的名称保存在locale.name中,例如en_US。可以使用Locale.translate方法翻译字符串。模板中也可以使用全局函数_()

翻译函数有两种形式:

代码语言:javascript复制
_("Translate this string")

基于当前的locale对象翻译字符串。

代码语言:javascript复制
_("A person liked this", "%(num)d people liked this",
  len(people)) % {"num": len(people)}

提供第三个参数,将字符串翻译成单数形式或者复数形式。

翻译过程中最为常用的还是Python的格式化字符串。

下面是一个模板示例:

代码语言:javascript复制
<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。如果想优先使用用户定义的语言信息,可以重写RequestHandler.get_user_locale方法:

代码语言:javascript复制
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格式文件
  • .csv格式文件

在启动应用的时候,分别通过tornado.locale.load_gettext_translationstornado.locale.load_translations进行加载。

通过tornado.locale.get_supported_locales()可以获取所有支持的语言,tornado会基于用户的设置选择最匹配的语言。

UI模块

Tornado支持UI模块,以便支持标准的,可重用的UI组件。UI模块是特殊的函数,用于渲染页面的组件,而且它们可包含自己的CSSJavascript文件。

例如,如果你想正在实现一个博客系统,你希望博客入口同时出现在博客主页和每篇博客的页面,这时可以创建一个Entry模块,然后在每个页面都进行渲染,首先创建UI模块uimodules.py:

代码语言:javascript复制
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使用uimodules.py

代码语言:javascript复制
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中:

代码语言:javascript复制
{% for entry in entries %}
  {% module Entry(entry) %}
{% end %}

entry.html中:

代码语言:javascript复制
{% module Entry(entry, show_comments=True) %}

模块中还可以包含CSSJavascript信息,重写embedded_cssembedded_javascriptjavascript_files或者css_files即可。

代码语言: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)

模块中的CSSJavascript只会加载一次,CSS包含在<head>中,Javascript位于在</body>前面。

当不需要格外的Python代码时,模板文件本身也可以作为一个模块。例如,前面的例子可以重写为module.entry.html

代码语言:javascript复制
{{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }}
<!-- more template html... -->

修正之后的模块调用如下:

代码语言:javascript复制
{% module Template("module-entry.html", show_comments=True) %}

set_resources函数只能通过{% module Template(...) %}来调用。跟{% include %}不一样,模板模块拥有一个独立的命名空间。

0 人点赞