基本的な使い方

この章では、フォームをレンダリングしてから、入力を受け取ってバリデーションを 行うまでの、 Deform の基本的な使い方について見ていきましょう。

フォームをレンダリングして、それに続くフォーム送信入力を受け取れるように するために、開発者がしなければならないステップは次のとおりです:

  • スキーマを定義する
  • フォームオブジェクトを作成する
  • フォームのフィールドにデフォルト以外のウィジェットを割り当てる (オプション)
  • フォームをレンダリングする

フォームが表示されると、ユーザはブラウザの中でフォームとやり取りして、 ある時点でそれを送信します。ユーザがフォームを送信すると、ユーザから 提供されたデータは適切にバリデーションされるか、そうでなければエラー マーカーとともにフォームが再度レンダリングされる必要があるでしょう。 それは、どの部分を (スキーマで定義されているように) 「正しく」再入力する 必要があるかをユーザに通知することをサポートします。ユーザはフォームに 入力して、送信して、再度バリデーションを行うことを無制限に継続すること ができます。

スキーマの定義

Deform を使うための第一歩は、フォームのレンダリングを通して受け取ろうとする データ構造を表わす schema を作成することです。

例えば、リレーショナル・データベースから得られるデータ構造におおよそ基づいて フォームを作成することを想像してください。そのようなデータ構造の一例は このようなものです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[
{
 'name':'keith',
 'age':20,
},
{
 'name':'fred',
 'age':23,
},
]

言い換えれば、実行されるデータベースクエリは people のシーケンスを 返します; それぞれの人は何らかのデータで表わされています。このデータを 編集する必要があります。このリストの中にいる人の数はそれほど多くありません。 そのためリストを辿るのに何らかのページングやバッチは必要ありません; 単一のフォームページにすべて表示することができます。

Deform は、上記の例と類似した構造を appstruct として指定しています。 用語 “appstruct” は、「アプリケーション構造」の短縮形です。なぜなら、 それがアプリケーションで通常関心のある高レベルの構造の一種だからです: appstruct で表されたデータは、アプリケーションそれ自体にとって直接的に 有用です。

Note

appstruct は、(pstructcstruct 構造のような) Deform が使用する他の構造とは異なります: pstruct とcstruct は、 典型的にはレンダリング処理の間でのみ有用です。

通常、ある appstruct を与えられると、その appstruct と関係付けられた データを編集することができる schema を推測することができます。 この特定の appstruct をフォームにシリアライズするスキーマを定義しましょう。 このアプリケーションには、生成されるフォームに以下のような要件があります:

  • 人の追加と削除がでなければなりません。
  • 人を追加した後で、任意の人の名前あるいは年齢を変更できなければなりません。

これらの要件を満たすスキーマはこのようになります:

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

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    age = colander.SchemaNode(colander.Integer(),
                              validator=colander.Range(0, 200))

class People(colander.SequenceSchema):
    person = Person()

class Schema(colander.MappingSchema):
    people = People()

schema = Schema()

Deform が使用するスキーマは、 Colander という名前のパッケージ から来ています。 Colander のための公式のドキュメンテーションは http://docs.pylonsproject.org/projects/colander/dev/ に存在します。複雑 なスキーマを構成するには、デフォルトの Colander データ型の ドキュメンテーションを読んで慣れておく必要があるでしょう。しかし、今の ところは、それを即興でやる (play it by ear) ことができます。

読んで理解しやすいように、ここでは上記の3つのスキーマを実際に定義 しましたが、最後のステップで schema としてそれらすべてを単一の スキーマ・インスタンスに結合しています。 People スキーマは Person スキーマノードのコレクションです。この定義の結果、 Person は次のように表現されます:

  • name, これは文字列でなければなりません。
  • age, それは整数に逆シリアライズできなければなりません; 逆シリアライズが起こった後で、バリデータはその整数が 0 以上 200 以下 (境界含む) であることを保証します。

スキーマノード・オブジェクト

Note

この節では、読者がフォームに関して学習している間このページから別の ページに移動する必要がないように、スキーマノードに関する Colander ドキュメンテーションを文脈に合わせて一部繰り返します。 しかし、ほとんど同じ情報が http://docs.pylonsproject.org/projects/colander/dev/ でも得ることが できます。

