[TurboGears] フォームのウィジェットをつくるときは、turbogears.widgets.Formを継承する。
環境
この記事の内容は、TurboGears 1.0.1で確認しました。
やりたいこと
フォーム全体をウィジェットとして作成したいとします。つまり、Kidテンプレートの中で、
${foo.display(value)}
とすると、
<form> : </form>
が出力されるようなウィジェットをつくることを考えます。
やること
そのウィジェットは、turbogears.widgets.Formクラスを継承して作成します。
詳細
- 作者: Mark Dangoor, Kevin Sayfan, Gigi Ramm
- 出版社/メーカー: Prentice Hall
- 発売日: 2006/11/07
- メディア: ペーパーバック
- クリック: 50回
- この商品を含むブログ (6件) を見る
上の本では、独自のウィジェットをつくるときはturbogears.widgets.CompoundWidgetを継承するように書かれています。しかし、CompoundWidgetを継承してフォームを作成すると、例えば以下のように@validateデコレータを使ったときにエラーになります。
@validate(form=foo) def bar(self): pass
エラーの内容は、具体的には以下の通りです。
500 Internal error The server encountered an unexpected condition which prevented it from fulfilling the request. Page handler:> Traceback (most recent call last): File "/usr/lib/python2.4/site-packages/CherryPy-2.2.1-py2.4.egg/cherrypy/_cphttptools.py", line 105, in _run self.main() File "/usr/lib/python2.4/site-packages/CherryPy-2.2.1-py2.4.egg/cherrypy/_cphttptools.py", line 254, in main body = page_handler(*virtual_path, **self.params) File " ", line 3, in do File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/controllers.py", line 334, in expose output = database.run_with_transaction( File " ", line 5, in run_with_transaction File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/database.py", line 302, in so_rwt retval = func(*args, **kw) File " ", line 5, in _expose File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/controllers.py", line 351, in mapping, fragment, args, kw))) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/controllers.py", line 378, in _execute_func output = errorhandling.try_call(func, *args, **kw) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/errorhandling.py", line 73, in try_call return func(self, *args, **kw) File " ", line 3, in do File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/controllers.py", line 131, in validate form = init_form(args and args[0] or kw["self"]) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/controllers.py", line 124, in init_form = lambda self: form(self) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/base.py", line 226, in __call__ return self.display(*args, **params) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/meta.py", line 107, in lockwidget output = self.__class__.display(self, *args, **kw) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/base.py", line 352, in display return super(CompoundWidget, self).display(value, **params) File "/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/base.py", line 264, in display return view.engines.get('kid').transform(params, self.template_c) File "/usr/lib/python2.4/site-packages/TurboKid-0.9.8-py2.4.egg/turbokid/kidsupport.py", line 173, in transform return ElementStream(t.transform()).expand() File "/usr/lib/python2.4/site-packages/kid-0.9.3-py2.4.egg/kid/pull.py", line 99, in expand for ev, item in self._iter: File "/usr/lib/python2.4/site-packages/kid-0.9.3-py2.4.egg/kid/pull.py", line 168, in _track for p in stream: File "/usr/lib/python2.4/site-packages/kid-0.9.3-py2.4.egg/kid/filter.py", line 21, in transform_filter for ev, item in apply_matches(stream, template, templates, apply_func): File "/usr/lib/python2.4/site-packages/kid-0.9.3-py2.4.egg/kid/filter.py", line 25, in apply_matches for ev, item in stream: File "/usr/lib/python2.4/site-packages/kid-0.9.3-py2.4.egg/kid/pull.py", line 168, in _track for p in stream: File "/usr/lib/python2.4/site-packages/kid-0.9.3-py2.4.egg/kid/pull.py", line 210, in _coalesce for ev, item in stream: File " ", line 53, in _pull AttributeError: 'Add' object has no attribute 'id'
ここで、最後の行にある"Add"はコントローラの名前であり、idは作成したウィジェットのテンプレートの中で${value.id}として表示しようとしている属性名です。すなわち、上ではなぜかテンプレートを使ってコントローラを表示しようとしていることになります。
この原因を探るため、@validateデコレータの中身を調べてみます。ファイルは、turbogears/controllers.pyになります。重要ではない箇所は省略します。
def validate(form=None, validators=None, failsafe_schema=errorhandling.FailsafeSchema.none, failsafe_values=None, state_factory=None): (略) def entangle(func): recursion_guard = dict(func=func) if callable(form) and not hasattr(form, "validate"): init_form = lambda self: form(self) else: init_form = lambda self: form def validate(func, *args, **kw): if tg_util.call_on_stack("validate", recursion_guard, 4): return func(*args, **kw) form = init_form(args and args[0] or kw["self"]) (略) return validate return weak_signature_decorator(entangle)
まず着目するのは、以下の部分です。
if callable(form) and not hasattr(form, "validate"): init_form = lambda self: form(self) else: init_form = lambda self: form
すべてのウィジェットの基底クラスであるturbogears.widgets.Widgetは__call__メソッドを実装しており(callable(form)は真となる)、validateメソッドは持っていないので、
init_form = lambda self: form(self)
となります。このとき、ヴァリデータの本体となるvalidate関数の、
form = init_form(args and args[0] or kw["self"])
が実行されると、turbogears.widgets.Widgetの__call__メソッドが呼び出されます。これは、turbogears/widgets/base.pyで以下のようになっています。
def __call__(self, *args, **params): """ Delegate to display. Used as an alias to avoid tiresome typing """ return self.display(*args, **params)
すなわち、ウィジェットを表示しようとします。これがエラーの原因です。
さて、toolboxのWidget Browserで見ることができるTableFormなどはturbogears.widgets.Formクラスを継承していますので、それにならってFormクラスを継承するように変更すると、上のエラーは解消されます。なぜならば、Formクラスはturbogears/widgets/form.pyで、
def validate(self, value, state=None): if self.validator: return self.validator.to_python(value, state)
のようにvalidateメソッドを実装しているからです。これにより、先ほどのコードの中の、
init_form = lambda self: form
が実行されるようになります。そして、先ほどはエラーとなった、
form = init_form(args and args[0] or kw["self"])
が無事に実行されます。