newforms ライブラリ
| revision-up-to: | 8001 (1.0pre SVN) |
|---|
django.newforms は、 Django に新たに導入された画期的なフォーム処理ライ ブラリです。従来のフォーム/マニピュレータ/バリデータフレームワークである django.forms を置き換えるものです。このドキュメントでは、新たなフォーム ライブラリの使い方について解説します。
移行の計画
django.newforms はリリース 0.96 で新たに採り入れられた機能ですが、もう 「新しい(new)」機能ではなくなったので、将来 django.forms に名前を変更す る予定です。現在の django.forms は Django 1.0 までの間 django.oldforms という名前で利用でき、その後削除するのが適当だと考えて います。
この変更は、現在皆さんが利用しているコードの将来のバージョンに対する互換性 に直接的に関わってきます。以下の移行計画を良く読んで、それに沿ったコードを 書くようにしてください:
従来のフォームフレームワーク (現在の django.forms) は django.oldforms にコピーされています。従って、互換性のない変更を 座して待つことなく、 今すぐ コードの修正に取り掛かれます。修正は、 各アプリケーションのコードを以下のように書き換えるだけです:
from django import forms # 移行前 from django import oldforms as forms # 移行後次の Django リリース (0.97) では、現在の django.newforms を django.forms に移動します。これは互換性のない変更なので、以前の django.forms を使い続けたい人は、上記に従って import 文を変更する 必要があります。
次の Django リリース、すなわち新たな django.forms が導入されたリ リースの 後 、 0.98 か 1.0 のいずれか先に出たバージョンで、 django.oldforms をコードから除去します。
この移行計画を念頭に、 django.newforms を使う場合には以下のような import 文を使うように勧めます:
from django import newforms as forms
こうしておけば、フォームライブラリを forms モジュールとして参照でき、 django.newforms が django.forms になったときに import 文を書き 換えるだけで済みます。
「 import * 」構文を使いたければ以下のようにできます:
from django.newforms import *
上の命令は、全てのフィールド、ウィジェット、フォームクラスと検証のためのユー ティリティをローカルな名前空間に import します。便利と考える人も、美しくな いと考える人もいるでしょう。
概要
django.newforms は、以前の django.forms (「マニピュレータ」) システ ムと同じく、 HTML フォームを表示し、データ処理 (検証) を行って再表示するた めのライブラリです。HTML フォームをサーバ側で検証する場合に使います。
例えば、 Web サイトに連絡用フォームを設置し、訪問者が電子メールでメッセージ を送信できるようにしたいなら、このライブラリを使って HTML フォームフィール ドを表示し、フォームの値を検証できます。このライブラリは、 HTML の <form> タグが必要になるような状況で利用できるわけです。
このライブラリでは、以下のような概念を扱います:
- ウィジェット (Widget) -- <input type="text"> や <textarea> のような、 HTML フォームウィジェットに対応するクラスで す。ウィジェットから HTML へのレンダリングもこのクラスで行われます。
- フィールド (Field) -- データの検証を行うためのクラスです。例えば、 EmailField はデータが有効な電子メールアドレスかどうか検証します。
- フォーム (Form) -- フィールドの集まりで、データの検証や HTML への 表示方法が実装されたものです。
- メディア (Media) -- フォームをレンダするときに必要な CSS や JavaScript リソースの定義です。
このライブラリは、データベースレイヤやビュー、テンプレートといった他の Django コンポーネントに対してカップリングしていません。 newforms ライブラリ が依存しているのは settings と django.utils の二つのヘルパ関数、そして 国際化のためのフック (ただし、このライブラリを使うために国際化の機能を使わ ねばならないわけではありません) だけです。
フォームオブジェクト
newforms ライブラリの第一の用途はフォームオブジェクト (form object) の 作成にあります。フォームオブジェクトは、 django.newforms.Form クラスを サブクラス化して、フォームのフィールドを定義して作成します。フォームのフィー ルド定義は Django のデータベースモデルで親しんでいるあの記述方法で行えます。 この節では、自分のウェブサイトに「連絡フォーム (contact me)」の機能を実装す るためのフォームオブジェクトを逐次開発してゆきます。
まずは ContactForm という名前で Form のサブクラスを作成するところか らはじめましょう:
from django import newforms as forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
フォームは Field オブジェクトの組み合わせでできています。今回の例では、 subject (題名)、 message (メッセージ)、 sender (送信者)、そして cc_myself (自分に CC する)、の 4 つのフィールドをフォームに持たせます。 CharField や EmailField といったフィールド型については、また後で説 明する予定です。
Form のインスタンスを生成する
Form のインスタンスには、何らかのデータの集まりをコンストラクタに渡して 結び付けたもの (束縛 (bound) フォーム) と、そうでないもの (非束縛 (unbound) フォーム) があります。
- データと 結び付いている フォームは、データを検証機能する機能と、 フォームを HTML にレンダリングするときにデータを HTML 形式で表示する 機能をあわせ持っています。
- データと 結び付いていない フォームには検証機能はありません (検証 すべきデータがないから当然ですね!) が、空のフォームを HTML としてレン ダリングする機能は備えています。
非束縛フォームのインスタンスを生成するには、単にフォームクラスのインスタン ス化を行います:
>>> f = ContactForm()
フォームにデータを結び付けるには、データの入った辞書をフォームクラスのコン ストラクタの第一引数に渡します:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
この辞書の中では、キーは各フィールドの名前であり、フォームクラスの各属性に 対応しています。値は検証すべきデータです。通常、値は文字列にしますが、必ず しも文字列でなくてかまいません。値にどんなデータ型を指定指定できるかは、フィー ルドの型に依存します。
実行時に束縛フォームと非束縛フォームを区別したければ、フォームの is_bound 属性を調べてください:
>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True
空の辞書を渡すと、空のデータの入った 束縛フォーム を返します:
>>> f = ContactForm({})
>>> f.is_bound
True
束縛フォームのインスタンスに入っているデータに何らかの変更を加えたい場合や、 束縛フォームを変換して、何らかのデータの入った非束縛フォームにしたい場合に は、新たにフォームインスタンスを生成してください。フォームインスタンス内の データを変更する方法はありません。一度フォームインスタンスを生成したら、デー タの有無に関わらず、インスタンス内のデータは変更不能だと考えてください。
フォームを使ってデータを検証する
フォームオブジェクトの主要な役割はデータの検証です。束縛フォームのインスタ ンスに対して is_valid() メソッドを呼び出すと、データの検証を行って、そ の結果をブール値で返します:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
無効なデータを入れてみましょう。例えば、 subject を空にしてみます (フィールドは全てデフォルトで必須なためエラーになります)。また、 sender に不正なメールアドレス情報を入れてみます:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid e-mail address',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
errors という属性にアクセスすると、エラーメッセージの入った辞書を参照で きます:
>>> f.errors
{'sender': [u'Enter a valid e-mail address.'], 'subject': [u'This field is required.']}
この辞書は、フィールド名がキーに、エラーメッセージを表す Unicode 文字列のリ ストが値になっています。エラーメッセージがリストになっているのは、一つのフィー ルドに対して複数のエラーが存在し得るからです。
is_valid() を呼ばなくても errors にはアクセスできます。 is_valid() を呼び出すか、 errors にアクセスすると、フォームのデータ は自動的に検証されます。
errors や is_valid() に何度アクセスしても、検証のルーチンはたった 一度しか呼ばれません。別の見方をすれば、検証の処理には副作用があり、その副 作用はたった一度しか呼び出されないということです。
cleaned_data には、 Form 内で定義されている 全ての フィールドのキー と値が入ります。フォームに渡したデータに、必須でないフィールドの値が入って いない場合でもです。下の例では、データ辞書には nick_name フィールドの値 が入っていませんが、 cleaned_data には空の値が入っています:
>>> class OptionalPersonForm(Form):
... first_name = CharField()
... last_name = CharField()
... nick_name = CharField(required=False)
>>> data = {'first_name': u'John', 'last_name': u'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'}
この例で、 cleaned_data の中の nick_name は空文字列なのは、 nick_name が CharField であり、 CharField は空の値を空文字列と みなすからです。各フィールドタイプは「空の」値が設定されています。例えば、 DateField の場合、空の値は空文字列ではなく None になります。 空の値を持ったフィールドの詳しい挙動は、「組み込みフィールドクラス」の節の 各フィールドの説明中の「空のフォームデータに対する値」の項目を参照してくだ さい。
個別のフォームフィールド (フィールド名ごと) やフォーム全体 (複数フィールド の組み合わせ) に対してバリデーションを実現するコードを書けます。詳しくは、 後述の フォームやフィールドのバリデーションコードを自作する の節を参照し てください。
非束縛フォームの動作
データを含まないフォームに対して "cleaned" を実行しても無意味でしかありませ んが、参考までに非束縛フォームに対して行ったときの動作を示しておきます:
>>> f = ContactForm()
>>> f.is_valid()
False
>>> f.errors
{}
「クリーニング済み」のデータにアクセスする
フォームクラスの各フィールドには、データの検証だけでなく、「クリーニング」 を行う役割もあります。データのクリーニングとは、データを一貫性のある書式に 正規化することです。データのクリーニングはとても素晴らしい機能で、クリーニ ングを行うと、ユーザがフィールドに色々な形式でデータを入力しても、常に一貫 性を持った出力を得られます。
例えば、 DateField はデータを Python の datetime.date オブジェクト に正規化します。フィールドの値は、 '1994-07-15' のような形式の文字列で も、 datetime.date オブジェクトでも、その他の形式でも、 DateField は有効なデータであるかぎり、常に出力を datetime.date オブジェクトで正規 化します。
データセットの入ったフォームインスタンスを生成して検証を行うと、 cleaned_data 属性を介してクリーニング済みのデータにアクセスできるようにな ります:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
Note
開発版で新たに登場した機能 cleaned_data 属性は、以前のリリース では clean_data と呼ばれていました。
CharField や EmailField のようなテキストベースのフィールドは、常に 入力を Unicode 文字列に変換します。エンコーディングに関する解説は、このドキュ メントの後の方でカバーする予定です。
データが まだ検証されていない 場合、フォームインスタンスには cleaned_data 属性がありません:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid e-mail address',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
Traceback (most recent call last):
...
AttributeError: 'ContactForm' object has no attribute 'cleaned_data'
フォームを生成するときに追加の値を渡した場合でも、 cleaned_data の中に 入るキーは、フォーム内で定義されているフィールド だけ です。以下の例でも、 ContactForm のコンストラクタに追加のフィールドデータを渡していますが、 cleaned_data が返すのは ContactForm で定義されているフィールドだけで す:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True,
... 'extra_field_1': 'foo',
... 'extra_field_2': 'bar',
... 'extra_field_3': 'baz'}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
非束縛フォームの動作
データの入っていないフォームをクリーニングしても無意味ですが、参考までに非 束縛フォームでの挙動を示します:
>>> f = ContactForm() >>> f.cleaned_data Traceback (most recent call last): ... AttributeError: 'ContactForm' object has no attribute 'cleaned_data'
ビューの実装例
これまでに紹介した内容をまとめる意味で、簡単な連絡フォームのビューメソッド の例を以下に示します:
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django import newforms as forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
cc_myself = forms.BooleanField()
def contact(request):
if request.POST:
f = ContactForm(request.POST)
if f.is_valid:
# ... do something with f.cleaned_data
return HttpResponseRedirect('/url/on/success/')
else:
f = ContactForm()
return render_to_response('contact.html', {'form': f})
フォームを HTML として出力する
フォームオブジェクトの二つ目の仕事は、フォームの HTML へのレンダリングです。 フォームを HTML として出力するには、フォームをインスタンス化して、 print で出力します:
>>> f = ContactForm() >>> print f <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr> <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr> <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
束縛フォームの場合、フォームのデータは適切な形で HTML 出力されます。例えば、 フィールドが <input type="text"> で表される場合、データは value 属 性の中に出力されます。フィールドが <input type="checkbox"> であれば、 必要に応じて checked="checked" が入ります:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> print f
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" value="hello" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" value="Hi there" /></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" value="foo@example.com" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" checked="checked" /></td></tr>
デフォルトの出力は 2 カラムの HTML テーブルになり、各フィールドが一つの <tr> タグの中に収まります。以下の点に注意してください:
- 柔軟性をもたせるために、出力中には <table> と </table> タグが 入っていません。また、 <form> と </form> や、 <input type="submit"> もありません。これらのタグは自分で入れる必 要があります。
- 各フィールドタイプには、それぞれデフォルトの HTML 表現があります。 CharField や EmailField は <input type="text"> で表され、 BooleanField は <input type="checkbox"> になります。とはいえ、 これらは便利なデフォルト値にすぎません。ウィジェット (widget) を使え ば、フィールドの表現にどのような HTML を使うかを指定できます。これに ついては後で説明する予定です。
- 各タグの name 属性は ContactForm クラスの属性名から直接取り出 して使われます。
- 'Subject:', 'Message:', 'Cc myself:' といった各フィール ドのテキストラベルは、フィールド名のアンダースコアを全てスペースに変 換し、先頭の文字を大文字にして生成します。これもまたデフォルト値にす ぎず、手動でもラベルを設定できるようになっています。
- 各テキストラベルは HTML の <label> タグで囲われています。このタグ には for 属性が付いていて、対応するフォームフィールドの id 属 性に対応しています。属性の値はフィールド名の前に 'id_' を付けたも のになります。 id 属性や <label> タグはフォーム生成の定石に従っ て組み込まれているものですが、この振舞は自分で変更できます。
テーブル組みによる出力は print した時に出力されるデフォルトで、他にもい くつか出力スタイルがあります。各スタイルはフォームオブジェクトのメソッドと して利用でき、各々のレンダリングメソッドは Unicode オブジェクトを返すように なっています。
as_p()
Form.as_p() はフォームを一連の <p> タグの集まりで組みます。各 <p> タグの中に一つのフィールドが入ります:
>>> f = ContactForm() >>> f.as_p() u'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>' >>> print f.as_p() <p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p> <p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p> <p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p> <p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
form をコンテキストに入れていれば、テンプレート上では以下のようにして出 力できます:
{{ f.as_p }}
as_ul()
Form.as_ul() はフォームを一連の <li> タグで組みます。各 <li> タ グの中に一つのフィールドが入ります。 as_ul() は <ul> や </ul> を出力に 含めません 。これは、ユーザが <ul> タグの HTML 属性を好きに 指定できるようにするためです:
>>> f = ContactForm() >>> f.as_ul() u'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>\n<li><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>' >>> print f.as_ul() <li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li> <li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li> <li><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></li> <li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>
form をコンテキストに入れていれば、テンプレート上では以下のようにして出 力できます:
{{ f.as_ul }}
as_table()
最後に、 Form.as_table() はフォームを <table> で組みます。これは print で出力したときに使われる形式と同じです。実際、フォームオブジェク トを print すると、背後では as_table() が呼び出されるようになってい ます:
>>> f = ContactForm() >>> f.as_table() u'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>' >>> print f.as_table() <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr> <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr> <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
form をコンテキストに入れていれば、テンプレート上では以下のようにして出 力できます:
{{ f.as_table }}
デフォルトの設定はテーブル出力なので、以下のようにしても出力できます:
{{ f }}
<label> タグの出力設定
<label> タグは、あるラベルテキストがどのフォーム要素に対応づけられてい るかを知らせるタグです。 <label> タグがあると、フォームの利便性が増し、 入力補助デバイスで操作しやすくなります。 <label> タグは常に使うようにし ておくよう勧めます。
デフォルトでは、フォームのレンダリングメソッドを呼び出すと、各フォーム要素 に id 属性が追加され、ラベルを <label> タグで囲って出力します。 id 属性の値はフォームのフィールド名の前に id_ を付けたものになりま す。とはいえ、 id 属性の命名規則を変えたり、そもそも <label> を出力 したくない人のために、この仕様は設定変更できるようになっています。
<label> タグや id の挙動を変更するには、 Form コンストラクタの auto_id 引数を使います。この引数は True 、 False 、文字列のいず れかで指定せねばなりません。
auto_id を False にすると、フォーム出力に <label> タグや id 属性が含まれなくなります:
>>> f = ContactForm(auto_id=False) >>> print f.as_table() <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" /></td></tr> <tr><th>Message:</th><td><input type="text" name="message" /></td></tr> <tr><th>Sender:</th><td><input type="text" name="sender" /></td></tr> <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr> >>> print f.as_ul() <li>Subject: <input type="text" name="subject" maxlength="100" /></li> <li>Message: <input type="text" name="message" /></li> <li>Sender: <input type="text" name="sender" /></li> <li>Cc myself: <input type="checkbox" name="cc_myself" /></li> >>> print f.as_p() <p>Subject: <input type="text" name="subject" maxlength="100" /></p> <p>Message: <input type="text" name="message" /></p> <p>Sender: <input type="text" name="sender" /></p> <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
auto_id が True の場合、フォームの出力には <label> タグが入り、 各フォームフィールドの id 属性の値にはフィールド名をそのまま使います:
>>> f = ContactForm(auto_id=True) >>> print f.as_table() <tr><th><label for="subject">Subject:</label></th><td><input id="subject" type="text" name="subject" maxlength="100" /></td></tr> <tr><th><label for="message">Message:</label></th><td><input type="text" name="message" id="message" /></td></tr> <tr><th><label for="sender">Sender:</label></th><td><input type="text" name="sender" id="sender" /></td></tr> <tr><th><label for="cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="cc_myself" /></td></tr> >>> print f.as_ul() <li><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></li> <li><label for="message">Message:</label> <input type="text" name="message" id="message" /></li> <li><label for="sender">Sender:</label> <input type="text" name="sender" id="sender" /></li> <li><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></li> >>> print f.as_p() <p><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></p> <p><label for="message">Message:</label> <input type="text" name="message" id="message" /></p> <p><label for="sender">Sender:</label> <input type="text" name="sender" id="sender" /></p> <p><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></p>
auto_id がフォーマット文字 '%s' を含む文字列になっている場合、フォー ム出力は <label> タグを含むようになり、タグの id 属性はフォーマット 文字列に従って生成されます。例えば、フォーマット文字列が field_%s の場 合、 subject という名前のフィールドの id は 'field_subject' に なります。出力例は以下のようになります:
>>> f = ContactForm(auto_id='id_for_%s') >>> print f.as_table() <tr><th><label for="id_for_subject">Subject:</label></th><td><input id="id_for_subject" type="text" name="subject" maxlength="100" /></td></tr> <tr><th><label for="id_for_message">Message:</label></th><td><input type="text" name="message" id="id_for_message" /></td></tr> <tr><th><label for="id_for_sender">Sender:</label></th><td><input type="text" name="sender" id="id_for_sender" /></td></tr> <tr><th><label for="id_for_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></td></tr> >>> print f.as_ul() <li><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li> <li><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></li> <li><label for="id_for_sender">Sender:</label> <input type="text" name="sender" id="id_for_sender" /></li> <li><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li> >>> print f.as_p() <p><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></p> <p><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></p> <p><label for="id_for_sender">Sender:</label> <input type="text" name="sender" id="id_for_sender" /></p> <p><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></p>
auto_id がこれ以外の偽でない値、つまり %s を含まない文字列のような 値の場合、 auto_id は True に設定されたものとみなされます。
デフォルトでは、 auto_id は 'id_%s' に設定されています。
通常、フォームのレンダ時には、ラベル名の直後にコロン (:) が付加されます。 このコロンを他の文字に変更したり、文字を出力しないようにするには、 label_suffix パラメタを使います:
>>> f = ContactForm(auto_id='id_for_%s', label_suffix='') >>> print f.as_ul() <li><label for="id_for_subject">Subject</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li> <li><label for="id_for_message">Message</label> <input type="text" name="message" id="id_for_message" /></li> <li><label for="id_for_sender">Sender</label> <input type="text" name="sender" id="id_for_sender" /></li> <li><label for="id_for_cc_myself">Cc myself</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li> >>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->') >>> print f.as_ul() <li><label for="id_for_subject">Subject -></label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li> <li><label for="id_for_message">Message -></label> <input type="text" name="message" id="id_for_message" /></li> <li><label for="id_for_sender">Sender -></label> <input type="text" name="sender" id="id_for_sender" /></li> <li><label for="id_for_cc_myself">Cc myself -></label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
ラベルの付加は、ラベルの最後の文字が区切り文字 (., !, ?, : のいずれか) でない場合にのみ行われます。
フィールドの並び順について
as_p() や as_ul(), as_table() ショートカットを使うと、各フィー ルドは Form クラス内で定義された順に出力されます。例えば、上の ContactForm の例では、フィールドの並び順は subject, message, sender, cc_myself になります。 HTML 出力の中でフィールドの並び順を 変更したければ、クラス定義内でのフィールドの並び順を変更してください。
エラーの出力方法
束縛フォームオブジェクトをレンダすると、フォームの検証がまだであればレンダ リング操作の中で自動的に検証が行われ、エラーがあれば HTML 出力中の該当フィー ルドの付近に <ul class=errorlist> でエラー内容が表示されます。エラーメッ セージ中の具体的なエラー表示位置は、どのメソッドでフォームをレンダしている かによります:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid e-mail address',
... 'cc_myself': True}
>>> f = ContactForm(data, auto_id=False)
>>> print f.as_table()
<tr><th>Subject:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" /></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" value="Hi there" /></td></tr>
<tr><th>Sender:</th><td><ul class="errorlist"><li>Enter a valid e-mail address.</li></ul><input type="text" name="sender" value="invalid e-mail address" /></td></tr>
<tr><th>Cc myself:</th><td><input checked="checked" type="checkbox" name="cc_myself" /></td></tr>
>>> print f.as_ul()
<li><ul class="errorlist"><li>This field is required.</li></ul>Subject: <input type="text" name="subject" maxlength="100" /></li>
<li>Message: <input type="text" name="message" value="Hi there" /></li>
<li><ul class="errorlist"><li>Enter a valid e-mail address.</li></ul>Sender: <input type="text" name="sender" value="invalid e-mail address" /></li>
<li>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></li>
>>> print f.as_p()
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
<p>Subject: <input type="text" name="subject" maxlength="100" /></p>
<p>Message: <input type="text" name="message" value="Hi there" /></p>
<p><ul class="errorlist"><li>Enter a valid e-mail address.</li></ul></p>
<p>Sender: <input type="text" name="sender" value="invalid e-mail address" /></p>
<p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>
エラーリストの出力形式をカスタマイズする
デフォルトでは、バリデーション時エラーの出力内容は、 django.newforms.util.ErrorList を使ってフォーマットされます。エラーの表 示に他のクラスを使いたければ、以下のようにフォームの生成時に指定します:
>>> from django.newforms.util import ErrorList >>> class DivErrorList(ErrorList): ... def __unicode__(self): ... return self.as_divs() ... def as_divs(self): ... if not self: return u'' ... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % e for e in self]) >>> f = ContactForm(data, auto_id=False, error_class=DivErrorList) >>> f.as_p() <div class="errorlist"><div class="error">This field is required.</div></div> <p>Subject: <input type="text" name="subject" maxlength="100" /></p> <p>Message: <input type="text" name="message" value="Hi there" /></p> <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div> <p>Sender: <input type="text" name="sender" value="invalid e-mail address" /></p> <p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>
より細かな出力調整
as_p() や as_ul(), as_table() といったメソッドは、単に面倒臭が りの開発者むけに用意されているショートカットでしかなく、他のやり方でもフォー ムを表示できます。
フォーム中のあるフィールドの HTML を表示するには、フォームを辞書のように扱 い、フィールドの名前をキーにして参照し、その値を出力します:
>>> f = ContactForm() >>> print f['subject'] <input id="id_subject" type="text" name="subject" maxlength="100" /> >>> print f['message'] <input type="text" name="message" id="id_message" /> >>> print f['sender'] <input type="text" name="sender" id="id_sender" /> >>> print f['cc_myself'] <input type="checkbox" name="cc_myself" id="id_cc_myself" />
フィールドを引数にして str() や unicode() を呼び出すと、レンダ結果 の HTML をそれぞれ string 型や Unicode 型のオブジェクトで返します:
>>> str(f['subject']) '<input id="id_subject" type="text" name="subject" maxlength="100" />' >>> unicode(f['subject']) u'<input id="id_subject" type="text" name="subject" maxlength="100" />'
フィールド固有の出力を行った場合でも、フォームオブジェクトの auto_id 設 定は有効です:
>>> f = ContactForm(auto_id=False) >>> print f['message'] <input type="text" name="message" /> >>> f = ContactForm(auto_id='id_%s') >>> print f['message'] <input type="text" name="message" id="id_message" />
あるフィールドに関するエラーのリストを取得するには、フィールドの errors 属性にアクセスします。このフィールドはリストライクなオブジェクトで、 HTML として出力すると <ul class="errorlist"> のリストになります:
>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
>>> f = ContactForm(data, auto_id=False)
>>> print f['message']
<input type="text" name="message" />
>>> f['message'].errors
[u'This field is required.']
>>> print f['message'].errors
<ul class="errorlist"><li>This field is required.</li></ul>
>>> f['subject'].errors
[]
>>> print f['subject'].errors
>>> str(f['subject'].errors)
''
ビューやテンプレートでフォームを使う
では、先ほどの連絡フォームを Django のビューとテンプレートに組み込みましょ う。
簡単なビューの例
以下のビューは、デフォルトでは連絡フォームを表示し、 POST リクエストで アクセスすると、入力値の検証と処理を行います:
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Do form processing here...
return HttpResponseRedirect('/url/on_success/')
else:
form = ContactForm()
return render_to_response('contact.html', {'form': form})
簡単なテンプレートの例
上の例で使っているテンプレート、 contact.html は、フォームを HTML で出 力する役割を担います。フォームの出力には、前述の フォームを HTML として出力する で述べたテク ニックを使えます。
フォームを HTML 出力するもっとも簡単な方法は、以下のようにフォーム変数その ものを使うというものです:
<form method="post" action="">
<table>{{ form }}</table>
<input type="submit" />
</form>
上のテンプレートコードを使うと、フォームを form.as_table() メソッドによっ てHTML テーブルにして表示します。テーブルが表示されるのは、テンプレートシス テムがオブジェクトの __str__() 値を表示するようになっていて、 Form クラスの __str__() メソッドが自身の as_table() メソッドを呼び出すか らです。
以下のコードも全く同じ出力を生成しますが、こちらの方がより明示的です:
<form method="post" action="">
<table>{{ form.as_table }}</table>
<input type="submit" />
</form>
もちろん、 form.as_ul や form.as_p も使えます。
上の二つの例では、 <form>, <table>, <input type="submit" />, </table> および </form> といったタグが入っていることに注意してくだ さい。フォームの便宜メソッド (as_table(), as_ul() および as_p()) は、これらの HTML タグを出力しません。
複雑なテンプレート出力
これまで何度か強調してきたように、 as_table(), as_ul(), as_p() といったメソッドは、よく使う例に対するショートカットにすぎません。個々のフィー ルドを操作すれば、完全にテンプレートを制御してフォームの設計を行えます。
もっとも簡単な方法は、 {% for field in form %} を使って、フォームのフィー ルドにわたって反復処理を行うというものです。例えば:
<form method="post" action="">
<dl>
{% for field in form %}
<dt>{{ field.label_tag }}</dt>
<dd>{{ field }}</dd>
{% if field.help_text %}<dd>{{ field.help_text }}</dd>{% endif %}
{% if field.errors %}<dd class="myerrors">{{ field.errors }}</dd>{% endif %}
{% endfor %}
</dl>
<input type="submit" />
</form>
このような反復処理のテクニックは、各フィールドに対して同じ HTML のフォーマッ トを適用したい場合や、あらかじめフォームフィールドの名前が分からない状況で フォームを生成したい場合などに役立ちます。フィールドは Form クラスで定 義されている順番に反復処理されます。
反復処理を使う代わりに、フォームのフィールドを名前で明示した調整も行えます。 フォームフィールドを明示的に指定するには、 {{ form.fieldname }} のよう にします。 fieldname はフィールドの名前です。 例を示します:
<form method="post" action="">
<ul class="myformclass">
<li>{{ form.sender.label_tag }} {{ form.sender }}</li>
<li class="helptext">{{ form.sender.help_text }}</li>
{% if form.sender.errors %}<ul class="errorlist">{{ form.sender.errors }}</ul>{% endif %}
<li>{{ form.subject.label_tag }} {{ form.subject }}</li>
<li class="helptext">{{ form.subject.help_text }}</li>
{% if form.subject.errors %}<ul class="errorlist">{{ form.subject.errors }}</ul>{% endif %}
...
</ul>
</form>
必須のフィールドをテンプレート中でハイライト表示する
ユーザに示必須のフィールドを示すという処理は、よく行われます。以下では、上 の例を変更して、必須のフィールドの後ろにアスタリスクを挿入しています:
<form method="post" action="">
<dl>
{% for field in form %}
<dt>{{ field.label_tag }}{% if field.field.required %}*{% endif %}</dt>
<dd>{{ field }}</dd>
{% if field.help_text %}<dd>{{ field.help_text }}</dd>{% endif %}
{% if field.errors %}<dd class="myerrors">{{ field.errors }}</dd>{% endif %}
{% endfor %}
</dl>
<input type="submit" />
</form>
{% if field.field.required %}*{% endif %} という部分が新たに追加されて いる部分です。このコードは、フィールドが必須である場合にのみ、アスタリスク を追加します。
Django は field.required ではなく、 field.field.required をチェック しているので注意してください。テンプレート上では、 field は newforms.forms.BoundField のインスタンスであり、実際のフィールドインス タンスは field 属性の中に保持しています。
アップロードされたファイルをフォームに結びつける
開発版の Django で新たに追加された機能です
FileField や ImageField といったフィールドの入ったフォームの扱いは、 通常のフォームより少しだけ複雑です。
まず、フォームからファイルをアップロードさせるには、 <form> エレメント で enctype が "multipart/form-data" に設定されていなければなりませ ん:
<form enctype="multipart/form-data" method="post" action="/foo/">
次に、フォームを使う場合、ファイルデータをフォームに結びつけなければ なりません。ファイルデータは通常のフォームデータとは分けて扱われるので、 フォームに FileField や ImageField が入っている場合、束縛フォームを 作るには、第2引数にファイルデータを渡さねばなりません。ContactForm に mugshot という名前の ImageField を組み込んだ場合、下記のようにして、 顔写真 (mugshot) のファイルデータをフォームに結びつけます:
# 画像ファイルフィールドつきの束縛フォーム
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
>>> f = ContactFormWithMugshot(data, file_data)
実践的には、 request.FILES をファイルデータのソースとして指定することに なるでしょう (request.POST をフォームデータのソースにするのと同様です):
# Bound form with an image field, data from the request >>> f = ContactFormWithMugshot(request.POST, request.FILES)
非束縛フォームの構築は通常通りで、フォームデータとファイルデータの 両方 を省略します:
# Unbound form with a image field >>> f = ContactFormWithMugshot()
マルチパート形式のフォームをテストする
再利用可能なビューやテンプレートを書いているのなら、フォームがマルチパー ト形式であるかどうか前もって分からない場合もあるでしょう。 is_multipart() メソッドを使うと、フォームがマルチパート形式でエンコード されたデータの提出を要求しているかどうかを調べられます:
>>> f = ContactFormWithMugshot() >>> f.is_multipart() True
テンプレートの中では、以下のようにして使います:
{% if form.is_multipart %}
<form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
<form method="post" action="/foo/">
{% endif %}
{% form %}
</form>
フォームのサブクラス化
同じフィールドを持つようなフォームクラスをいくつも作りたい場合、サブクラス 化を用いると冗長性を排除できます。
フォームクラスをサブクラス化すると、できたフォームクラスには親クラスの全て のフィールドが入っています。サブクラスで定義したフィールドは親クラスのフィー ルドの後に続きます。
以下の例では、 ContactFormWithPriority には ContactForm の全てのフィー ルドと、 priority という追加のフィールドが入っています。 ContactForm のフィールドは先に表示されます:
>>> class ContactFormWithPriority(ContactForm): ... priority = forms.CharField() >>> f = ContactFormWithPriority(auto_id=False) >>> print f.as_ul() <li>Subject: <input type="text" name="subject" maxlength="100" /></li> <li>Message: <input type="text" name="message" /></li> <li>Sender: <input type="text" name="sender" /></li> <li>Cc myself: <input type="checkbox" name="cc_myself" /></li> <li>Priority: <input type="text" name="priority" /></li>
複数のフォームを親クラスにしたサブクラス化も可能です。この場合、親クラスの フォームは「混ぜ込み (mix-in)」クラスのように扱われます。以下の例では、 BeatleForm が PersonForm と InstrumentForm を (この順番で) サブクラス化しています。フィールドのリストには、親クラスのフィールドが順番 に表示されます:
>>> class PersonForm(Form): ... first_name = CharField() ... last_name = CharField() >>> class InstrumentForm(Form): ... instrument = CharField() >>> class BeatleForm(PersonForm, InstrumentForm): ... haircut_type = CharField() >>> b = BeatleForm(auto_id=False) >>> print b.as_ul() <li>First name: <input type="text" name="first_name" /></li> <li>Last name: <input type="text" name="last_name" /></li> <li>Instrument: <input type="text" name="instrument" /></li> <li>Haircut type: <input type="text" name="haircut_type" /></li>
フォームのプレフィクス
Django のフォームは、一つの <form> タグの中に複数入れられます。 各々のフォームに独自の名前空間を持たせるには、 prefix キーワード引数を 使います:
>>> mother = PersonForm(prefix="mother") >>> father = PersonForm(prefix="father") >>> print mother.as_ul() <li><label for="id_mother-first_name">First name:</label> <input type="text" name="mother-first_name" id="id_mother-first_name" /></li> <li><label for="id_mother-last_name">Last name:</label> <input type="text" name="mother-last_name" id="id_mother-last_name" /></li> >>> print father.as_ul() <li><label for="id_father-first_name">First name:</label> <input type="text" name="father-first_name" id="id_father-first_name" /></li> <li><label for="id_father-last_name">Last name:</label> <input type="text" name="father-last_name" id="id_father-last_name" /></li>
フィールド
フォームクラスの作成で一番重要なのは、フォームの各フィールドの定義です。各 フィールドは固有のデータ検証ロジックと、いくつかのフックが備わっています。
フィールドクラスの主な用途はフォームクラスにおけるフィールド定義ですが、フィー ルドクラスは直接インスタンス化して使えるので、フィールドの動作を理解する役 に立つはずです。各フィールドインスタンスは clean() メソッドを備えており、 単一の引数を取って検証を行い、その結果に応じて django.newforms.ValidationError を送出するか、クリーニング済みの値を返 します:
>>> f = forms.EmailField()
>>> f.clean('foo@example.com')
u'foo@example.com'
>>> f.clean(u'foo@example.com')
u'foo@example.com'
>>> f.clean('invalid e-mail address')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid e-mail address.']
Django の古いフォーム/検証フレームワークを使ったことがある人は、この VaridationError が以前の VaridationError とは別の例外であることに注 意してください。以前の例外は django.core.validators.ValidationError で すが、新たな例外は django.newforms.ValidationError です。
フィールドの主な引数
各フィールドクラスのコンストラクタは、少なくとも以下に示す引数を取ります。 フィールドクラスによっては他にもフィールド固有の引数をとりますが。ここに示 す引数はどのフィールドクラスでも 常に 指定できる引数です:
required
デフォルトでは、フィールドクラスはフィールドが必須 (reqired) であると仮定し ています。従って、フィールドに空の値、すなわち None や空文字列 ("") を渡すと、 clean() は VaridationError 例外を送出します:
>>> f = forms.CharField()
>>> f.clean('foo')
u'foo'
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(' ')
u' '
>>> f.clean(0)
u'0'
>>> f.clean(True)
u'True'
>>> f.clean(False)
u'False'
必須で ない フィールドにするには、フィールドのコンストラクタに required=False を指定します:
>>> f = forms.CharField(required=False)
>>> f.clean('foo')
u'foo'
>>> f.clean('')
u''
>>> f.clean(None)
u''
>>> f.clean(0)
u'0'
>>> f.clean(True)
u'True'
>>> f.clean(False)
u'False'
required=False のフィールドの clean() に空の値を渡して呼び出すと、 clean() は VaridationError を送出する代わりに 正規化された 空の値 を返します。例えば CharField の場合なら、 Unicode の空文字列になります。 その他のフィールドクラスでは None になるはずです (フィールドによって異 なります)。
label
label 引数を使うと、フィールドに「人間に優しい」ラベルを指定できます。 このラベルはフォーム内でフィールドを表示するときに使われます。
上の フォームを HTML として出力する で説明し たように、フィールドのデフォルトのラベルはフィールド名のアンダースコアを除 去して、頭文字を大文字にしたものです。デフォルトのラベル命名規則が期待通り のラベルを出力しない場合には、この引数を指定してください。
label を指定したフォームの例を以下に示します。出力を短くするために auto_id=False にしています:
>>> class CommentForm(forms.Form): ... name = forms.CharField(label='Your name') ... url = forms.URLField(label='Your Web site', required=False) ... comment = forms.CharField() >>> f = CommentForm(auto_id=False) >>> print f <tr><th>Your name:</th><td><input type="text" name="name" /></td></tr> <tr><th>Your Web site:</th><td><input type="text" name="url" /></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
initial
initial 引数を使うと、非束縛フォーム内でフィールドをレンダするときの初 期値を指定できます。
この引数を使うケースは、例えば以下のように、「空の」フォームの各フィールド を特定の値で初期化して表示したい場合です:
>>> class CommentForm(forms.Form): ... name = forms.CharField(initial='Your name') ... url = forms.URLField(initial='http://') ... comment = forms.CharField() >>> f = CommentForm(auto_id=False) >>> print f <tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr> <tr><th>Url:</th><td><input type="text" name="url" value="http://" /></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
こんなことをしなくても、フォームに初期データの入った辞書を渡せばいいのにと 思うかもしれませんね。しかし、フォームにデータを渡して束縛フォームにすると、 データを表示する際に検証がトリガされてしまい、 HTML 出力にバリデーションエ ラーが入ってしまいます:
>>> class CommentForm(forms.Form):
... name = forms.CharField()
... url = forms.URLField()
... comment = forms.CharField()
>>> default_data = {'name': 'Your name', 'url': 'http://'}
>>> f = CommentForm(default_data, auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
<tr><th>Url:</th><td><ul class="errorlist"><li>Enter a valid URL.</li></ul><input type="text" name="url" value="http://" /></td></tr>
<tr><th>Comment:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="comment" /></td></tr>
このため、非束縛フォームの場合に限って initial に指定した値が出力される ようになっているのです。束縛フォームの場合、出力は常に束縛済みのデータにな ります。
また、 initial の指定値は、フィールドの値が指定されなかった場合の 「フォールバック用の」値には ならない ので注意が必要です。 initial の値は初期値の入ったフォームの表示 だけ に用いられます:
>>> class CommentForm(forms.Form):
... name = forms.CharField(initial='Your name')
... url = forms.URLField(initial='http://')
... comment = forms.CharField()
>>> data = {'name': '', 'url': '', 'comment': 'Foo'}
>>> f = CommentForm(data)
>>> f.is_valid()
False
# フォームの値は初期値にフォールバック *しません。*
>>> f.errors
{'url': [u'This field is required.'], 'name': [u'This field is required.']}
widget
widget 引数を使うと、フィールドをレンダリングするときの Widget クラ スを指定できます。詳しくは後述の ウィジェット を参照してください。
help_text
help_text 引数を使うと、フィールドに説明文をつけられます。 help_text を指定した場合、フォームを (as_ul() のような) フォームメ ソッドでレンダした時に、該当フィールドの隣に表示されます。
以下に、 help_text を使ったフォームの例を示します。この例では、フォーム の二つのフィールドに help_text を指定しています。出力を単純にするために、 auto_id=False を指定しています:
>>> class HelpTextContactForm(forms.Form): ... subject = forms.CharField(max_length=100, help_text='100 characters max.') ... message = forms.CharField() ... sender = forms.EmailField(help_text='A valid e-mail address, please.') ... cc_myself = forms.BooleanField(required=False) >>> f = HelpTextContactForm(auto_id=False) >>> print f.as_table() <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" /><br />100 characters max.</td></tr> <tr><th>Message:</th><td><input type="text" name="message" /></td></tr> <tr><th>Sender:</th><td><input type="text" name="sender" /><br />A valid e-mail address, please.</td></tr> <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr> >>> print f.as_ul() <li>Subject: <input type="text" name="subject" maxlength="100" /> 100 characters max.</li> <li>Message: <input type="text" name="message" /></li> <li>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</li> <li>Cc myself: <input type="checkbox" name="cc_myself" /></li> >>> print f.as_p() <p>Subject: <input type="text" name="subject" maxlength="100" /> 100 characters max.</p> <p>Message: <input type="text" name="message" /></p> <p>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</p> <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
error_messages
開発版の Django で新たに追加された機能です
error_messages 引数を使うと、フィールドが送出するデフォルトのメッセージ をオーバライドできます。オーバライドしたいメッセージに対応する文字列をキー とし、メッセージを値に持つ辞書を渡してください。例えば、デフォルトのメッセー ジが以下のようだったとします:
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
カスタムのエラーメッセージは以下のようにして設定します:
>>> name = forms.CharField(error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
...
ValidationError: [u'Please enter your name']
各フィールドで定義されているエラーメッセージのキーは、後述の 組み込みフォームフィールドクラス の節で定義しています。
初期値を動的に決定する
フィールドオブジェクトの initial 引数を使えばフィールドの初期値をハード コードできます。では、初期値を動的に決めたい場合はどうすればよいのでしょう。 例えば、現在ログインしているユーザで username フィールドを埋めたいよう な場合です。
動的に初期値を決めるには、フォームオブジェクトの initial 引数を使います。 この引数はフィールド名と初期値を対応づけた辞書として指定します。全てのフィー ルドを含める必要はなく、初期値を設定したいフィールドだけでかまいません。例 を示しましょう:
>>> class CommentForm(forms.Form):
... name = forms.CharField()
... url = forms.URLField()
... comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'your username'}, auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="your username" /></td></tr>
<tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
>>> f = CommentForm(initial={'name': 'another username'}, auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="another username" /></td></tr>
<tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
フィールドの initial パラメタと同じく、フォームの初期値が表示されるのは 非束縛フォームだけであり、ユーザが特定のフィールドに値を入力しなかったとき のフォールバックとしては使われません。
最後に、フィールドに initial が定義されていて、 かつ フォームの初期化 時にも initial を指定した場合、後者の initial が優先されるので気を 付けてください。例えば、以下のように、フィールドとフォームの両方に initial を指定した場合、フォームの値の方が使われます:
>>> class CommentForm(forms.Form):
... name = forms.CharField(initial='class')
... url = forms.URLField()
... comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
<tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
組み込みフォームフィールドクラス
通常、 newforms ライブラリには、一般的なバリデーション機能を備えたフィー ルドクラスのセットがついてきます。この節では、そうした組み込みフィールドに ついて述べます。
各フィールドについて、 widget パラメタを指定しなかったときのデフォルト のウィジェット型について説明しています。また、データが空の値だったとき (前述の requied の節を参照してください) に返される値についても定義して います。
BooleanField
- デフォルトのウィジェット: CheckboxInput
- 空のフォームデータに対する値: False
- Python データへの正規化: Python の True または False 値
- required=True の場合、チェックボックスがチェックされているか (値 が True であるか) 検証します。
- エラーメッセージのキー: required
開発版の Django で新たに追加された機能: CheckboxInput (および 標準の BooleanField) に空の値を指定した場合のデフォルト値は、開発版では None から False に変更されました。
Note
全てのフィールドのサブクラスにデフォルトで required=True が設定され るようになったので、バリデーション条件の設定は重要です。チェックされる 場合とされない場合のあるチェックボックスをフォームに含めたい場合は、 BooleanField を生成する際に required=False を忘れずに指定せねば なりません。
CharField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: '' (空文字列)
- Python データへの正規化: Unicode 文字列オブジェクト
- max_length または min_length が指定された場合、文字列長を検証 します。それ以外の場合、どのような入力も valid とみなします。
オプションの引数として、 max_length と min_length の二つをとれます。 これらの引数を指定すると、文字列長が最大、あるいは最小値の条件を満たしてい るか検証します。
ChoiceField
- デフォルトのウィジェット: Select
- 空のフォームデータに対する値: '' (空文字列)
- Python データへの正規化: Unicode 文字列オブジェクト
- 入力値が選択肢内にある値かどうか検証します。
- エラーメッセージのキー: required, invalid_choice
追加の引数として、 choices 引数をとります。 choices 引数はイテレー ション可能オブジェクト (たとえばリストやタプルなど) で、各要素はフィールド の選択肢として使える 2 要素のタプルでなければなりません。この引数には、モデ ルフィールドの choices 引数と同じ形式で選択肢を指定できます。詳しくは モデル API のドキュメント を参照してください。
DateField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: None
- Python データへの正規化: Python datetime.date オブジェクト
- 入力値が datetime.date や datetime.datetime オブジェクト、ま たは特定の日付フォーマットの形式に従っているか検証します。
- エラーメッセージのキー: required, invalid
オプションの引数として、 input_formats をとります。 input_formats は、文字列から有効な datetime.date オブジェクトへの変換を試みるために使 われるフォーマット文字列からなるリストです。
input_formats を指定しない場合、デフォルトで以下の入力フォーマットをサ ポートします:
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
DateTimeField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: DateTimeInput
- Python データへの正規化: Python datetime.datetime オブジェクト
- 入力値が datetime.date や datetime.datetime オブジェクト、ま たは特定の日付フォーマットの形式に従っているか検証します。
- エラーメッセージのキー: required, invalid
オプションの引数として、 input_formats をとります。 input_formats は、文字列から有効な datetime.datetime オブジェクトへの変換を試みるため に使われるフォーマット文字列からなるリストです。
input_formats を指定しない場合、デフォルトで以下の入力フォーマットをサ ポートします:
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d', # '2006-10-25' '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' '%m/%d/%Y %H:%M', # '10/25/2006 14:30' '%m/%d/%Y', # '10/25/2006' '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' '%m/%d/%y %H:%M', # '10/25/06 14:30' '%m/%d/%y', # '10/25/06'
開発版の Django で新たに追加された機能: DateTimeField はデフォルト で TextInput を使っていましたが、変更されました。
DecimalField
開発版の Django で新たに追加された機能です
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: None
- Python データへの正規化: Python の decimal オブジェクト
- 値が 10 進小数に変換可能か検証します。文字列の先頭および末尾の空白文 字は除去されます。
- エラーメッセージのキー: required, invalid, max_value, min_value, max_digits, max_decimal_places, max_whole_digits
max_value, min_value, max_digits, および decimal_places の 4 つのオプション引数を取ります。 max_digits は最大の桁数 (先頭のゼロ詰 め桁を除いた上での小数点前後の桁数の合計) です。 decimal_places は小数 部の最大桁数です。
EmailField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: '' (空文字列)
- Python データへの正規化: Unicode 文字列オブジェクト
- やや複雑な正規表現を使って、入力値が有効なメールアドレスであるか検証 します。
- エラーメッセージのキー: required, invalid
オプションの引数として、 max_length と min_length の二つをとれます。 これらの引数を指定すると、文字列長が最大、あるいは最小値の条件を満たしてい るか検証します。
FileField
開発版の Django で新たに追加された機能です
- デフォルトのウィジェット: FileInput
- 空のフォームデータに対する値: None
- Python データへの正規化: ファイルコンテンツとファイル名を一つのオブジェ クトとしてラップする UploadedFile オブジェクト。
- フォームに結びつけられているファイルデータが空でないか検証します。
- エラーメッセージのキー: required, invalid, missing, empty
UploadedFile オブジェクトの詳細は ファイルアップロードのドキュメント を参照してください。
UploadedFile の文字列表現は filename 属性の値そのものです。
FileField をフォームで使う場合、 フォームにファイルデータを束縛する のを忘れないようにしてください。
FilePathField
開発版の Django で新たに追加された機能です
- デフォルトのウィジェット: Select
- 空のフォームデータに対する値: None
- Python データへの正規化: unicode オブジェクト
- 入力値が選択肢内にある値かどうか検証します。
- エラーメッセージのキー: required, invalid_choice
このフィールドを使うと、あるディレクトリの下にあるファイルを選択できます。 フィールドは以下の 3 つの追加引数を取ります:
引数 必須 説明 path Yes ファイルの一覧を表示したいディレクトリの絶 対パスです。実在するディレクトリを指定せね ばなりません。 recursive No False (デフォルト値) の場合、 path の直下にあるファイルのみを選択肢として表示 します。 True にすると、 path 以下 の全てのファイルを再帰的に探索して選択肢に します。 match No 正規表現です。この引数を指定すると、正規表 現にマッチしたファイル名だけを選択肢として 表示します。
FloatField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: None
- Python データへの正規化: Python の float 型
- 指定された値が浮動小数点数を表すかどうか検証します。 Python の float() 関数と同じく、前後に空白があってもかまいません。
- エラーメッセージのキー: required, invalid, max_value, min_value
オプションの引数として、 max_value および min_value をとります。こ れらの値は、入力値のとりえる値域の調整に使われます。
ImageField
開発版の Django で新たに追加された機能です
- デフォルトのウィジェット: FileInput
- 空のフォームデータに対する値: None
- Python データへの正規化: ファイルコンテンツとファイル名を一つのオブジェ クトとしてラップする UploadedFile オブジェクト。
- 空でないファイルデータがフォームに結びつけられていて、かつその内容が PIL で扱えるファイル形式であるか検証します。
- エラーメッセージのキー: required, invalid, missing, empty, invalid_image
ImageField を使いたい場合、 Python Imaging Library をインストールしてお かねばなりません。
ImageField をフォームで使う場合、 フォームにファイルデータを束縛する のを忘れないようにしてください。
IntegerField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: None
- Python データへの正規化: Python 整数型または長整数型
- 入力値が整数であるか検証します。 Python の int() 関数と同様、先頭 や末尾に空白があってもかまいません。
- エラーメッセージのキー: required, invalid, max_value, min_value
オプションの引数として、指定可能な数値の範囲を表す max_value および min_value をとります。
IPAddressField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: '' (空の文字列)
- Python データへの正規化: Unicode オブジェクト
- 正規表現を使って、値が正しい IPv4 アドレス形式であるか検証します。
- エラーメッセージのキー: required, invalid
MultipleChoiceField
- デフォルトのウィジェット: SelectMultiple
- 空のフォームデータに対する値: [] (空のリスト)
- Python データへの正規化: Unicode 文字列オブジェクトのリスト
- 入力値のリスト中の全ての値が選択肢内の値であるかどうか検証します。
- エラーメッセージのキー: required, invalid_choice, invalid_list
追加の引数として、 choices 引数をとります。 choices 引数はイテレー ション可能オブジェクト (たとえばリストやタプルなど) で、各要素はフィールド の選択肢として使える 2 要素のタプルでなければなりません。この引数には、モデ ルフィールドの choices 引数と同じ形式で選択肢を指定できます。詳しくは モデル API のドキュメント を参照してください。
NullBooleanField
- デフォルトのウィジェット: NullBooleanSelect
- 空のフォームデータに対する値: None
- Python データへの正規化: True, False または None 。
- バリデーションなし (VaridationError を送出しない)
RegexField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: '' (空文字列)
- Python データへの正規化: Unicode 文字列オブジェクト
- 入力値が特定の正規表現にマッチするかどうか検証します。
- エラーメッセージのキー: required, invalid
必須の引数 regex をとります。 regex は正規表現を表す文字列またはコ ンパイル済みの正規表現オブジェクトです。
また、以下のオプション引数をとります:
引数 説明 max_length 文字列の最大長です。 min_length 文字列の最小長です。
以前のバージョンとの互換性のため、オプション引数 error_message も指定で きるようになっています。エラーメッセージを指定したければ、 error_messages を使って、 'invalid' をキーにしたメッセージを指定す るよう薦めます。
TimeField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: None
- Python データへの正規化: Python datetime.time オブジェクト
- 入力値が datetime.time オブジェクトまたは特定の日付フォーマットの 形式に従っているか検証します。
- エラーメッセージのキー: required, invalid
オプションの引数として、 input_formats をとります。 input_formats は、文字列から有効な datetime.time オブジェクトへの変換を試みるために使 われるフォーマット文字列からなるリストです。
input_formats を指定しない場合、デフォルトで以下の入力フォーマットをサ ポートします:
'%H:%M:%S', # '14:30:59' '%H:%M', # '14:30'
URLField
- デフォルトのウィジェット: TextInput
- 空のフォームデータに対する値: '' (空文字列)
- Python データへの正規化: Unicode 文字列オブジェクト
- 入力値が有効な URL であるかどうか検証します。
- エラーメッセージのキー: required, invalid, invalid_link
以下のオプションの引数をとります:
引数 説明 Description max_length 文字列の最大長です。 min_length 文字列の最小長です。 verify_exists True にすると、バリデータが指定された URL をロードできるか調べ、該当ページの HTTP レス ポンスが 404 のときに ValidationError を 送出します。デフォルト値は False です。 validator_user_agent URL が存在するか確かめるときに使うユーザエー ジェントを表す文字列です。デフォルト値は URL_VALIDATOR_USER_AGENT 設定の値です。
やや複雑な組み込みフィールドクラス
以下のフィールドはまだドキュメント化されていません。使いかたは、このドキュ メントの末尾のリンクから辿れる各フィールド型のユニットテストを参照してくだ さい。
ComboField
MultiValueField
SplitDateTimeField
リレーションを扱うフィールド
モデル間のリレーションを表現するために、二つのフィールドが提供されています。 これらのフィールドは、選択肢を QuerySet から取り出し、 cleaned_data 辞書にモデルオブジェクトを入れます。フィールドはいずれも以下の必須の引数を 取ります:
- queryset
- フィールドが選択肢として表示するモデルオブジェクトの QuerySet です。 また、フィールドはこの QuerySet を使って、ユーザの選択値が QuerySet 内に含まれているかを検証します。
ModelChoiceField
モデルオブジェクト一つを選択できるフィールドです。外部キーを表現するのに適 しています。フィールドの選択肢として表示される文字列の取得には、モデルオブ ジェクトの __unicode__ メソッドを使います。表示をカスタマイズしたければ、 ModelChoicefield をサブクラス化して、 label_for_instance メソッドを オーバライドしてください。このメソッドはモデルオブジェクトを引数にとり、表 示に適した文字列を返さねばなりません:
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
ModelMultipleChoiceField
複数のモデルオブジェクトを選択できるフィールドです。多対多のリレーションを 表現するのに向いています。 ModelChoiceField と同様、オブジェクトの表示 を変更するには label_from_instance メソッドをオーバライドしてください。
カスタムフィールドの作成
組み込みのフィールドクラスが用途に合わなくても、カスタムのフィールドクラス は簡単に作成できるので大丈夫です。カスタムのフィールドクラスは django.newforms.Field をサブクラス化します。 フィールドクラスを定義する際の制約は、 clean() メソッドを実装すること、 __init__() メソッドが前述のコアとなる引数 (required, label, initial, widget, help_text) を持つことだけです。
フォームやフィールドのバリデーションコードを自作する
フォームのバリデーションは、データのクリーニングを行ったときに実行されます。 この挙動をカスタマイズしたければ、目的に応じていくつかの手法から選択するこ とになります。フォームの処理では、 3 つのデータクリーニング過程があります。 これらの過程は通常、フォームの is_valid() メソッドを呼び出したときに実 行されます。データのクリーニングとバリデーションをトリガする要素は他にもあ ります (errors 属性へのアクセスや、 full_clean() の呼び出し) が、 通常は直接用いることはありません。
一般に、データクリーニングのメソッドは、処理中のデータに何らかの問題がある 場合、 ValidationError 例外を送出し、その際 ValidationError のコン ストラクタにエラーメッセージを渡すことになっています。 ValidationError を送出しない場合、クリーニングメソッドはクリーニング済み (正規化済み) のデー タを Python オブジェクトとして返さねばなりません。
クリーニング処理中に複数のエラーを検出し、全てのエラーをフォームのユーザに 提示したければ、 ValidationError のコンストラクタにエラーのリストを渡せ ます。
クリーニングメソッドとは、以下の 3 つです:
フィールドサブクラスの clean() メソッド。このメソッドは、該当フィー ルドクラスのインスタンスに共通して必要なクリーニングを行う役割を担いま す。例えば、 FloatField の clean() メソッドは、データを Python の float オブジェクトに変換するか、 ValidationError を送出します。
フォームサブクラスの clean_<fieldname>() (<fieldname> はフォー ムフィールドを指す属性名) メソッド。このメソッドは特定の属性名のフィー ルドに対するクリーニングを行います。フィールドの型は問いません。この メソッドはパラメタ無しで呼び出されます。フィールドの値は self.cleaned_data を介して参照せねばならず、値はこの段階ではフォー ムとして提出された元の文字列ではなく、Python オブジェクトであることに 注意してください。(cleaned_data に入るのは、上記の clean() メ ソッドが既にデータを一度クリーニングしているからです。)
例えば、 serialnumber という名前の CharField の中身が一意な値 になるようバリデーションを行いたければ、 clean_serialnumber() を 使うのが適切です。特定のフィールド (実際には CharField) 向けでは なく、フォーム上のフィールド固有のバリデーションや、データのクリーニ ング、正規化を行うことになります。
フォームのサブクラスの clean() メソッド。このメソッドは、フォーム 上の複数のフィールドに一度にアクセスする必要があるようなバリデーショ ンを実行できます。「フィールド A に値が入っている時に、フィールド B には有効なメールアドレスが入っていなければならない」といったバ リデーションにはこのメソッドを使います。このメソッドの返すデータは、 フォームの cleaned_data 属性の最終的な値になるので、このメソッド をオーバライドする場合は必ず全てのクリーニング済みデータを返すように してください (デフォルトでは、 Form.clean() は self.cleaned_data をそのまま返します)。
オーバライドされた Form.clean() の送出するエラーは特定のフィー ルドではなく、(__all__ という名の) 特殊な「フィールド」に関連づけ られます。エラーは non_field_errors() でアクセスできます。
上に挙げたメソッドは、各フィールドごとに一度づつ、上に挙げた順に呼び出され ます。すなわち、フォームの各フィールドについて、 (フォーム定義で宣言した順 に) まず Field.clean() メソッドを呼び出し、次いで cleaned_<fieldname>() 、を呼びます。最後に、 Form.clean() メソッド (オーバライドされていればそれを) を呼び出します。
先程も書いた通り、上記のメソッドは VaridationError を送出することがあり ます。個々のフィールドで、 Field.clean() メソッドが ValidationError を送出した場合、フォーム上のフィールドごとのクリーニングメソッドは呼び出さ れません。ただし、残りの全てのフィールドに対するクリーニング処理は最後まで 行われます。
フォームクラスやサブクラスに対する clean() メソッドは常に実行されます。 このメソッドが VaridationError を送出する場合、 cleaned_data は空の 辞書になります。
前の段落の意味するところは、 Form.clean() をオーバライドする場合、 self.cleaned_data.items() の各要素を調べ、さらにフォームの _errors 属性も考慮して、どのフィールドが個別のバリデーション用件を満たしているかを 調べる必要があるということです。
簡単な例
入力値がカンマで区切られたメールアドレスになっていて、少なくとも一つアドレ スが入っているかどうか検証するカスタムフィールドの例を以下に示します。簡単 のため、個々のメールアドレスの検証は is_valid_email() という関数で行っ ていることにします。クラスの全体像は以下のようになります:
from django import newforms as forms
class MultiEmailField(forms.Field):
def clean(self, value):
if not value:
raise forms.ValidationError('Enter at least one e-mail address.')
emails = value.split(',')
for email in emails:
if not is_valid_email(email):
raise forms.ValidationError('%s is not a valid e-mail address.' % email)
return emails
件の ContactForm の例を使って、上のカスタムフィールドをフォームで使って みます。以下のように、 forms.EmailField を MultiEmailField に置き換 えてください:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
senders = MultiEmailField()
cc_myself = forms.BooleanField(required=False)
ウィジェット(Widget)
ウィジェットとは、Django で HTML の入力エレメントを表現するためのオブジェク トです。ウィジェットは、 HTML のレンダリングや、個々のウィジェットに対応す るデータをGET/POST 辞書から抽出する処理を行います。
Django は、基本的な HTML ウィジェットすべてと、よく使われるウィジェットグルー プを提供しています:
ウィジェット名 等価な HTML 表現 TextInput <input type='text' ... PasswordInput <input type='password' ... HiddenInput <input type='hidden' ... MultipleHiddenInput 複数の <input type='hidden' ... FileInput <input type='file' ... DateTimeInput <input type='text' ... Textarea <textarea>...</textarea> CheckboxInput <input type='checkbox' ... Select <select><option ... NullBooleanSelect 「不明」, 「はい」, 「いいえ」 を 選択肢とする <select> SelectMultiple <select multiple='multiple'><option ... RadioSelect <ul><li><input type='radio' ... CheckboxSelectMultiple <ul><li><input type='checkbox' ... MultiWidget 複数ウィジェットに対するラッパ SplitDateTimeWidget 日付と時刻を表す二つの TextInput ウィジェットに対するラッパ
開発版の Django で新たに追加された機能: DateTimeInput が追加されて います。
ウィジェットの指定
フォームに何らかのフィールドを作成する場合、 Django は表示するデータの型に 応じて、適切なデフォルトのウィジェットを使います。どのフィールドがどのウィ ジェットを使っているかは、組み込みフィールドクラスのドキュメントを参照して ください。
とはいえ、デフォルトとは違うウィジェットを使いたい場合もあるでしょう。その 場合には、以下の例のように、フィールドを定義する際に widget 引数を指定 します:
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField(widget=forms.Textarea)
上のコードでは、 comment フィールドにデフォルトの TextInput ウィジェットで はなく、より大きな TextArea ウィジェットを使うように指定しています。
ウィジェットインスタンスのカスタマイズ
ウィジェットを HTML としてレンダリングする際、 Django は最小限の HTML しか 出力しません。すなわち、クラス定義やウィジェット固有の属性は一切付加しない のです。従って、例えばページ上にまったく同じ見栄えの 'TextInput' ウィジェッ トが並ぶわけです。
ウィジェットごとに見栄えを変えたいのなら、各々のウィジェットを指定するとき に、HTML レンダリング時に追加したい属性のリストを指定してやる必要があります。
例えば、以下のような簡単なフォームを考えましょう:
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField()
このフォームは 3 つの TextInput ウィジェットからなり、デフォルトのレンダリ ングでは CSS クラスや属性は指定されていません。従って、各ウィジェットは全く 同じ入力ボックスとしてレンダリングされます:
>>> f = CommentForm(auto_id=False) >>> f.as_table() <tr><th>Name:</th><td><input type="text" name="name" /></td></tr> <tr><th>Url:</th><td><input type="text" name="url"/></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
現実の Web ページでは、全ウィジェットが同じに見えるような見栄えを期待しない でしょう。コメント欄をもうちょっと大きな入力ボックスにしたかったり、 'name' ウィジェットに特別な CSS クラスを指定したかったりするかもしれま せん。個々のウィジェットの属性こ細かく指定するには、フィールドにカスタムの ウィジェットを指定して、ウィジェットのレンダリング時に使われる属性を指定し ます:
class CommentForm(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={'class':'special'}))
url = forms.URLField()
comment = forms.CharField(
widget=forms.TextInput(attrs={'size':'40'}))
これで、 Django はレンダリング結果に追加の属性を組み込むようになります:
>>> f = CommentForm(auto_id=False) >>> f.as_table() <tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr> <tr><th>Url:</th><td><input type="text" name="url"/></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
カスタムウィジェット
フォームをたくさん書くようになれば、特定の属性が指定されたウィジェットを再 利用したくなることでしょう。カスタムウィジェットを使えば、属性定義をキャプ チャできるので、毎回必要に応じて同じ属性定義を繰り返さなくてすみます。
例えば、自分のコードでコメント入力フィールドの必要なフォームをたくさん使っ ていることに気付いたとしましょう。そんなときは、デフォルトの size 値を 特定の値にした TextInput を、 TextInput ウィジェットを拡張したカス タムウィジェットとして定義できます:
class CommentWidget(forms.TextInput):
def __init__(self, *args, **kwargs):
kwargs.setdefault('attrs',{}).update({'size': '40'})
super(CommentWidget, self).__init__(*args, **kwargs)
このウィジェットでは、ユーザが size 属性をオーバライドできるようになっ てはいますが、デフォルトの挙動では常にコンストラクタに attrs={'size': 40} を渡しています。
そして、このウィジェットをフォームで使います:
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField(widget=CommentWidget)
カスタムウィジェットは、他のウィジェットと同様に更にカスタマイズできます。 一回限りしか使わないような CSS クラス属性を CommentWidget に追加したけ れば、属性定義にパラメタを追加するだけです:
class CommentForm(forms.Form):
name = forms.CharField(max_length=20)
url = forms.URLField()
comment = forms.CharField(
widget=CommentWidget(attrs={'class': 'special'}))
また、カスタムウィジェットを使うカスタムのフィールド型を簡単に定義できます。 例えば、コメント入力用のカスタムフィールドは以下のように定義できます:
class CommentInput(forms.CharField):
widget = CommentWidget
こうしておけば、コメント入力が必要なフォーム全般に CommentInput を利用でき ます:
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = CommentInput()
モデルからフォームを生成する
モデルとフォームを組み合わせる方法として現在推奨されているのは、 ModelForm を使う方法です。 ModelForm のドキュメント を参照してくださ い。
form_for_model や form_for_instance は撤廃されます。これらのドキュ メントは、 別のドキュメント に移されました。
メディアファイル
魅力的で使いやすい Web フォームをレンダしたいなら、 HTML だけでは能力不足で しょう。CSS スタイルシートが必要でしょうし、ファンシーな "Web2.0" 的ウィジェッ トを使いたければ、各ページに JavaScript を仕込む必要もあるでしょう。あるペー ジでどのCSS と JavaScript の組み合わせが必要かは、そのページで使われている ウィジェットによって異なります。
そこで、 Django のメディア定義が登場します。 Django は様々なメディアファイ ルをそのメディアファイルを必要とするフォームやウィジェットに関連付けられる ようにしています。例えば、カレンダーを使って DateField をレンダしたいので、 カスタムの Calendar ウィジェットを定義したとします。このウィジェットには、 カレンダーをレンダするときに必要な CSS や JavaScript を関連付けておけます。 Calendar ウィジェットをフォーム上で使うときに、 Django は必要な CSS や JavaScript ファイルを特定して、それらのファイル名のリストを Web ページに簡 単に組み込める形式で提供します。
Django admin とメディアファイル
Django の admin アプリケーションは、カレンダーやフィルタ機能つきのセレ クタといった、様々なカスタムウィジェットを定義しています。これらのウィ ジェットは、それぞれが必要なメディアを定義していて、 Django は admin サ イトを表示するときに、デフォルトのウィジェットの代りにカスタムウィジェッ トを表示しています。 admin のテンプレートは、ページごとに必要なメディア ファイルだけを取り込み、ウィジェットをレンダするようになっています。
Django の admin アプリケーションのウィジェットを使いたいのなら、どうぞ ご自由に!ウィジェットは、全て django.contrib.admin.widgets で定義 されています。
どの JavaScript ツールキットを使えばいいの ?
JavaScript ツールキットは世の中に沢山あり、そのほとんどが(カレンダーの ような)アプリケーションの操作性を向上させられるウィジェットを定義して います。 Django は特定の JavaScript ツールキットを贔屓にしないよう慎重 に設計されています。どのツールキットにも、お互いに長所や短所があります。 自分にとって使いやすいツールキットを使ってください。 Django はどんな JavaScript ツールキットでも組み込めます。
メディア定義を静的に行う
メディア定義の簡単な方法は、クラスで静的に行うというものです。この方法では、 メディア定義を内部クラスとして宣言します。必要なメディアファイルの情報は、 内部クラスのプロパティとして定義します。
簡単な例を示しましょう:
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('pretty.css',)
}
js = ('animations.js', 'actions.js')
このコードでは、 TextInput をベースにして CalendarWidget を定義して います。 CalendarWidget をフォーム内で使うと、フォームは CSS ファイル pretty.css と JavaScript ファイル animations.js および actions.js を組み込むよう指示を受けます。
このような静的なメディア定義は、実行時に media というウィジェットのプロ パティに変換されます。 CalendarWidget インスタンスのメディア情報は、プ ロパティを介して以下のように取り出せます:
>>> w = CalendarWidget() >>> print w.media <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" /> <script type="text/javascript" src="http://media.example.com/animations.js"></script> <script type="text/javascript" src="http://media.example.com/actions.js"></script>
Media には、以下のようなオプションがあります。必須のものはありません。
css
出力媒体ごとの CSS ファイル要件を定義した辞書です。
この辞書の値はメディアファイル名を列挙したタプルまたはリストです。メディア ファイルのパスを指定する方法は メディアパスの節 を参照してください。
キーは出力媒体の名前です。このキーは、 CSS ファイルのメディアタイプ宣言で使 われるのと同じ、すなわち 'all', 'aural', 'braille', 'embossed', 'handheld', 'print', 'projection', 'screen', 'tty' そして 'tv' です。 メディアタイプごとに別々のスタイルシートを使いたければ、各メディアごとに CSS ファイルのリストを指定してください。以下の例では、スクリーン (screen) と印刷 (print) 用の CSS オプションを定義しています:
class Media:
css = {
'screen': ('pretty.css',),
'print': ('newspaper.css',)
}
一つの CSS ファイルグループが複数のメディアタイプをカバーできる場合、辞書の キーにメディアタイプをカンマ区切りのリストにして指定してください。以下の例 では、テレビ (tv) およびプロジェクタ (projector) に同じメディアファイル要件 を指定しています:
class Media:
css = {
'screen': ('pretty.css',),
'tv,projector': ('lo_res.css',),
'print': ('newspaper.css',)
}
'print' の CSS 定義を使ってレンダリングを実行すると、以下のような HTML を出 力します:
<link href="http://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" /> <link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" /> <link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
js
必要な JavaScript ファイルを列挙したタプルです。メディアファイルのパスを指 定する方法は メディアパスの節 を参照してください。
extend
メディア宣言を親クラスから継承するかどうかを決めるブール値です。
デフォルトの設定では、静的メディア宣言を使うオブジェクトは全て、親クラスの ウィジェットで定義されているメディアを継承します。この継承は、親クラスでど んなメディアファイル要件を宣言したかに関係なく行われます。例えば、上の例の カレンダーウィジェットを、以下のように拡張したとします:
class FancyCalendarWidget(CalendarWidget):
class Media:
css = {
'all': ('fancy.css',)
}
js = ('whizbang.js',)
>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
FancyCalendarWidget は、親ウィジェットの全てのメディアを継承します。こ のやり方で親モデルのメディア宣言を継承したくないのなら、 extend=False を Media クラスの宣言に追加します:
class FancyCalendar(Calendar):
class Media:
extend = False
css = {
'all': ('fancy.css',)
}
js = ('whizbang.js',)
>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
メディア宣言の継承をより細かく制御したければ、 動的なプロパティ を使って メディア宣言を定義してください。動的プロパティを使えば、どのメディアファイ ルを継承し、どのファイルを継承しないかを完全に制御できます。
動的プロパティでのメディア定義
media プロパティを直接定義すれば、メディアファイル要件をより洗練された やり方で制御できます。この方法では、 forms.Media のインスタンスを 返すようなプロパティを定義します。 forms.Media のコンストラクタは css や js といったキーワード引数をとり、それぞれ静的なメディア定義 で使われているのと同じ形式の値を指定できます。
例えば、先の CalendarWidget の静的メディア定義を動的なやり方で実現する と、以下のようになります:
class CalendarWidget(forms.TextInput):
def _media(self):
return forms.Media(css={'all': ('pretty.css',)},
js=('animations.js', 'actions.js'))
media = property(_media)
動的なメディアプロパティの戻り値を構築するための詳しい方法は、 Media オブジェクト の節を参照してください。
メディア定義のパス
メディアファイルの場所を指定するためのパスは、相対でも絶対でもかまいません。 パスが '/', 'http://', 'https://' のいずれかで開始していれば、 絶対パス指定として、値をそのまま使います。それ以外のパスの前には settings.MEDIA_URL の値を付加します。例えば、 MEDIA_URL が http://media.example.com/ の場合は、以下のウィジェット:
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('/css/pretty.css',),
}
js = ('animations.js', 'http://othersite.com/actions.js')
は、以下のような HTML を出力します。
>>> w = CalendarWidget() >>> print w.media <link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" /> <script type="text/javascript" src="http://media.example.com/animations.js"></script> <script type="text/javascript" src="http://othersite.com/actions.js"></script>
Media オブジェクト
ウィジェットやフォームの media 属性を調べると、 forms.Media オブジェク トが返されます。すでに解説した通り、 Media オブジェクトの文字列表現は、 HTML ページの <head> ブロックでメディアファイルを取り込むときに必要な HTML コードです。
とはいえ、 Media オブジェクトには他にもいくつか興味深いプロパティがあり ます。
Media サブセット
特定のタイプのメディアファイル情報だけを出力したければ、添え字表記を使って 以下のように欲しいメディアだけを指定できます:
>>> w = CalendarWidget() >>> print w.media <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" /> <script type="text/javascript" src="http://media.example.com/animations.js"></script> <script type="text/javascript" src="http://media.example.com/actions.js"></script> >>> print w.media['css'] <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
添え字表記を使ってアクセスすると、新たな Media オブジェクトが返されます。 ただし、このオブジェクトには指定したメディアしか入っていません。
Media オブジェクトを組み合わせる
Media オブジェクトは加算できます。二つの Media オブジェクトを加算す ると、結果の Media オブジェクトには両方のメディアファイル情報が入ります:
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('pretty.css',)
}
js = ('animations.js', 'actions.js')
class OtherWidget(forms.TextInput):
class Media:
js = ('whizbang.js',)
>>> w1 = CalendarWidget()
>>> w2 = OtherWidget()
>>> print w1+w2
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
フォームのメディア
メディアファイル定義を持てるのはウィジェットだけではありません。フォームに もまた、メディアを定義できます。フォームのメディアファイル定義には、ウィジェ ットと同じ規則が適用されます。すなわち、静的・動的な宣言ができ、宣言内容に は絶対・相対パスの解釈や継承といった規則がウィジェット同様に当てはまります。
どちらにメディア情報を定義したかに関係なく、フォームオブジェクトには 必ず media プロパティがあります。このプロパティのデフォルト値は、 フォームを構成する全てのウィジェットのメディアファイル定義を加算したもので す:
class ContactForm(forms.Form):
date = DateField(widget=CalendarWidget)
name = CharField(max_length=40, widget=OtherWidget)
>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
フォーム固有のメディア宣言を付加したい場合、例えば、フォームのレイアウトを 決める CSS を指定したい場合には、以下のようにメディア定義を追加します:
class ContactForm(forms.Form):
date = DateField(widget=CalendarWidget)
name = CharField(max_length=40, widget=OtherWidget)
class Media:
css = {
'all': ('layout.css',)
}
>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
フォームセット (formsets)
フォームセットとは、同じページで複数のフォームを扱うための抽象化レイヤで、 いわばデータグリッドのようなものです。フォームセットを説明するために、まず 以下のようなフォームを考えましょう:
>>> from django import newforms as forms >>> class ArticleForm(forms.Form): ... title = forms.CharField() ... pub_date = forms.DateField()
このフォームを使って、ユーザが一度に複数の記事を作成できるようにしたい場合 があったとします。そのために、 ArticleForm からフォームセットを生成しま す:
>>> from django.newforms.formsets import formset_factory >>> ArticleFormSet = formset_factory(ArticleForm)
ArticleFormSet という名前のフォームセットクラスができました。このフォー ムセットには、フォームセットに入っているフォームを一つ一つ取り出して、それ ぞれを普通のフォームとして表示する機能があります:
>>> formset = ArticleFormSet() >>> for form in formset.forms: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
出力を見て分かる通り、フォームは一つだけ表示されています。これは、 formset_factory のデフォルトの設定で、「追加のフォーム表示数 (extra)」 を 1 に設定しているからです。表示数は extra パラメタで制御できます:
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
フォームセットに初期データを指定する
初期データは、フォームセットのユーザビリティに影響する大きな要素です。上に 示したように、 formset_factory には追加のフォーム表示数を指定できます。 この「追加」とは、初期データを渡したときに表示されるフォームの中で、追加で 表示されている空のフォーム数という意味です。以下の例をよく見てください:
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(initial=[
... {'title': u'Django is now open source',
... 'pub_date': datetime.date.today()},
... ])
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
上の例では、今度は 3 つのフォームが表示されました。初期データとして渡した 1 つと、 2 つの追加フォームです。初期データとして、辞書のリストを渡しているこ とにも注意してください。
フォームの最大表示数を制限する
formset_factory に max_num パラメタを指定すると、フォームセット中に 表示されるフォームの最大数を制御できます:
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1) >>> formset = ArticleFormset() >>> for form in formset.forms: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
max_num のデフォルト値は表示数に制限がないことを示す 0 に設定されて います。
フォームセットのバリデーション
フォームセットのバリデーションは、普通の Form とほぼ同じです。フォーム セットにも is_valid メソッドがあり、フォームセット中の全てのフォームを 簡単に検証できます:
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
True
この例では、フォームセットにデータを渡さなかったので、有効なフォームを返し ています。フォームセットは賢くて、データの変更されなかったフォームを無視し てくれます。あるフォーム上の記事を変更しようとして、失敗した場合の挙動を以 下に示します:
>>> data = {
... 'form-TOTAL_FORMS': u'1',
... 'form-INITIAL_FORMS': u'1',
... 'form-0-title': u'Test',
... 'form-0-pub_date': u'',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{'pub_date': [u'This field is required.']}]
このように、フォームセットはバリデーションを正しく実行して、期待通りのエラー を表示します。
ManagementForm を理解する
上の例でフォームセットに与えた初期値には、追加のデータが入っていたことに気 付いたでしょうか。これはフォームセットで内部的に処理されているフォーム、 ManagementForm で扱うためのデータです。追加のデータ抜きでフォームセット を使おうとすると、例外が送出されます:
>>> data = {
... 'form-0-title': u'Test',
... 'form-0-pub_date': u'',
... }
>>> formset = ArticleFormSet(data)
Traceback (most recent call last):
...
django.newforms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
ManagementForm のデータは、表示するフォームインスタンスの数を追跡するた めに使われます。 JavaScript でフォームを動的に追加する場合、 ManagementForm データのカウントも増やさねばなりません。
カスタムのフォームセットバリデーション
Form クラスと同様、フォームセットには clean メソッドがあります。こ のメソッドには、フォームセットレベルで扱う独自のバリデーションはここに定義 します:
>>> from django.newforms.formsets import BaseFormSet
>>> class BaseArticleFormSet(BaseFormSet):
... def clean(self):
... raise forms.ValidationError, u'An error occured.'
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
False
>>> formset.non_form_errors()
[u'An error occured.']
フォームセットの clean メソッドは、全てのフォームに対して Form.clean メソッドが呼出された後に実行されます。フォームセットに関する エラーは、フォームセットの non_form_errors() メソッドで取り出せます。
フォームの並び順や削除の扱い
フォームセットを扱う上でよくあるユースケースは、フォームインスタンスの並び 順や削除の処理です。フォームセットはこれらの処理を実行してくれます。 formset_factory には can_order および can_delete という二つの パラメタがあり、指定するとフォームにフィールドを追加して、並び順や削除フラ グを操作する簡単な手段を提供します。
can_order
デフォルト値: False
True にすると、フォームセットは順番を指定できるフォームを生成します:
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
このオプションを指定すると、各フォームにフィールドが追加されます。フィール ドの名前は ORDER で、型は forms.IntegerField です。初期データを使っ てフォームを生成した場合、 ORDER フィールドには自動的に数値が割り当てら れます。ユーザがこの値を変更するとどうなるか見てみましょう:
>>> data = {
... 'form-TOTAL_FORMS': u'3',
... 'form-INITIAL_FORMS': u'2',
... 'form-0-title': u'Article #1',
... 'form-0-pub_date': u'2008-05-10',
... 'form-0-ORDER': u'2',
... 'form-1-title': u'Article #2',
... 'form-1-pub_date': u'2008-05-11',
... 'form-1-ORDER': u'1',
... 'form-2-title': u'Article #3',
... 'form-2-pub_date': u'2008-05-01',
... 'form-2-ORDER': u'0',
... }
>>> formset = ArticleFormSet(data, initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
... print form.cleaned_data
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
can_delete
デフォルト値: False
True にすると、フォームセットは削除を実行できるフォームを生成します:
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset.forms:
.... print form.as_table()
<input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" />
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>
can_order と同様、 DELETE という名前のついたフィールドが追加されま す。このフィールドは forms.BooleanField です。削除フラグフィールドをマー クしたデータを投入すると、削除フラグの立っているフォームに deleted_forms でアクセスできます:
>>> data = {
... 'form-TOTAL_FORMS': u'3',
... 'form-INITIAL_FORMS': u'2',
... 'form-0-title': u'Article #1',
... 'form-0-pub_date': u'2008-05-10',
... 'form-0-DELETE': u'on',
... 'form-1-title': u'Article #2',
... 'form-1-pub_date': u'2008-05-11',
... 'form-1-DELETE': u'',
... 'form-2-title': u'',
... 'form-2-pub_date': u'',
... 'form-2-DELETE': u'',
... }
>>> formset = ArticleFormSet(data, initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
フォームセットにフィールドを追加する
フォームセットには、追加のフィールドを簡単に追加できます。フォームセットの ベースクラスは add_fields メソッドを提供しています。このメソッドをオー バライドして、独自にフィールドを追加したり、デフォルトのフィールドや属性を 再定義したり、フィールドを削除したりできます:
>>> class BaseArticleFormSet(BaseFormSet): ... def add_fields(self, form, index): ... super(BaseArticleFormSet, self).add_fields(form, index) ... form.fields["my_field"] = forms.CharField() >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) >>> formset = ArticleFormSet() >>> for form in formset.forms: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr>
ビューやテンプレートでフォームセットを使う
ビュー内でのフォームセットの扱いは簡単で、普通の Form クラスと同じよう に使うだけです。気をつけておかねばならないのは、テンプレート内で management_form を使うという点です。ビューの例を以下に示します:
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
if request.method == 'POST':
formset = ArticleFormSet(request.POST, request.FILES)
if formset.is_valid():
# formset.cleaned_data を使った処理をここに
else:
formset = ArticleFormSet()
return render_to_response('manage_articles.html', {'formset': formset})
manage_articles.html テンプレートは以下のようになります:
<form method="POST" action="">
{{ formset.management_form }}
<table>
{% for form in formset.forms %}
{{ form }}
{% endfor %}
</table>
</form>
ただし、上のような構造は、下記のようなショートカットを使って、フォームセッ ト自体に管理フォームを扱わせられます:
<form method="POST" action="">
<table>
{{ formset }}
</table>
</form>
このショートカットは、フォームセットの as_table を呼び出した時と同じ内 容を出力します。
つづく
現時点でのドキュメントはこれだけです。詳しくは django.newforms のユニッ トテスト、 http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/ を参照してください。 newforms でできることをよく理解できるはずです。(サブ モジュール毎に、個別のテストが入っています。)
このライブラリを使ってみたくてうずうずしているなら、もう少しだけ我慢してく ださいね。今、コードとドキュメントの仕上げにかかっているところなんです。