スキーマは、1つまたは複数の スキーマノード ・オブジェクトから構成されます。 それぞれのスキーマノードは、典型的にはクラス colander.SchemaNode のインスタンスで、通常は入れ子状に配置されます。 それぞれのスキーマノード・オブジェクトには、必須の type, (逆シリアライズ 後のデータを調節するための) オプションの preparer, (prepare 済みのデータを 逆シリアライズするための) オプションの validator, オプションの default, オプションの missing, オプションの title, オプションの description, ほとんど必須の (slightly less optional) name があります。 さらに、それは 任意の キーワード引数を受け付けます。それらは属性として ノードインスタンスに直接設定されます。

スキーマノードの type は、そのデータ型を示します (例えば colander.Intcolander.String などです)。

スキーマノードの preparer は、逆シリアライズの後、バリデーションの前 に呼ばれます; それはバリデーションのために逆シリアライズされた値を用意 します。複数 url 値に省略可能のスキーマを追加すること、リッチテキストエディタ から提供される html をフィルターすること、などが例として挙げられるでしょう。 preparer はシリアライズ中には呼ばれません。逆シリアライズ中にのみ呼ばれます。

スキーマノードの validator は逆シリアライズと prepare の後に呼ばれます; それは、値が制約と一致することを確認します。そのようなバリデーションの 例は上記のスキーマの中に示されています: validator=colander.Range(0, 200) 。バリデーションはスキーマノードの シリアライズの後には呼ばれません。ノードの逆シリアライズの後にのみ 呼ばれます。

スキーマノードの default は、シリアライズ中に入力データの中に スキーマノードに対する値が見つからない場合にシリアライズされる値を示します。 それは逆シリアライズ形式で表現されます。

スキーマノードの missing は、逆シリアライズ中に入力データの中に スキーマノードに対する値が見つからない場合に、逆シリアライズされる値を 指定します。それは逆シリアライズ形式で表現されます。スキーマノードが missing 値を持っていない場合、逆シリアライズされたデータ構造が 一致する値を含んでいなければ colander.Invalid 例外が上げられます。

スキーマノードの name は、複数のスキーマノードを互いに関連付けるため に使用されます。さらに、タイトルが提供されない場合にはタイトルとしても 使用されます。

スキーマノードの title は、スキーマノードに関するメタデータです。 それは、スキーマノードに関連するフォームフィールド上部の legend に 表示されます。デフォルトでは、これは name の先頭を大文字にした文字列です。

スキーマノードの description は、スキーマノードに関するメタデータです。 field と関連するフォームコントロールの上にマウスカーソルを 合わせたときにツールチップとして表示されます。デフォルトでは空文字列です。

colander.MappingSchema, colander.TupleSchema, colander.SequenceSchema のクラスレベル属性として定義されている スキーマノードの名前は、そのクラス属性の名前です。例えば:

1
2
3
4
5
6
import colander

class Phone(colander.MappingSchema):
    location = colander.SchemaNode(colander.String(),
                                   validator=colander.OneOf(['home','work']))
    number = colander.SchemaNode(colander.String())

上記のスキーマ中で location = colander.SchemaNode(..) として定義されたスキーマノードの名前は location です。 同じスキーマノードのタイトルは Location です。

スキーマ・オブジェクト

上記の例を注意深く見ていれば、 colander.MappingSchemacolander.SequenceSchema のサブクラスとしてクラスを定義している ことに気がついたかもしれません。それは、 “It’s turtles all the way down” です (訳注: 親亀の上に子亀、孫亀が無数に乗っている様子。 上から下まですべて同じものでできていること): colander.MappingSchema, colander.TupleSchema colander.SequenceSchema オブジェクトのいずれかのインスタンスを 生成した結果も、 colander.SchemaNode オブジェクトになります。

colander.MappingSchema をインスタンス化すると、 typecolander.Mapping を持つスキーマノードが生成されます。

colander.TupleSchema をインスタンス化すると、 typecolander.Tuple を持つスキーマノードが生成されます。

colander.SequenceSchema をインスタンス化すると、 typecolander.Sequence を持つスキーマノードが生成されます。

クラス構文を使わないスキーマの作成 (命令的)

class 構文を使わずにスキーマを作成する方法については、 http://docs.pylonsproject.org/projects/colander/dev/basics.html#defining-a-schema-imperatively を参照してください。

スキーマを作成するのに class 構文を使うか使わないかは、純粋に スタイル上の意思決定です; class 構文を使わずにスキーマを作成した結果は class 構文を使って作成した結果と同じです。

フォームのレンダリング

以前このようなスキーマを定義しました:

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

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    age = colander.SchemaNode(colander.Integer(),
                              validator=colander.Range(0, 200))

class People(colander.SequenceSchema):
    person = Person()

class Schema(colander.MappingSchema):
    people = People()

schema = Schema()

今度はこのスキーマを使ってフォームを生成して、レンダリングと バリデーションをしてみましょう。

