ウィジェット

ウィジェットは、以下を行うことを意図したコード片です:

  • cstruct をフォームレンダリングにおける表示用の HTML に シリアライズする
  • フォーム送信から得られたデータ (pstruct) を schema node による逆シリアライズに適したデータ構造 (cstruct) に逆シリアライズする
  • バリデーションエラーを扱う

Deform は組み込みのウィジェットを多数同梱しています。組み込みのウィジェット が予測していなかったことをしようとしているのでなければ、たいていは自作 のウィジェットは必要はありません。しかし、やりたいことが組み込みの Deform ウィジェットだけでは実現できない場合、タスクにより適した新しい ウィジェットを作成することで Deform を拡張することができます。

ウィジェットテンプレート

ウィジェットはテンプレートファイルを使用する必要はありませんが、 組み込みのウィジェットのそれぞれはテンプレートファイルを使用しています。 テンプレートは通常、デフォルトウィジェットの templatereadonly_template 属性によって割り当てられます; その後、それらの 属性はウィジェットの serialize メソッドの中で使用されます, ala:

1
2
3
4
5
 def serialize(self, field, cstruct, readonly=False):
     if cstruct in (null, None):
         cstruct = ''
     template = readonly and self.readonly_template or self.template
     return field.renderer(template, field=field, cstruct=cstruct)

deform.field.renderer() メソッドは、 (texinput のような) 論理的なテンプレート名を受け取り、active な Deform renderer を 使用してそれをレンダリングするメソッドです; デフォルトのレンダラーは ZPT レンダラーです。それは、 deform パッケージ内にある deform/templates ディレクトリ内のテンプレートを使用します。 ウィジェットテンプレートについての詳細は、 テンプレート を 参照してください。

ウィジェット Javascript

いくつかの組み込みの Deform ウィジェットは JavaScript を要求します。 組み込みの Deform ウィジェットが正常に機能するために JavaScript を読み込む ことができるように、フォームを含むページはレンダリングされる際に deform.load() JavaScript 関数を呼ばれなければなりません。

いくつかの組み込みの Deform ウィジェットには、ロードされたときにローカルの input 要素を操作する JavaScript が含まれます。例えば、 deform.widget.AutocompleteInputWidget テンプレートはこのように なります:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 <span tal:omit-tag="">
     <input type="text"
            name="${field.name}"
            value="${cstruct}"
            tal:attributes="size field.widget.size;
                            class field.widget.css_class"
            id="${field.oid}"/>
     <script tal:condition="field.widget.values" type="text/javascript">
       deform.addCallback(
         '${field.oid}',
         function (oid) {
             $('#' + oid).autocomplete({source: ${values}});
             $('#' + oid).autocomplete("option", ${options});
         }
       );
     </script>
 </span>

field.oid は、 Deform が各フィールドウィジェットをレンダリング する際に与える順序付き識別子を参照します。このウィジェットがレンダリング されるときに実行されるスクリプトが deform.addCallback という名前の 関数を呼び出すこと、その関数には field.oid の値とコールバック関数が それぞれ oidcallback として渡されることが分かるでしょう。 コールバック関数は、実行されると $('#' + oid) に対する jQuery セレクタ結果の autocomplete メソッドを呼び出します。

上で定義されたコールバックは、以下の2つの状況で呼ばれます:

  • ページが最初にロードされて deform.load() JavaScript 関数が呼ばれたとき。
  • sequence が関係していて、シーケンス要素が追加されたとき。 これは deform.addSequenceItem() JavaScript 関数の呼び出しの 結果として起こります。

デフォルトの Deform ウィジェットが、レンダリングされたスクリプトの中で 単に直接 ${field.oid} を使用するのではなく deform.addCallback を呼んでいる理由は、シーケンス要素の処理は既存のプロトタイプノードの クローンを作ることによって完全にクライアント側で起こり、シーケンス要素が 追加できるようになる前にフィールドを構成する HTMLの すべての id 属性が ユニークになるように変更しなければならないためです。 addCallback による間接性は、プロトタイプノードの oid ではなく 修正された oid で コールバックが実行されることを保証します。あなたのウィジェットが シーケンスの一部として使用されることを意図している場合、同じように しなければなりません。

ウィジェットの要求 (requirement) とリソース

