Tornado 4.3文件翻譯: 使用者指南-模板和UI

TaoBeier發表於2016-01-16

譯者說

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 ... %}指令, 模板模組有一個明確的名稱空間它們的包含模板-它們只能看到全域性模板名稱空間和它們自己的關鍵字引數.

相關文章