テンプレートと 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 ステートメントに対応します。if
、for
、while
、および try
をサポートし、これらはすべて {% end %}
で終了します。また、extends
ステートメントと block
ステートメントを使用した *テンプレート継承* もサポートしています。これらについては、tornado.template
のドキュメントで詳しく説明しています。
式は、関数呼び出しを含む任意の Python 式にすることができます。テンプレートコードは、以下のオブジェクトと関数を含む名前空間で実行されます。(このリストは、RequestHandler.render
および 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
のエイリアスApplication
設定のui_methods
およびui_modules
からのすべてのエントリrender
またはrender_string
に渡されたすべてのキーワード引数
実際のアプリケーションを構築する場合は、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_locale
が None
を返す場合は、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.locale
は es
になります。近い一致が見つからない場合は、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_css
、embedded_javascript
、javascript_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 ... %}
ディレクティブとは異なり、テンプレートモジュールは、それを含むテンプレートとは異なる名前空間を持ちます。グローバルテンプレートの名前空間と、独自のキーワード引数のみを参照できます。