フォームオブジェクトの生成

フォームオブジェクトを生成するために、このようにします:

1
2
from deform import Form
myform = Form(schema, buttons=('submit',))

前の節で作成した schema オブジェクト (colander.MappingSchema のインスタンス) を、 deform.Form クラスの最初の位置パラメータとして 使用しました; buttons キーワード引数の値として値 ('submit',) を渡しました。これによって、フォームのレンダリング結果の下部に Submit というラベルの付いた単一の submit 入力要素が差し込まれます。ここで はボタン名を文字列のシーケンスとして渡すようにしましたが、 deform.Button クラスのインスタンスのシーケンスを渡すこともできます。 いずれかが許容されます。

deform.Form の最初の位置引数が mapping オブジェクト (キーを値に写像する構造) を表わすスキーマノードでなければならないことに 注意してください。上記の例では、 (colander.MappingSchema コンストラクタによって得られた) schema オブジェクトを deform.Form コンストラクタに schema 引数として渡すことで この制約を満たしました。

Deform の deform.Form インスタンスによって使用されるスキーマの 中に異なる種類のスキーマノードが存在することは可能ですが、フォーム インスタンスはその schema パラメータの値としてシーケンス、タプルスキーマ、 文字列、整数などを表わすスキーマノードを扱うことができません; マッピングを 表わすスキーマノードだけが可能です。これは、典型的には deform.Form コンストラクタに対して schema 引数として渡された オブジェクトが、 colander.MappingSchema コンストラクタ (あるいは 等価な命令的なコマンド) を使用した結果として得られなければならないことを 意味します。

フォームのレンダリング

フォームオブジェクトを生成したら、 deform.Field.render() メソッド を呼ぶことで、問題なくレンダリングすることができます: deform.Form クラスは deform.Field クラスのサブクラスです。 したがって、このメソッドは deform.Form インスタンスでも利用可能です。

“add” フォーム (初期データのないフォーム) をレンダリングしたいなら、 deform.Field.render() を呼ぶ際に単に appstruct を省略します。

form = myform.render()

既にある既存のデータを持っている場合、フォームを使ってそのデータを編集したいと 思うでしょう (そのフォームは “add フォーム” に対して “edit フォーム” です)。 データはこんな感じになります:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 appstruct = [
     {
         'name':'keith',
         'age':20,
         },
     {
         'name':'fred',
         'age':23,
         },
     ]

これを編集すべきデータとしてシリアライズされたフォームに含めるために、 そのデータを deform.Field.render() メソッドに渡してフォームを レンダリングします:

form = myform.render(appstruct)

もし最後のところで、代わりに同じ appstruct を使用する edit フォームの 「読み取り専用」の異なるバリエーションをレンダリングしたければ、 readonly フラグを True として deform.Field.render() メソッドに渡します。

form = myform.render(appstruct, readonly=True)

これはフォームコントロールのない未処理の (crude) フォームでページを レンダリングします。そのため、このフォームを提示されたユーザは、フォームを 編集できません。

上記のいずれかの文が実行されれば、 form 変数は edit フォームをレンダリング した HTML を含む Unicode オブジェクトになります (これはブラウザに対して ページを返すのに便利です)。レンダリングの root タグは、このフォームを表わす <form> タグ (あるいは少なくとも form タグを含む <div> タグ) になります。したがって、それを使用するアプリケーションは、必要に応じて HTML の <html> および <body> タグでそれをラップする必要があります。 HTML エスケープなしで “structure” として挿入される必要があるでしょう。

レンダリングされたフォームを返す

ここまでで、フォームをレンダリングする HTML が form という名前の 変数として手に入りました。しかし、ブラウザユーザに対して無事にそれを提供 できるようになる前に、 Deform による静的 asset が適切に解決されることを 確かめなければなりません。いくつかの Deform ウィジェット (少なくともサンプル スキーマの中で1つ使われています) は、HTTPによって画像のような静的 asset へのアクセスを要求します。

これらのウィジェットが適切に動作するためには、 deform パッケージ内の static という名前のディレクトリ内のファイルが、フォーム自身を出力する ページと同じホスト名およびポート番号に存在する URL に解決できるように 調整する必要があります。例えば、URL /static/css/form.cssdeform パッケージの static/css ディレクトリ内にある form.css CSS ファイルを text/css コンテンツとして返す必要があり、 同様に /static/scripts/deform.jstext/javascript コンテンツ として返す必要があります。これをどのように行うかは使用しているウェブ フレームワークに依存します。 pyramid の命令的な設定の場合は、 以下のように行われます:

