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

Makoのテンプレート継承

Python Mako

Makoのテンプレート継承について、ドキュメントを読みながら確認してみました。はじめはこのドキュメントを翻訳しようとしていたのですが、原文の説明がちょっと回りくどい感じがしたので、それは省略。
わざわざPylonsプロジェクトを作るのも面倒なので、次のようなスクリプトを用意し、コマンドラインから試してみます。必要なテンプレートは、templatesというディレクトリの下に置いています。

from mako.lookup import TemplateLookup

lookup = TemplateLookup(directories=['templates'])
tmpl = lookup.get_template('c.html')
print tmpl.render()

単純な継承

c.htmlがa.htmlを継承するという一番単純なケースです。

## a.html
<html>
<head>
  <title>${self.title()}</title>
</head>
<body>
  ${self.body()}
</body>
</html>
<%def name="title()">title comes here</%def>
## c.html
<%inherit file="a.html" />
<%def name="title()">Hello Mako</%def>

<h1>Hello, Mako</h1>

継承されるテンプレートに${self.block_name()}のように関数を定義しておくと、継承するテンプレートでその定義を上書きすることができます。

子テンプレートで関数ブロックを定義しなかった場合例外が発生するので、必ず子テンプレートで上書きしなくてはならないというケース以外では、親テンプレートの方でも、ダミーの内容でブロックを定義しておくのがよいのではないでしょうか。
上の例だと、<%def name="title()">title comes hereがそれに当たります。つまり、子テンプレートでtitle()を上書きしない場合は、"title comes here"と表示するわけです。


実際の、出力結果は次のようになります。

<html>
<head>
  <title>Hello Mako</title>
</head>
<body>
<h1>Hello, Mako</h1>
</body>
</html>

next名前空間を使う

next名前空間を使うと、子要素を親要素でラッピングし、孫要素を子要素でラッピングする・・・ということができます。
具体例の方が分かりやすいでしょう。
上の例を少し修正し、さらにb.htmlを追加します。

## a.html 親テンプレート
<html>
<head>
  <title>${self.title()}</title>
</head>
<body>
  ${next.body()}
</body>
</html>
<%def name="title()">title comes here</%def>
## b.html 子テンプレート
<%inherit file="a.html" />
<h1>Hello from b.html</h1>
${next.body()}
<p>Bye from b.html</p>
## c.html 孫テンプレート
<%inherit file="b.html" />
<%def name="title()">Hello Mako</%def>
<h1>Hello from c.html</h1>
<p>Bye from c.html</p>

出力結果は次のようになります。

<html>
<head>
  <title>Hello Mako</title>
</head>
<body>
<h1>Hello from b.html</h1>
<h1>Hello from c.html</h1>
<p>Bye from c.html</p>
<p>Bye from b.html</p>
</body>
</html>

継承の順番はa→b→cとなっており、${next.body()}を呼び出すたびに、a→bのbody()、b→cのbody()といったように、継承連鎖の「次」の要素を呼び出すことができるわけです。

parentを使って親を呼び出す

parentを使うと親テンプレートの要素を呼び出すことができます。
b.html, c.htmlを修正しました。

## b.html
<%inherit file="a.html" />
<h1>Hello from b.html</h1>
${next.body()}
<p>Bye from b.html</p>
<%def name="title()">Mako Test</%def>
## c.html
<%inherit file="b.html" />
<%def name="title()">${parent.title()}::Hello Mako</%def>
<h1>Hello from c.html</h1>
<p>Bye from c.html</p>

子テンプレートにもtitle()という要素を定義し、孫テンプレートから${parent.title()}を使って、c.htmlの親テンプレート(b.html)のtitle()を呼び出すようにしました。
出力結果は次のようになります。

<html>
<head>
  <title>Mako Test::Hello Mako</title>
</head>
<body>
<h1>Hello from b.html</h1>
<h1>Hello from c.html</h1>
<p>Bye from c.html</p>
<p>Bye from b.html</p>
</body>
</html>

titleの部分が、「子のtitle()::孫のtitle()」というように構成されています。

まとめ

ちょっと説明が分かりにくいかもしれませんが、自分でやってみるとすぐに馴染むことができます。self, next, parentを使い分けると、親テンプレートの要素に追記したり、あるいは完全に上書きしたりといったことが簡単にできるので便利です。
Djangoにもテンプレート継承はありますが、Djangoで上と同じことをやろうとしても、一工夫必要です。