いくつかのウィジェットは、 (CSS や Javascript ファイルのような) 外部リソースが適切に働くことを必要とします。 Deform は、特定のフォーム レンダリングで どの リソースが必要とされるかを決定するための メカニズムを提供します。それにより、アプリケーションはレンダリングされた フォームを含むページの HEAD にそれらのリソースをインクルードすることが できます。

(低レベル) deform.Field.get_widget_requirements() メソッド

ウィジェットとともにフォームが完全に実体化された後で、フォームオブジェクトに 対して deform.Field.get_widget_requirements() メソッドを呼び出すと、 2 要素のタプルのシーケンスが返されます。 deform.Field.get_widget_requirements() によって空でないシーケンス が返される場合、それはフォームをレンダリングするページ上の何らかの ウィジェットが適切に機能するために CSS あるいは JavaScript リソースを ロードする必要があるということを意味します。

それぞれの2要素タプルの第1要素は 要求名 を表わします。 それは、1つ または複数 の Javascript あるいは CSS リソースへの論理的 な参照を表現しています。それぞれの2要素タプルの第2要素は要求バージョンです。 それは None かもしれません。その場合には、要求バージョンは無指定です。 要求バージョンが無指定の場合、リソースセットのデフォルトバージョンが選択 されます。

要求名/バージョンのペアは何らかのリソースの集合を示唆していますが、 それは URL ではなく、ファイル名やファイル名のプレフィックスでもありません。 deform.Field.get_widget_requirements() の呼び出し元は 返されたリソース名を 論理的な 参照として使用しなければなりません。 例えば、要求名が jquery でバージョン id が 1.4.2 の場合、 呼び出し元はページヘッダー内で jQuery ライブラリをロードする必要があると 解釈することができます (例えばレンダリングされた HTML ページの HEAD タグ 内に HTML <script type="text/javascript" src="http://deformdemo.repoze.org/static/scripts/jquery-1.4.2.min.js"></script> を挿入することによって)。

ユーザはほぼ確実に、フォームレンダリングで要求される相対的なリソースパスの 完全に展開されたリストを得るために deform.Field.get_widget_resources() API (次のセクションの中で説明 されます) を使用することを好むでしょう。しかし、カスタムな要求名から リソースへのマッピングを resource registry (リソースレジストリ) の 助けなしで行う必要がある場合、 deform.Field.get_widget_requirements() を使用することができます。

deform.Widgetrequirements の説明も参照してください。

(高レベル) deform.Field.get_widget_resources() メソッド

フォームの要求をリソースの相対ファイル名に解決するメカニズムが 存在します: deform.Field.get_widget_resources() メソッドです。

Note

Deform はフレームワークについては関知しない (framework-agnostic) ので、 このメソッドはフォームを正しくレンダリングするのに必要なリソースパスを 単に呼び出し元に 報告 します。 Deform はページレンダリングに際して 報告した要求が満たされるように準備したりはしません (できません); これらの要求を満たすことは、呼び出し元のコードの責任です。

deform.Field.get_widget_resources() メソッドは、 jscss という2つのキーを持つ辞書を返します。辞書中の各キーと関係する値は 相対的な リソース名のリストです。それぞれのリソース名は、アプリケーション の Deform リソースが含まれる静的ディレクトリ (通常 Deform パッケージ内部 の static ディレクトリのコピー) からの相対とみなされます。このメソッドを 引数なしで呼ぶと辞書を返します。この辞書は、現在のフォームによって要求 されるリソースを表すのと同じ形式です。このメソッドを要求のセット (deform.Field.get_widget_requirements() メソッドによって返された値) を伴って呼ぶと、渡された要求を解決しようとします。このメソッドは 以下のように使うことができます:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import deform

form = deform.Form(someschema)
resources = form.get_widget_resources()
js_resources = resources['js']
css_resources = resources['css']
js_links = [ 'http://my.static.place/%s' % r for r in js_resources ]
css_links = [ 'http://my.static.place/%s' % r for r in css_resources ]
js_tags = ['<script type="text/javascript" src="%s"></script>' % link
           for link in js_links]
css_tags = ['<link rel="stylesheet" href="%s"/>' % link
           for link in css_links]
tags = js_tags + css_tags
return {'form':form.render(), 'tags':tags}

