水島雄太のブログ

個人的かつ雑多なブログです。

Flask Blueprint における template_folderパス解決の罠

Flask には Blueprintというアプリケーションをモジュール化出来る機能が備わっていますが、このBlueprintのtemplate_folderパス解決にはちょっとした罠があります。

Flaskで以下のようなパッケージ構成を組んだと仮定します。

run.py
admin/
    __init__.py
    views.py
    pages/
        index.html
frontend/
    __init__.py
    views.py
    pages/
        index.html

admin/views.py

from flask import Blueprint, render_template
admin = Blueprint('admin', __name__, template_folder='pages')

@admin.route('/')
def index():
    return render_template('index.html')

frontend/views.py

from flask import Blueprint, render_template
frontend = Blueprint('frontend', __name__, template_folder='pages')

@frontend.route('/')
def index():
    return render_template('index.html')

run.py

app = Flask(__name__)

from admin.views import admin
app.register_blueprint(admin)

from frontend.views import frontend
app.register_blueprint(frontend)

一見template folder として admin/views.py では admin/pages/ を 、 frontend/views.py ではfrontend/pages/ を参照するように読めます。

しかしこのコードは正常に動作しません。

admin/views.pyfrontend/views.pytemplate_folder が、admin/pages/ になるか 、 frontend/pages/ になるかは不定となります。

Flask Blueprintのマニュアルを参照してみると以下のように記述があります。 http://flask.pocoo.org/docs/0.10/blueprints/

So if you have a blueprint in the folder yourapplication/admin and you want to render the template 'admin/index.html' and
you have provided templates as a template_folder you will have to create a file like this: yourapplication/admin/templates/admin/index.html.

つまり、以下のようなパッケージ構成にする必要があるということです。

run.py
admin/
    __init__.py
    views.py
    pages/
        admin/
            index.html
frontend/
    __init__.py
    views.py
    pages/
        frontend/
            index.html

admin/views.py

from flask import Blueprint, render_template
admin = Blueprint('admin', __name__, template_folder='pages')

@admin.route('/')
def index():
    return render_template('admin/index.html')

frontend/views.py

from flask import Blueprint, render_template
frontend = Blueprint('frontend', __name__, template_folder='pages')

@frontend.route('/')
def index():
    return render_template('frontend/index.html')

この仕様は度々Flaskの利用者に誤解を与えてきたようで、以下のように Issueでも議論にもなりました。 Blueprint template lookup not documented enough · Issue #266 · pallets/flask · GitHub

なぜこのような仕様になっているかというと、実装上の都合です。

Flaskで指定するtemplate_folderは Jinja2 の FileSystemLoader のsearchpathに渡されます。

searchpathはアプリケーションのトップレベルに追加され、再帰的に検索されるため、template_folderで指定したディレクトリ文字列がかぶっているとどのテンプレートを選択すれば良いのか一意に特定出来ません。

そのため、このような仕様になっています。

Flask Web Development: Developing Web Applications with Python
Flask Web Development: Developing Web Applications with PythonMiguel Grinberg

O'Reilly Media 2014-04-28
売り上げランキング :


Amazonで詳しく見る
by G-Tools
Pythonスタートブック
Pythonスタートブック辻 真吾

技術評論社 2010-04-24
売り上げランキング : 1920


Amazonで詳しく見る
by G-Tools