Tornado 模板和用戶界面

2022-03-08 10:40 更新

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

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

配置模板

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

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

編譯后的模板默認(rèn)緩存; 要關(guān)閉此緩存并重新加載模板以對(duì)底層文件的更改始終可見(jiàn),請(qǐng)使用應(yīng)用程序設(shè)置?compiled_template_cache=False? 或?debug=True?。

模板語(yǔ)法

Tornado 模板只是 HTML(或任何其他基于文本的格式),在標(biāo)記中嵌入了 Python 控制序列和表達(dá)式:

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

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

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

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

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

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

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

國(guó)際化

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

_("Translate this string")

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

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

它根據(jù)第三個(gè)參數(shù)的值翻譯一個(gè)可以是單數(shù)或復(fù)數(shù)的字符串。 在上面的示例中,如果 ?len(people)? 為 ?1?,則返回第一個(gè)字符串的翻譯,否則返回第二個(gè)字符串的翻譯。

最常見(jiàn)的翻譯模式是對(duì)變量使用 Python 命名的占位符(上例中的 %(num)d),因?yàn)檎嘉环梢栽诜g時(shí)移動(dòng)。

以下是一個(gè)正確國(guó)際化的模板:

<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>

默認(rèn)情況下,我們使用用戶瀏覽器發(fā)送的 ?Accept-Language? 表頭檢測(cè)用戶的語(yǔ)言環(huán)境。 如果找不到合適的 ?Accept-Language? 值,我們選擇 ?en_US?。 如果您讓用戶將其區(qū)域設(shè)置為首選項(xiàng),則可以通過(guò)覆蓋 RequestHandler.get_user_locale 來(lái)覆蓋此默認(rèn)區(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 和相關(guān)工具使用的 ?.mo? 格式,以及簡(jiǎn)單的 ?.csv? 格式。 應(yīng)用程序通常會(huì)在啟動(dòng)時(shí)調(diào)用一次 tornado.locale.load_translations 或 tornado.locale.load_gettext_translations

您可以使用 tornado.locale.get_supported_locales() 獲取應(yīng)用程序中支持的語(yǔ)言環(huán)境列表。 根據(jù)支持的語(yǔ)言環(huán)境,用戶的語(yǔ)言環(huán)境被選擇為最接近的匹配。 例如,如果用戶的語(yǔ)言環(huán)境是 ?es_GT?,并且支持 ?es? 語(yǔ)言環(huán)境,那么對(duì)于該請(qǐng)求,?self.locale? 將是 ?es?。 如果找不到匹配項(xiàng),我們將使用 ?en_US?。

用戶界面模塊

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

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

使用應(yīng)用程序中的 ?ui_modules? 設(shè)置告訴 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 %}? 語(yǔ)句調(diào)用模塊。 例如,您可以從 ?home.html? 中調(diào)用 ?Entry? 模塊:

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

?entry.html?:

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

模塊可以通過(guò)覆蓋 ?embedded_css?、?embedded_javascript?、?javascript_files? 或 ?css_files? 方法來(lái)包含自定義 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)

無(wú)論模塊在頁(yè)面上使用多少次,模塊 CSS 和 JavaScript 都會(huì)被包含一次。 CSS 總是包含在頁(yè)面的 ?<head>? 中,而 JavaScript 總是包含在頁(yè)面末尾的 ?</body>? 標(biāo)記之前。

當(dāng)不需要額外的 Python 代碼時(shí),可以將模板文件本身用作模塊。 例如,可以重寫(xiě)前面的示例以將以下內(nèi)容放入 ?module-entry.html? 中:

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

這個(gè)修改后的模板模塊將被調(diào)用:

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

?set_resources? 函數(shù)僅在通過(guò) ?{% module Template(...) %}? 調(diào)用的模板中可用。 與 ?{% include ... %}? 指令不同,模板模塊具有與其包含模板不同的命名空間——它們只能看到全局模板命名空間和它們自己的關(guān)鍵字參數(shù)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)