返り値をレンダリングするテンプレートは「タグ」の意味を理解する必要があります (それは HEAD の中にタグをまとめて挿入するでしょう)。明らかに、 get_widget_resources の結果を使用して HEAD タグをレンダリングするための 他の戦略を工夫することができます。これは単なる例です。

deform.Field.get_widget_resources() は、リソースパスに 要求名をマッピングするために resource registry を使用 します。 deform.Field.get_widget_resources() が要求名を 解決できない場合、または提供された要求名の バージョン に関連付けられた リソースのセットを見つけることができない場合、 ValueError が送出されます。この例外が起こる場合、それはフォームに 関連付けられた resource registry が要求名やバージョンを 解決できないということを意味します。この場合、要求のことを知っている リソースレジストリを明示的にフォームに関連付ける必要があるでしょう。 例えば:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
registry = deform.widget.ResourceRegistry()
registry.set_js_resources('requirement', 'ver', 'bar.js', 'baz.js')
registry.set_css_resources('requirement', 'ver', 'foo.css', 'baz.css')

form = Form(schema, resource_registry=registry)
resources = form.get_widget_resources()
js_resources = resources['js']
css_resources = resources['css']
js_links = [ 'http://my.static.place/%s' % r for r in js_resources ]
css_links = [ 'http://my.static.place/%s' % r for r in css_resources ]
js_tags = ['<script type="text/javascript" src="%s"></script>' % link
           for link in js_links]
css_tags = ['<link type="text/css" href="%s"/>' % link
           for link in css_links]
tags = js_tags + css_tags
return {'form':form.render(), 'tags':tags}

deform.Field.set_default_resource_regis() クラスメソッドを 呼び出すことにより、別のデフォルトのリソースレジストリを すべての フォームと関連付けることができます:

1
2
3
4
registry = deform.widget.ResourceRegistry()
registry.set_js_resources('requirement', 'ver', 'bar.js', 'baz.js')
registry.set_css_resources('requirement', 'ver', 'foo.css', 'baz.css')
Form.set_default_resource_registry(registry)

これによって registry レジストリが set_default_resource_registry の 呼び出しより後に生成されたすべてのフォームインスタンスでデフォルトの リソースレジストリとして使用されることになります。これによって再び リソース解決が適切に動作するようになることが期待できます。

deform.Fieldresource_registry 引数の ドキュメンテーションと、 deform.widget.ResourceRegistry の ドキュメンテーションも参照してください。

ウィジェットの要求を指定する

新しいウィジェットを生成するときに、 requirements 属性を使用することで 要求を指定することができます:

1
2
3
4
from deform.widget import Widget

class MyWidget(Widget):
    requirements = ( ('jquery', '1.4.2'), )

要求名の構成に関して明確なルールはありません。ウィジェットの docstring は、 その要求名が何を意味するか、そして論理的な要求名からどのように resource registry 内のリソースパスにマッピングするかを説明するべきです。 例えば、 docstring にはこのようなテキストがあるかもしれません: 「このウィジェットは、要求リストの中にある jquery.tools の ライブラリ名を使用します。名前 jquery.tools は、このウィジェットを 使用するフォームを含む HTML ページをレンダリングする前に jQuery Tools ライブラリをロードしなければならないことを示唆します; jQuery Tools は jQuery に依存します。したがって、 jQuery もロード しなければなりません。ウィジェットは jQuery Tools バージョン X.X (version フィールドで指定) を期待します。 それは、 jQuery バージョン X.X が以前にロードされることを期待します」。 そして次に、論理的な jquery.tools 名を相対的なリソースパスのセット へ解決するためにリソースのセットを resource registry に追加する 必要があり、その結果として生じるカスタムリソースレジストリをフォームを 構築する際に使用すべきであるということを説明するかもしれません。 デフォルトのリソースレジストリ (deform.widget.resource_registry) は、 新しく作られた要求のためのリソースのマッピングを含んでいません。

独自ウィジェットを書く

Deform ウィジェットを書くことは、概念的なウィジェットインタフェースを 提供するオブジェクトを作成することを意味します。そのインタフェースは deform.widget.Widget クラスのドキュメンテーションで述べられて います。このインタフェースを実装するものを作成する最も簡単な方法は、 deform.widget.Widget クラスそれ自体から直接継承するクラスを 作成することです。