config = Configurator(...)
...
config.add_static_view('static', 'deform:static')
...

他のウェブフレームワークでは、静的ファイルを返すのに異なるメカニズムを 使用するでしょう。

deform パッケージの static ディレクトリにある JavaScript と CSS ファイルと画像の中で、いくつかの重要なファイルは以下の通りです:

static/scripts/jquery-1.4.2.min.js
jQuery javascript ライブラリのローカルコピー。ウィジェットと他の JavaScript ファイルによって使用されます。
static/scripts/deform.js
Deform フォームをレンダリングするすべてのテンプレートでロードしなければ ならない JavaScript ライブラリ。
static/css/form.css
フォーム要素のレンダリングに関係する CSS 。

これらのライブラリそれぞれは、 Deform フォームをレンダリングするページの <head> タグに含まれているはずです。例えば:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<head>
  <title>
    Deform Demo Site
  </title>
  <!-- Meta Tags -->
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <!-- CSS -->
  <link rel="stylesheet" href="/static/css/form.css" type="text/css" />
  <!-- JavaScript -->
  <script type="text/javascript"
          src="/static/scripts/jquery-1.4.2.min.js"></script>
  <script type="text/javascript"
          src="/static/scripts/deform.js"></script>
</head>

deform.Field.get_widget_resources() メソッドは、 static ディレクトリからの相対パスでどのファイルが特定のフォームレンダリングに よって必要とされるかを知るために使用することができます。 その結果、ページのレンダリングに必要なものだけを含めることができます。

JavaScript を使用するウィジェットが適切なイベントと振る舞いのバインディング を行うために、 Deform フォームをレンダリングする HTML ページで JavaScript 関数 deform.load() を (通常はページの最後の方で <script..>deform.load()</script> のようにして script タグの中で) 必ず 呼び出す必要があります。この関数が呼ばれなければ、 JavaScript を 使用する内蔵のウィジェットは正常に機能しません。例えば、レンダリング されたページの body 内の最後の方に、これがインクルードされます:

1
2
3
<script type="text/javascript">
   deform.load()
</script>

上記のように、 head はさらに Content-Type http-equiv に utf-8 charset を指定する <meta> タグを含む必要があります。 これは、ほとんどのシステムのための良識的な設定です。

フォーム送信のバリデーション

フォームが表示され、ユーザがある程度の時間をかけて (chew a bit) 入力を 終えたら、ユーザはやがてフォームを送信するでしょう。フォームが送信された 時に、フォームバリデーションを扱うために使用されるロジックはいくつかの ことをしなければなりません:

  • submit ボタンがクリックされたことを検知する。
  • フォーム POST データから form controls のリストを得る。
  • フォームコントロールのリストと共に deform.Form.validate() メソッド を呼び出す。
  • バリデーションエラーがあった場合、 deform.ValidationFailure 例外を捕捉して、再度フォームをレンダリングする。

例えば、上記のタスクのために WebOb API を使い、以前に作成した form オブジェクトを使うと、そのようなダンスはこんな感じになります:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if 'submit' in request.POST: # detect that the submit button was clicked

    controls = request.POST.items() # get the form controls

    try:
        appstruct = myform.validate(controls)  # call validate
    except ValidationFailure, e: # catch the exception
        return {'form':e.render()} # re-render the form with an exception

    # the form submission succeeded, we have the data
    return {'form':None, 'appstruct':appstruct}

上記の一連の文は Deform を使用するすべてのウェブアプリが行わなければ ならない種類のロジックです。バリデーション段階で失敗しなければ、フォーム からシリアライズされたデータで appstruct という名前の変数が作られ、 アプリケーションの中で使用できるようになります。そうでなければ、フォーム は再度レンダリングされます。

デフォルトでは、あるフォームの送信ボタンがクリックされた時に、その フォームと同じ URL に post リクエストが送られることに注意してください。 これは deform.Form コンストラクタに異なる action を渡す ことで変更できます。

動作中のデモ

この章のスキーマに従う “add フォーム” が実際に動作している状態を見るため、 http://deformdemo.repoze.org/sequence_of_mappings/ を訪れてください。

この章のスキーマに従う “読み取り専用 edit フォーム” が実際に動作している 状態を見るため、 http://deformdemo.repoze.org/readonly_sequence_of_mappings/ を訪れてください。

http://deformdemo.repoze.org にあるアプリケーションは、Deform の ほとんどの特徴をデモする pyramid アプリケーションです。Deform を 使用するアプリケーション内で使用することのできるほとんどのウィジェットと データ型を含んでいます。