2009年12月26日土曜日

Django の ModelForm / ModelFormSet まとめ

ModelForm の扱いが分かってきたのでまとめ。

なぜ ModelForm を使うのか (普通の Form との違い)

普通のフォームはデータベースの構成とは独立しているので自由に入力フォームを作成することができる反面、データベースの構造を反映したフォームを作成する際には、モデルの記述とほぼ同じ内容をもう一度書き下す必要が出てくる。これは冗長であるし、またモデルの変更をフォームに反映しなければならない点で保守性が低い。
きれいに構成された Web アプリケーションであればデータの入出力はデータベース上での生成、変更、削除にほぼ対応しているわけで、そのようなシーンにおいて、モデルを反映した入力を実現するためにモデルから半自動的にフォームを生成するものが ModelForm である。

ModelForm の定義

モデルフォーム内のサブクラス "Meta" の属性として各種の定義を書く。
  • model 属性: フォームのもととなるモデル
  • fields 属性: モデルの中でこのフォームに含めたいフィールド
各々のフィールドがどのようなフォームで表示されるか (テキストなのかチェックボックスなのかプルダウンメニューなのか等々)は、モデルで指定したフィールドのタイプに基づいて適当なものが選択される。
とはいえ実際の運用においては表示の仕方を変えたいことも多々ある(最もよくあるのは hidden にしたい場合など)ので、これは変更することが可能。
これは Meta 内ではなく、モデルフォーム自身の属性として記述。

ModelFormSet

一度に複数のレコードを編集する場合、同じモデルフォームをそのまま複数並べるわけにはいかない。(同じ name を持つ input 要素が複数作成されて、submit されてもどれとどれを組み合わせるのか分からなくなるから。)
FormSet はこの問題を解決するためのもの。ひとまとめに扱う input 要素ごとに id (フォームセットの内部的なもの。HTML の id 属性ではない) を作成してフォームの name 属性を適宜変更、送信されたデータをきちんとグループ分けして一つ一つのフォームを切り分けてくれる。
ModelFormSet は、上述の Form と ModelForm の関係ように、Model を反映した FormSet。

ModelFormSet の定義

modelformset_factory に、ベースにしたいモデルを渡す。
フィールドの表示の種類を変更するためには、意図する表示を定義した ModelForm を予め定義しておき、引数に "form=モデルフォーム名" を追加してやればよい。
ここで、上述の引数として渡したモデルフォームにおいて、fields 属性が定義されていても、新たに作成される ModelFormSet はそれらを無視して全てのフィールドに関してフォームを作成する点に注意。
これを抑制するには、modelformset_factory の 引数に "fields=フィールド名リスト" を与える。

データ受信後の処理

POST でデータを受け取ってからは、送信されたデータをデータベースに書き込むことになる。
ここで、既存のデータを変更する場合には注意すべき点がある。
基本的に、ModelForm のデータを保存する場合はモデルフォームオブジェクトの save メソッドを利用する。
これは新規作成(SQL でいうと INSERT)でも上書き(SQL でいうと UPDATE)でも同じで、Django はそれらを自動的に使い分ける。
使い分けの基準は、"保存しようとしているデータの主キー値と同じ主キー値を持つレコードがあれば UPDATE、それ以外は INSERT"となっている。
よって、既存のデータを UPDATE するためには、必ず主キーをフォームの中に含めなければならない。
また、一部のフィールドのみを用いたモデルフォームの場合、除外したフィールドが必須であった場合、その値を与えなければ is_valid が False となり保存できない。
このような場合は、受信後にフォームインスタンスを作成する際に、POST データとともにもとのデータベースレコードを渡してやることで、フォームに含まれているフィールドはその値を、そうでないフィールドはデータベースの値を用いて、不足するデータを補ったフォームオブジェクトを作成することができる。
渡し方は、モデルフォームの場合は引数 instance、モデルフォームセットの場合は引数 queryset。

0 件のコメント:

コメントを投稿