読者です 読者をやめる 読者になる 読者になる

【Bottle】@viewデコレータを自前で定義する【Jinja2】

通常Bottleを使用する場合は、使用するテンプレートに併せて必要なメソッドやデコレータをインポートして用いることで、viewテンプレートの描画を非常に簡単に行うことが出来ます。

例えば、Jinja2テンプレートを使用し、メソッドでviewテンプレートの描画を行う場合は以下のようになります。

from bottle import route, jinja2_template as template

@route('/')
def index():
    return template('index.html', data = some_func())

また、同様の処理をデコレータで実装すると以下のようになります。

from bottle import route, jinja2_view as view

@route('/')
@view('index.html')
def index():
    return dict(data=some_func())

このメソッド群は非常に便利ですが、細かいセッティングをしようとすると、少なくない問題が発生します。
例えば、テンプレート側で何らかの関数を利用したい場合、jinja2.Environmentglobalsに追加しますが、bottleに用意されているメソッドでそれを実装するのは少々難しいです(そこまでソースコードを読んでいないので可能かもしれません)。

というわけで、templateの展開を行うviewデコレータを自力で実装してしまえという結論に至ります。

環境

Bottle v0.12
Jinja2 v2.8

実装

新しいview用のファイルをdecoview.pyという名前で作成し、以下のようにします。

# decoview.py
import functools
import jinja2

env = jinja2.Environment(
    loader=jinja2.FileSystemLoader('./path/to/views'),
    autoescape = True
)

env.globals.update({
    'some_func_key', some_func()
})

def some_func():
    return 'Called some_func!'

def view(template_name):
    def decorator(view_func):
        @functools.wraps(view_func)
        def wrapper(*args, **kwargs):
            response = view_func(*args, **kwargs)
            if isinstance(response, dict):
                template = env.get_or_select_template(template_name)
                return template.render(**response)
            else:
                return response
        return wrapper
    return decorator

せっかくなのでautoescapeTrueにしてみました。

新しく作成したdecoviewをインポートして使用すれば、jinja2テンプレートファイルの中でsome_func()が実行可能です。

# index.py
from bottle import route
from decoview import view

@route('/')
@view('index.html')
def index():
    return {}

例えば下記のようにjinja2テンプレートを呼び出します。

<!-- index.html -->
{{ some_func_key }}

きっとCalled some_func!という文字列が表示されていることと思います。

参考

API — Jinja2 Documentation (2.8-dev)
Jinja2 templates and Bottle | Reliably Broken