Skip to content

Latest commit

 

History

History
258 lines (198 loc) · 11.2 KB

templates.md

File metadata and controls

258 lines (198 loc) · 11.2 KB

テンプレート

テンプレート

Flaskは特定のテンプレート言語を強制しませんが、この本ではJinjaを使うことにします。 Flaskコミュニティでは殆どの開発者がJinjaテンプレートを利用しているので私もJinjaを推奨しています。 他のテンプレート言語を利用するFlask拡張にはFlask-GenshiFlask-Mako などがあります。 特に理由が無い限りデフォルトのJinjaテンプレートを利用してください。 シンタックスをよく知らなくても構いません。 これが一番手っ取り早いです。

注記

この本でのJinjaとは、Jinja2のことを示しています。 Jinja1というものがありましたがここではこれを扱いません。 Jinjaと言えばこのJinja2のことだと思ってください。

Jinja入門

Jinjaの構文と言語機能については公式ドキュメントに素晴らしい説明があります。 ここで繰り返し説明は行いませんが最も重要な説明だけを抜粋します。

{% ... %}{{ ... }}という2種類のカッコがあります。 {% ... %}はfor文や代入文などの式の実行に利用します。 {{ ... }}はテンプレート中に記述した式の評価結果を表示するために利用します。

--- Jinja Template Designer Documentation

テンプレートの管理方法

テンプレートは何処に配置すれば良いのでしょうか。 ここまで読んできた方はFlaskはどこに何を配置するかという点について柔軟であることに気がついているかもしれません。 テンプレートも例外ではありません。 ただし、これまでと同様に推奨される場所が存在します。 テンプレートはパッケージディレクトリに配置します。

myapp/
    __init__.py
    models.py
    views/
    templates/
    static/
run.py
requirements.txt
templates/
    layout.html
    index.html
    about.html
    profile/
        layout.html
        index.html
    photos.html
    admin/
        layout.html
        index.html
        analytics.html

テンプレートディレクトリの構造はルーティングの構造とよく似ています。 myapp.com/admin/analyticsというURLでルーティングするテンプレートは、templates/admin/analytics.htmlに配置します。 直接レンダリングされないテンプレートも同じ場所に配置します。 たとえばlayout.htmlは他のテンプレートから継承されるファイルです。

継承

数あるバットマンの物語のように、テンプレートディレクトリはよく継承されます。 通常、全ての子テンプレートに共通する構造を親テンプレートに定義します。 今回の例では、layout.htmlが親テンプレートであり、その他の*.html*ファイルが子テンプレートです。

最上位に配置するlayout.htmlにはサイトの全てのページに共通するレイアウトを定義します。 先ほどのディレクトリ構造を見ると、トップレベルに配置されているmyapp/templates/layout.htmlmyapp/templates/profile/layout.htmlmyapp/templat-es/admin/layout.htmlから継承されていることが解ります。

継承を行うには{% extends %}タグと{% block %}タグを利用します。 親テンプレートでは子テンプレートで上書きされるブロックを定義します。