deform.widget.Widget クラスは、コンストラクタと handle_error メソッドの具体的な実装を持ち、すべての必須の属性に対するデフォルト値を 持っています。 deform.widget.Widget クラスは、さらに serializedeserialize の抽象的な実装を持っています。(それぞれ NotImplementedError 例外を送出します); サブクラスはこれらを オーバーライドしなければなりません; さらに、任意で基底クラスの handle_error メソッドをオーバーライドすることもできます。

例えば:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 from deform.widget import Widget

 class MyInputWidget(Widget):
     def serialize(self, field, cstruct=None, readonly=False):
         ...

     def deserialize(self, field, pstruct=None):
         ...

     def handle_error(self, field, error):
         ...

serialize メソッド、 deserialize メソッド、 handle_error メソッドについて次に説明します。

serialize メソッド

ウィジェットの serialize メソッドは、 HTML レンダリングに対する cstruct 値をシリアライズしなければなりません。 cstruct 値は、 このウィジェットに関連したスキーマノード用の Colander スキーマ シリアライズに起因する値です。このメソッドの結果は常にある HTML を含んでいる unicode 型でなければなりません。

serialize に渡される field 引数は、このウィジェットが 取り付けられている field オブジェクトです。 field オブジェクト自身がそのフィールドを使用するウィジェットへの 参照を (field.widget として) 持っているので、循環参照を回避するために フィールドオブジェクトは field 属性を持つウィジェットではなく ウィジェットの serialize メソッドに渡されます。

serialize に渡された readonly 引数が True の場合、この シリアライズの結果は cstruct データから HTML への読み出し専用の レンダリングである (つまりアクティブなフォームコントロールがない) ことを 示します。

新しい MyInputWidget がシリアライズ中にテキスト入力コントロールを 作成することだけが必要としましょう。その serialize メソッドはこのように 定義されるかもしれません:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 from deform.widget import Widget
 from colander import null
 import cgi

 class MyInputWidget(Widget):
     def serialize(self, field, cstruct=None, readonly=False):
         if cstruct is null:
             cstruct = u''
         quoted = cgi.escape(cstruct, quote='"')
         return u'<input type="text" value="%s">' % quoted

呼び出し元からデータを渡されたかどうかに関わらず、 serialize メソッド が常にシリアライズ結果を返す責任を持つことに注意してください。通常 cstruct 値は、ウィジェットのレンダリングロジックで直接使用することが できる適切なデータを含みます。しかし、それは時々 colander.null に なります。このウィジェットを使用するフォームがデータなしでシリアライズ される時 (例えば “add form”) 、 colander.null になるでしょう。

すべてのウィジェットは、 serialize 中に cstruct として渡された値が colander.null 番兵値かどうかチェック しなければなりません 。 ウィジェットはこの不測の事態を扱うことに責任を持ちます。 それは、しばしば論理的な「空の」値をシリアライズすることにより行われます。

ウィジェットがデフォルト値をどのように計算しようとするかにかかわらず、 それは cstructcolander.null である場合にもレンダリング結果を 返せなければなりません。上記の例の場合では、ウィジェットは cstruct 値として空の文字列を使用しています。この種類の「スカラー」入力ウィジェット では、それが適切です; より「構造的な」種類のウィジェットについては、 空の辞書やリストのような他の値がデフォルトになるかもしれません。

例の中で作成した MyInputWidget はテンプレートを使用しません。 あらゆるウィジェットはテンプレートを使用することができますが、その使用 は必須ではありません; 特定のウィジェットがテンプレートを使用するかどうかは、 Deform には関係ないことです: deform は、単にウィジェットの serialize メソッドが HTML を含んだ Unicode オブジェクトを返すことを期待します; その Unicode オブジェクトをウィジェットがどのように作成するかについても あまり考慮しません。

組み込みの Deform ウィジェット (deform.widget の中のウィジェット実装) は、レンダリングされたときのウィジェットの見た目を Deform 内部の Python コードを変更することなく簡単にオーバーライドできるように、たまたま テンプレートを使用しています。ウィジェット自体に関係する Python コード を変更する必要がある代わりに、組み込みのウィジェットのユーザは、しばしば 組み込みのウィジェット実装に関連付けられたテンプレートを交換することで 十分なカスタマイズを行なうことができます。しかしながら、これは純粋に 利便性のためです; テンプレートは、主に組み込みのウィジェットセットの実装詳細で、 コア Deform フレームワークにとって不可欠な部分ではありません。

