Tornado 模板和用戶界面

2022-03-08 10:40 更新

Tornado 包含一種簡單、快速且靈活的模板語言。本節(jié)介紹該語言以及國際化等相關問題。

Tornado 也可以與任何其他 Python 模板語言一起使用,盡管沒有將這些系統(tǒng)集成到 RequestHandler.render. 只需將模板呈現(xiàn)為字符串并將其傳遞給RequestHandler.write

配置模板

默認情況下,Tornado 在與引用它們的 ?.py ?文件相同的目錄中查找模板文件。 要將您的模板文件放在不同的目錄中,請使用 ?template_path? 應用程序設置(或者如果您對不同的處理程序有不同的模板路徑,則覆蓋 ?RequestHandler.get_template_path?)。

要從非文件系統(tǒng)位置加載模板,子類 ?tornado.template.BaseLoader?傳遞一個實例作為?template_loader?應用程序設置。

編譯后的模板默認緩存; 要關閉此緩存并重新加載模板以對底層文件的更改始終可見,請使用應用程序設置?compiled_template_cache=False? 或?debug=True?。

模板語法

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 模板支持控制語句和表達式。 控制語句被 ?{%? 和 ?%}? 包圍,例如 ?{% if len(items) > 2 %}?。 表達式被 ?{{? 和 ?}}? 包圍,例如 ?{{ item[0] }}?。

控制語句大多數(shù)精確地映射到 Python 語句。 它支持 ?if?、?for?、?while ?和 ?try?,它們都以 ?{% end %}? 結束。 它還支持使用 ?extends ?和 ?block ?語句的模板繼承,在 tornado.template 的文檔中有詳細描述。

表達式可以是任何 Python 表達式,包括函數(shù)調用。 模板代碼在包含以下對象和函數(shù)的命名空間中執(zhí)行。 (請注意,此列表適用于使用 RequestHandler.render 和 render_string 呈現(xiàn)的模板。如果您直接在 RequestHandler 之外使用 tornado.template 模塊,則其中許多條目不存在)。

當你構建一個真正的應用程序時,你會想要使用 Tornado 模板的所有特性,尤其是模板繼承。閱讀本節(jié)中有關這些功能的所有信息tornado.template (一些功能,包括?UIModules?在 tornado.web模塊中實現(xiàn))

在底層,Tornado 模板被直接翻譯成 Python。您包含在模板中的表達式被逐字復制到代表您的模板的 Python 函數(shù)中。它不會試圖阻止模板語言中的任何內容;它明確創(chuàng)建它是為了提供其他更嚴格的模板系統(tǒng)所阻止的靈活性。因此,如果您在模板表達式中編寫隨機內容,則在執(zhí)行模板時會出現(xiàn)隨機 Python 錯誤。

默認情況下,使用 tornado.escape.xhtml_escape 函數(shù)對所有模板輸出進行轉義。 可以通過將 ?autoescape=None? 傳遞給 Application 或 tornado.template.Loader 構造函數(shù)來全局更改此行為,對于具有 ?{% autoescape None %}? 指令的模板文件,或者通過替換 ?{{ ... }}? 來更改單個表達式 使用 ?{% raw ...%}?。 此外,在這里的每一個地方,都可以使用替代轉義函數(shù)的名稱來代替 ?None?。

請注意,雖然 Tornado 的自動轉義有助于避免 XSS 漏洞,但并非在所有情況下都足夠。 出現(xiàn)在某些位置的表達式,例如 JavaScript 或 CSS,可能需要額外的轉義。 此外,必須注意始終在可能包含不受信任的內容的 HTML 屬性中使用雙引號和 xhtml_escape,或者必須為屬性使用單獨的轉義函數(shù)(參見例如此博客文章)。

國際化

當前用戶的語言環(huán)境(無論他們是否登錄)始終在請求處理程序中作為?self.locale?和在模板中作為?locale?可用。 語言環(huán)境的名稱(例如 ?en_US?)以 ?locale.name?的形式提供,您可以使用 Locale.translate 方法翻譯字符串。 模板還具有可用于字符串翻譯的全局函數(shù)調用 ?_()?。 translate函數(shù)有兩種形式:

_("Translate this string")

它直接根據(jù)當前語言環(huán)境翻譯字符串,并且:

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

它根據(jù)第三個參數(shù)的值翻譯一個可以是單數(shù)或復數(shù)的字符串。 在上面的示例中,如果 ?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>

默認情況下,我們使用用戶瀏覽器發(fā)送的 ?Accept-Language? 表頭檢測用戶的語言環(huán)境。 如果找不到合適的 ?Accept-Language? 值,我們選擇 ?en_US?。 如果您讓用戶將其區(qū)域設置為首選項,則可以通過覆蓋 RequestHandler.get_user_locale 來覆蓋此默認區(qū)域選擇:

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() 獲取應用程序中支持的語言環(huán)境列表。 根據(jù)支持的語言環(huán)境,用戶的語言環(huán)境被選擇為最接近的匹配。 例如,如果用戶的語言環(huán)境是 ?es_GT?,并且支持 ?es? 語言環(huán)境,那么對于該請求,?self.locale? 將是 ?es?。 如果找不到匹配項,我們將使用 ?en_US?。

用戶界面模塊

Tornado 支持 UI 模塊,以便在您的應用程序中輕松支持標準的、可重用的 UI 小部件。 UI 模塊就像渲染頁面組件的特殊函數(shù)調用,它們可以使用自己的 CSS 和 JavaScript 打包。

例如,如果你正在實現(xiàn)一個博客,并且你想讓博客條目出現(xiàn)在博客主頁和每個博客條目頁面上,你可以制作一個?Entry?模塊來在兩個頁面上呈現(xiàn)它們。 首先,為您的 UI 模塊創(chuàng)建一個 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 %}

?entry.html?:

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

模塊可以通過覆蓋 ?embedded_css?、?embedded_javascript?、?javascript_files? 或 ?css_files? 方法來包含自定義 CSS 和 JavaScript 函數(shù):

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? 函數(shù)僅在通過 ?{% module Template(...) %}? 調用的模板中可用。 與 ?{% include ... %}? 指令不同,模板模塊具有與其包含模板不同的命名空間——它們只能看到全局模板命名空間和它們自己的關鍵字參數(shù)。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號