{# _myapp/templates/layout.html_ #}

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
    {% block body %}
        <h1>親テンプレートで定義した見だしです。</h1>
    {% endblock %}
    </body>
</html>

そして、子テンプレートでは親テンプレートを継承してブロックの内容を記述します。

{# _myapp/templates/index.html_ #}

{% extends "layout.html" %}
{% block title %}Hello world!{% endblock %}
{% block body %}
    {{ super() }}
    <h2>子テンプレートで定義した見出しです。</h2>
{% endblock %}

このsuper()関数は元々親テンプレートのブロックに定義されていた内容を表示します。

注記 継承についての情報はJinja Template Inheritence documentationを参照してください。

マクロの作成

マクロを作成してコードを抽象化することでDRY(同じことを繰り返さない)の原則を実践することが出来ます。 例えば、ナビゲーションバーを表示するHTMLでは現在表示されているページへのリンクは異なるCSSクラスを指定したいはずです。 マクロを利用せずこれを実装すると、if ... else文で現在のページを判別してリンクを記述しなければなりません。

マクロを作成することでこの様なコードを関数の様ににモジュール化出来ます。 それでは、マクロを利用してナビゲーションバーを実装してみましょう。

{# myapp/templates/layout.html #}

{% from "macros.html" import nav_link with context %}
<!DOCTYPE html>
<html lang="en">
    <head>
    {% block head %}
        <title>My application</title>
    {% endblock %}
    </head>
    <body>
        <ul class="nav-list">
            {{ nav_link('home', 'Home') }}
            {{ nav_link('about', 'About') }}
            {{ nav_link('contact', 'Get in touch') }}
        </ul>
    {% block body %}
    {% endblock %}
    </body>
</html>

このテンプレートではまだ定義していないマクロnav_linkを呼び出しています。 このマクロには2つのパラメーター(遷移先のビューの関数名と表示文字列)を渡しています。

注記

インポート文でwith contextを指定していることに気がついたでしょうか? Jinjaのコンテキストとはrender_template()関数で渡された引き数と、Pythonコードから渡された環境コンテキストを含みます。 このインポート文により、これらの値をテンプレート内で利用できるようになります。

例えば、以下のように値を渡します。

`render_template("index.html", color="red")`

Flaskには自動的にコンテキストを渡す機能(requestgsession)がありますが、{% from ... import ... with context %}を指定した時はマクロに対して利用可能な全ての値を渡します。

注記

それでは、テンプレート内にnav_linkマクロを定義してみましょう。

{# myapp/templates/macros.html #}

{% macro nav_link(endpoint, text) %}
{% if request.endpoint.endswith(endpoint) %}
    <li class="active"><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% else %}
    <li><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% endif %}
{% endmacro %}

このマクロはmyapp/templates/macros.htmlに配置しています。 ここではJinjaコンテキストでデフォルトで利用可能なrequestオブジェクトを利用しています。 URLのエンドポイントをチェックして、現在表示しているページであればCSSクラスを変更します。

注記

myapp/templates/user/blog.htmlというテンプレートから、相対パスでマクロをインポートする場合、 from "../macros.html" import nav_link with contextとします。

カスタムフィルター

Jinjaのフィルター機能は{{ ... }}内で利用できます。 この処理はテンプレートが表示される前に適用されます。

<h2>{{ article.title|title }}</h2>

このコードではarticle.titletitleフィルターに渡し、最初の文字を大文字に変換して表示します。これはUNIXでプログラムの出力を別のプログラムに渡す「パイプ」の動作によく似ています。

注記

上記のtitleの様な組み込みフィルターの一覧はJinjaのドキュメントを参照してください。

Jinjaテンプレート内で独自のフィルターを定義することも可能です。 以下に、全ての文字を大文字に変換する単純なフィルターの実装例を示します。

注記 Jinjaには既にこれを実現するupperフィルターやcapitalizeフィルターが存在しますので、実用ではこちらを利用してください。

フィルターモジュールはmyapp/util/filters.pyに配置することにします。 このutilパッケージは種々様々なモジュールを置きます。

# myapp/util/filters.py

from .. import app

@app.template_filter()
def caps(text):
    """全ての文字を大文字に変換します。"""
    return text.uppercase()

このコードでは@app.template_filter()デコレーターを利用してJinjaフィルターを登録しています。 デフォルトでは単純に関数名がフィルター名になりますが、以下の様にデコレーターに引き数を渡してフィルター名を変更することができます。

@app.template_filter('make_caps')
def caps(text):
    """全ての文字を大文字に変換します。"""
    return text.uppercase()

以下のように関数名のcapsではなく、make_capsという名前でフィルターを呼び出せます。

{{ "hello world!"|make_caps }}

フィルターを有効にするには、最上位の*__init.py__*でインポートする必要があります。

# myapp/__init__.py

# 循環importを避けるためにappが初期化されていることを確認してください。
from .util import filters

まとめ

  • Jinjaテンプレートを使ってください。
  • Jinjaでは2種類のカッコを利用します: {% ... %}{{ ... }}です。前者のカッコは式の実行やfor文、値の代入などで利用し、後者のカッコは式の評価結果を表示します。
  • テンプレートは*myapp/templates/*という様なアプリケーションのパッケージ内に配置します。
  • テンプレートディレクトリはアプリケーションのURL構造と対応させることを推奨します。
  • サイト内のページで共通するレイアウトをトップレベルのlayout.htmlに配置し、継承すると良いでしょう。
  • マクロはテンプレート内で利用できる関数の様なものです。
  • フィルターはテンプレート内で変数を加工する機能です。