Make組ブログ

Python、Webアプリや製品・サービス開発についてhirokikyが書きます。

DjangoでオレオレWidgetを作ってみた(Renderer編)

ウィジェットとは、Django で HTML の入力エレメントを表現するためのオブジェクトです。ウィジェットは、 HTML のレンダリングや、個々のウィジェットに対応するデータをGET/POST 辞書から抽出する処理を行います。

http://djangoproject.jp/doc/ja/1.0/ref/forms/widgets.html

Djangoのフォームは複数のフィールドをもっているわけだけど、そのFieldはウィジェットというのをもっている。これが実際にHTMLのレンダリングを行なっていて、他にもデータを受け渡したりもする、まぁ非常にHTML臭い、Formの末端って感じか。
例えばTextInputというウィジェットはinputタグをレンダリングしてくれて、CharFieldなどはこれを使っている。

ウィジェットというとフィールドのHTMLタグに属性を追加したいときに widgets=TextInput(attrs=...) とするくらいにしか思っていなかったけど、ウィジェットを自分で書けば結構色々できて面白い。

何ができるか

Django既存のウィジェットだけだと使える幅が狭い。自分で書けば色々できる。

例えば既存のRadioSelectは以下のようになる。

これだと各項目(例えば「律ちゃん」)の後ろに何らかの要素を追加することができない。
出力されるHTMLは以下のようになって、まぁliタグで区切られてしまう。

<tr><th><label for="id_radio_0">嫁:</label></th><td><ul>
<li><label for="id_radio_0"><input type="radio" id="id_radio_0" value="R" name="radio" /> 律ちゃん</label></li>
<li><label for="id_radio_1"><input type="radio" id="id_radio_1" value="S" name="radio" /> サニキ</label></li>
</ul></td></tr>

ちょこっと手を加えてやれば以下のようなことは簡単にできる。


書いてみたもの

VerboseRadioFieldRendererというものを書いてみた。
これは通常のラジオボタンの後ろに何でもいいからHTMLを渡せば表示してくれるというもの。
以下のように書けば良い。

VERBOSE_RADIO_CHOICES = (
    (('R', u'律ちゃん'), u'<a href="http://www.tbs.co.jp/anime/k-on/">http://www.tbs.co.jp/anime/k-on/</a>'),
    (('S', u'サニキ'), u'<a href="http://asahi.co.jp/precure/">http://asahi.co.jp/precure/</a>'),
    )

class YomeForm(forms.Form):
    yome = forms.ChoiceField(choices=VERBOSE_RADIO_CHOICES, label=u'嫁', widget=forms.RadioSelect(renderer=VerboseRadioFieldRenderer))

そしたら

こういうラジオボタンレンダリングされる。

使い方解説

ChoiceFieldにRadioSelectを指定する際にRendererも渡している。RadioSelectはrendererという引数でレンダリング用のクラスを受け取ってくれるので、これに自前のRendererクラスを渡せばいい。便利。

ウィジェットWidgetクラスを継承して作ってやればよくて、そのクラスのrenderメソッドがHTMLのレンダリングをする。ただ今回のRadioSelectの場合は受け取ったRendererクラスがrenderメソッドで使われているので、ウィジェットそのものは書かなくて良かった。

おわりに

今回はウィジェットレンダリング(renderメソッド)の部分を記事にした。けどこれだけでは幅が狭い。
受け取った値を処理したり、Widgetにさらに別の値を受け取るようにして、それによって処理を変えるなどが考えられる。
次はそういったことを書こうかなーと思っている。例えばDjangoのClearableFileInputがすごく面白いけど、そういうこと。

ちなみにVerboseRadioFieldRenderer、この記事で紹介した使い方では非常に貧相。今回の例みたいな使い方だけじゃなくて、もっと良い使い方がある。別のウィジェット内で呼び出して使うと良くて、ラジオボタンの後ろに表示したいHTML(もちろん呼び出し元のウィジェットが生成、加工したもの)を渡すとなお良い。そういったことも次の記事で書こうかな。