テンプレートと UI

Tornado には、シンプルで高速かつ柔軟なテンプレート言語が含まれています。このセクションでは、その言語と、国際化などの関連事項について説明します。

Tornado は、他の Python テンプレート言語でも使用できますが、これらのシステムを RequestHandler.render に統合する機能はありません。テンプレートを文字列としてレンダリングし、それを RequestHandler.write に渡すだけです。

テンプレートの設定

デフォルトでは、Tornado はテンプレートファイルを参照する .py ファイルと同じディレクトリ内でテンプレートファイルを検索します。テンプレートファイルを別のディレクトリに配置するには、template_path Application 設定 を使用します(または、ハンドラーごとに異なるテンプレートパスがある場合は RequestHandler.get_template_path をオーバーライドします)。

ファイルシステム以外の場所からテンプレートをロードするには、tornado.template.BaseLoader をサブクラス化し、そのインスタンスを template_loader アプリケーション設定として渡します。

コンパイル済みのテンプレートはデフォルトでキャッシュされます。このキャッシュを無効にして、基盤となるファイルへの変更が常に表示されるようにテンプレートをリロードするには、アプリケーション設定 compiled_template_cache=False または debug=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 テンプレートは、*制御ステートメント*と*式*をサポートします。制御ステートメントは {%%} で囲まれます。例: {% if len(items) > 2 %}。式は {{}} で囲まれます。例: {{ items[0] }}

制御ステートメントは、ほぼ正確に Python ステートメントに対応します。ifforwhile、および try をサポートし、これらはすべて {% end %} で終了します。また、extends ステートメントと block ステートメントを使用した *テンプレート継承* もサポートしています。これらについては、tornado.template のドキュメントで詳しく説明しています。

式は、関数呼び出しを含む任意の Python 式にすることができます。テンプレートコードは、以下のオブジェクトと関数を含む名前空間で実行されます。(このリストは、RequestHandler.render および render_string を使用してレンダリングされたテンプレートに適用されることに注意してください。 RequestHandler の外部で tornado.template モジュールを直接使用している場合、これらのエントリの多くは存在しません)。

実際のアプリケーションを構築する場合は、Tornado テンプレートのすべての機能、特にテンプレートの継承を使用することをお勧めします。これらの機能については、tornado.template セクション(UIModules を含む一部の機能は、tornado.web モジュールに実装されています)で詳しく説明します。

Tornado テンプレートは、内部では Python に直接変換されます。テンプレートに含めた式は、テンプレートを表す Python 関数にそのままコピーされます。テンプレート言語では何も防ごうとはしません。他の厳格なテンプレートシステムが防止する柔軟性を提供するために明示的に作成しました。したがって、テンプレートの式の中にランダムなものを記述すると、テンプレートを実行したときにランダムな Python エラーが発生します。

すべてのテンプレート出力は、デフォルトで tornado.escape.xhtml_escape 関数を使ってエスケープされます。この動作は、Application または tornado.template.Loader のコンストラクタに autoescape=None を渡すこと、テンプレートファイル内で {% autoescape None %} ディレクティブを使用すること、あるいは {{ ... }}{% raw ...%} に置き換えることで、単一の式に対してグローバルに変更できます。さらに、これらの場所では、None の代わりに、代替のエスケープ関数の名前を使用することもできます。

Tornado の自動エスケープは XSS 脆弱性を回避するのに役立ちますが、すべての場合に十分ではないことに注意してください。JavaScript や CSS など、特定の場所に表示される式は、追加のエスケープが必要になる場合があります。また、信頼できないコンテンツが含まれる可能性のある HTML 属性では、常に二重引用符と xhtml_escape を使用するか、属性には別のエスケープ関数を使用する必要があります(例えば、このブログ記事を参照してください)。

国際化

現在のユーザーのロケール(ログインしているかどうかに関わらず)は、リクエストハンドラでは常に self.locale として、テンプレートでは locale として利用できます。ロケールの名前(例:en_US)は locale.name として利用でき、Locale.translate メソッドで文字列を翻訳できます。テンプレートでは、文字列の翻訳のためにグローバル関数 _() も利用できます。翻訳関数には 2 つの形式があります。

_("Translate this string")

これは、現在のロケールに基づいて文字列を直接翻訳します。

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

これは、3 番目の引数の値に基づいて、単数または複数の形になる可能性のある文字列を翻訳します。上記の例では、len(people)1 の場合は最初の文字列の翻訳が返され、それ以外の場合は 2 番目の文字列の翻訳が返されます。

翻訳の最も一般的なパターンは、変数のために 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_signed_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_localeNone を返す場合は、Accept-Language ヘッダーにフォールバックします。

tornado.locale モジュールは、gettext および関連ツールで使用される .mo 形式と、単純な .csv 形式の 2 つの形式での翻訳の読み込みをサポートしています。アプリケーションは通常、起動時に tornado.locale.load_translations または tornado.locale.load_gettext_translations のいずれかを 1 回呼び出します。サポートされている形式の詳細については、これらのメソッドを参照してください。

アプリケーションでサポートされているロケールの一覧を取得するには、tornado.locale.get_supported_locales() を使用します。ユーザーのロケールは、サポートされているロケールに基づいて最も近い一致として選択されます。たとえば、ユーザーのロケールが es_GT で、es ロケールがサポートされている場合、そのリクエストに対する self.localees になります。近い一致が見つからない場合は、en_US にフォールバックします。

UI モジュール

Tornado は、アプリケーション全体で標準的な再利用可能な UI ウィジェットを簡単にサポートできるように、UI モジュールをサポートしています。UI モジュールは、ページのコンポーネントをレンダリングするための特別な関数呼び出しのようなもので、独自の CSS と JavaScript を同梱できます。

たとえば、ブログを実装していて、ブログのホームページと各ブログエントリページのどちらにもブログエントリを表示したい場合は、Entry モジュールを作成して、両方のページにレンダリングできます。まず、UI モジュール用の 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_cssembedded_javascriptjavascript_files、または css_files メソッドをオーバーライドすることにより、カスタム CSS および 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)

モジュールの CSS および JavaScript は、ページでモジュールが何回使用されても、1 回だけ含まれます。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 ... %} ディレクティブとは異なり、テンプレートモジュールは、それを含むテンプレートとは異なる名前空間を持ちます。グローバルテンプレートの名前空間と、独自のキーワード引数のみを参照できます。