「スカラー」ウィジェット (値のコレクションではなく単一の値を表わすウィジェット) は、「必須」ラベルや、バリデーションが失敗したときにエラー情報を提供するために 使用される周囲を囲む div のような「ページの装飾」を提供する責任を持たないことに 注意してください。これは、スカラーウィジェットフィールドの親フィールド に関連付けられた「構造的な」ウィジェット(「親ウィジェット」) の責任です; 親ウィジェットは、通常 deform.widget.MappingWidget または deform.widget.SequenceWidget のうちのどちらかです。

deserialize メソッド

ウィジェットの deserialize メソッドは、 pstruct 値を cstruct 値に逆シリアライズして cstruct 値を返さなければ なりません。 pstruct 引数は、 Peppercorn パッケージの parse メソッドに起因する値です。 field 引数はこのウィジェットが 取り付けられているフィールドオブジェクトです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 from deform.widget import Widget
 from colander import null
 import cgi

 class MyInputWidget(Widget):
     def serialize(self, field, cstruct, readonly=False):
         if cstruct is null:
             cstruct = u''
         return '<input type="text" value="%s">' % cgi.escape(cstruct)

     def deserialize(self, field, pstruct):
         if pstruct is null:
             return null
         return pstruct

ウィジェットの deserialize メソッドが (serialize と同じように) colander.null 値が渡される可能性に対処しなければならないことに注意 してください。 pstruct に値が含まれない場合、ウィジェットに colander.null が渡されます。ウィジェットは通常 deserializecolander.null 値が渡された場合 colander.null を返すことで対処 します。それは、 underlying スキーマに対してスキーマノードに対する デフォルト値が存在するなら使用されるべきであることを示します。

deserialize メソッドに対する他のもう一つの実際の制約は、 serialize メソッドが deserialize の返り値を再シリアライズできなければならない、 というものです。

handle_error メソッド

deform.widget.Widget クラスは既に適切な実装を持っています; deform.widget.Widget からサブクラス化する場合、特別な エラーハンドリングの振る舞いが必要なければ、デフォルト実装のオーバーライドは 必須ではありません。

これは、 MyInputWidget クラスでの deform.widget.Widget.handle_error() メソッドの実装です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 from deform.widget import Widget
 from colander import null
 import cgi

 class MyInputWidget(Widget):
     def serialize(self, field, cstruct, readonly=False):
         if cstruct is null:
             cstruct = u''
         return '<input type="text" value="%s">' % cgi.escape(cstruct)

     def deserialize(self, field, pstruct):
         if pstruct is null:
             return null
         return pstruct

     def handle_error(self, field, error):
         if field.error is None:
             field.error = error
         for e in error.children:
             for num, subfield in enumerate(field.children):
                 if e.pos == num:
                     subfield.widget.handle_error(subfield, e)

ウィジェットの handle_error メソッドは以下のことをしなければなりません:

  • 渡された field オブジェクトの error 属性をセットする (まだ error 属性がセットされていなかった場合)。
  • エラーがあるすべてのサブフィールドの handle_error メソッドを呼び出す。

handle_error をオーバーライドできる能力は、 (親フィールドに子のエラー をすべて表示するような) 純粋に高度なタスクのために存在します。例えば:

1
2
3
4
5
6
7
8
 def handle_error(self, field, error):
     msgs = []
     if error.msg:
         field.error = error
     else:
         for e in error.children:
             msgs.append('line %s: %s' % (e.pos+1, e))
         field.error = Invalid(field.schema, '\n'.join(msgs))

この実装は、フィールドの子にエラーを表示しません; 代わりに、レビューのために すべての子のエラーをフィールド自体に表示します。

テンプレート

ウィジェットをレンダリングするために使用されるテンプレートは、ウィジェット オブジェクトから入力を受け取ります。これには field が含まれ、それは ウィジェットによって表現されるフィールドオブジェクトになるでしょう。 それは、通常 field.name 値をウィジェットの中の最初のコントロールの name 入力要素として 使用し、 field.oid 値をウィジェットの中の 最初のコントロールの id 要素として使用します。