Tornado 4.3 文档翻译: 用户指南-模板和UI

译者说

Tornado 4.3于2015年11月6日发布,该版本正式支持Python3.5async/await关键字,并且用旧版本CPython编译Tornado同样可以使用这两个关键字,这无疑是一种进步。其次,这是最后一个支持Python2.6Python3.2的版本了,在后续的版本了会移除对它们的兼容。现在网络上还没有Tornado4.3的中文文档,所以为了让更多的朋友能接触并学习到它,我开始了这个翻译项目,希望感兴趣的小伙伴可以一起参与翻译,项目地址是tornado-zh on Github,翻译好的文档在Read the Docs上直接可以看到。欢迎Issues or PR。

模板和UI

Tornado 包含一个简单,快速并灵活的模板语言. 本节介绍了语言以及相关的问题,比如国际化.

Tornado 也可以使用其他的Python模板语言, 虽然没有准备把这些系统整合到 RequestHandler.render 里面. 而是简单的将模板转成字符串并传递给 RequestHandler.write

配置模板

默认情况下, Tornado会在和当前.py文件相同的目录查找关联的模板文件. 如果想把你的模板文件放在不同的目录中, 可以使用template_pathApplication setting (或复写 RequestHandler.get_template_path 如果你不同的处理函数有不同的模板路径).

为了从非文件系统位置加载模板, 实例化子类 tornado.template.BaseLoader并为其在应用设置(application setting)中配置template_loader.

默认情况下编译出来的模板会被缓存; 为了关掉这个缓存也为了使(对目标的)修改在重新加载后总是可见, 使用应用设置(application settings)中的compiled_template_cache=Falsedebug=True.

模板语法

一个Tornado模板仅仅是用一些标记把Python控制序列和表达式嵌入HTML(或者任意其他文本格式)的文件中:

    <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模板支持控制语句(control statements)表达式(expressions).控制语句被包在{%%}中间, 例如,{% if len(items) > 2 %}. 表达式被包在{{}}之间, e.g.,{{ items[0] }}.

控制语句或多或少都和Python语句类似. 我们支持if,for,while, 和try, 这些都必须使用{% end %}来标识结束. 我们也支持 模板继承(template inheritance) 使用extendsblock标签声明, 这些内容的详细信息都可以在 tornado.template 中看到.

表达式可以是任意的Python表达式, 包括函数调用. 模板代码会在包含以下对象和函数的命名空间中执行 (注意这个列表适用于使用 RequestHandler.render
RequestHandler.render_string渲染模板的情况. 如果你直接在RequestHandler之外使用tornado.template模块, 下面这些很多都不存在).

  • 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设置的条目
  • 任何传递给 RequestHandler.renderRequestHandler.render_string 的关键字参数

当你正在构建一个真正的应用, 你可能想要使用Tornado模板的所有特性,尤其是目标继承. 阅读所有关于这些特性的介绍在 tornado.template部分 (一些特性, 包括UIModules是在 tornado.web 模块中实现的)

在引擎下, Tornado模板被直接转换为Python. 包含在你模板中的表达式会逐字的复制到一个代表你模板的Python函数中. 我们不会试图阻止模板语言中的任何东西; 我们明确的创造一个高度灵活的模板系统, 而不是有严格限制的模板系统. 因此, 如果你在模板表达式中随意填充(代码), 当你执行它的时候你也会得到各种随机错误.

所有模板输出默认都会使用 tornado.escape.xhtml_escape 函数转义.这个行为可以通过传递autoescape=NoneApplication 或者tornado.template.Loader 构造器来全局改变, 对于一个模板文件可以使用{% autoescape None %}指令, 对于一个单一表达式可以使用{% raw ...%}来代替{{ ... }}. 此外, 在每个地方一个可选的转义函数名可以被用来代替None.

注意, 虽然Tornado的自动转义在预防XSS漏洞上是有帮助的, 但是它并不能胜任所有的情况. 在某一位置出现的表达式, 例如Javascript 或 CSS, 可能需要另外的转义. 此外, 要么是必须注意总是在可能包含不可信内容的HTML中使用双引号和 xhtml_escape , 要么必须在属性中使用单独的转义函数(参见 e.g. http://wonko.com/post/html-escaping)

国际化

当前用户的区域设置(无论他们是否登录)总是可以通过在请求处理程序中使用self.locale或者在模板中使用locale进行访问. 区域的名字(e.g.,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"]

如果get_user_locale返回None,那我们(继续)依靠Accept-Language头(进行判断).

tornado.locale 模块支持两种形式加载翻译: 一种是用gettext和相关的工具的.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 modules使它易于支持标准, 在你的应用程序中复用UI组件. UI模块像是特殊的函数调用来渲染你的页面上的组件并且它们可以包装自己的CSS和JavaScript.

例如, 如果你实现一个博客, 并且你想要有博客入口出现在首页和每篇博客页, 你可以实现一个Entry模块来在这些页面上渲染它们. 首先,为你的UI模块新建一个Python模块, e.g.,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 %}语法调用一个模块. 例如,你可以调用Entry模块从home.html:

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

entry.html:

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

模块可以包含自定义的CSS和JavaScript函数, 通过复写embedded_css,embedded_javascript,javascript_files, 或css_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 ... %}指令, 模板模块有一个明确的命名空间它们的包含模板-它们只能看到全局模板命名空间和它们自己的关键字参数.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容