.. _topics-forms-formsets:
フォームセット (formsets)
=============================
:revision-up-to: 9084 (1.0)
フォームセットとは、同じページで複数のフォームを扱うための抽象化レイヤで、
いわばデータグリッドのようなものです。フォームセットを説明するために、まず
以下のようなフォームを考えましょう::
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
このフォームを使って、ユーザが一度に複数の記事を作成できるようにしたい場合
があったとします。そのために、 ``ArticleForm`` からフォームセットを生成しま
す::
>>> from django.forms.formsets import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
``ArticleFormSet`` という名前のフォームセットクラスができました。このフォー
ムセットには、フォームセットに入っているフォームを一つ一つ取り出して、それ
ぞれを普通のフォームとして表示する機能があります::
>>> formset = ArticleFormSet()
>>> for form in formset.forms:
... print form.as_table()
| |
| |
出力を見て分かる通り、フォームは一つだけ表示されています。これは、
``formset_factory`` のデフォルトの設定で、「追加のフォーム表示数 (extra)」
を 1 に設定しているからです。表示数は ``extra`` パラメタで制御できます::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
.. _Using initial data with a formset:
フォームセットに初期データを指定する
-------------------------------------
初期データは、フォームセットのユーザビリティに影響する大きな要素です。上に
示したように、 ``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()
| |
| |
| |
| |
| |
| |
上の例では、今度は 3 つのフォームが表示されました。初期データとして渡した 1
つと、 2 つの追加フォームです。初期データとして、辞書のリストを渡しているこ
とにも注意してください。
.. _Limiting the maximum number of forms:
フォームの最大表示数を制限する
------------------------------------
``formset_factory`` に ``max_num`` パラメタを指定すると、フォームセット中に
表示されるフォームの最大数を制御できます::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormset()
>>> for form in formset.forms:
... print form.as_table()
| |
| |
``max_num`` のデフォルト値は表示数に制限がないことを示す ``0`` に設定されて
います。
.. _Formset validation:
フォームセットのバリデーション
------------------------------------
フォームセットのバリデーションは、普通の ``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.']}]
このように、フォームセットはバリデーションを正しく実行して、期待通りのエラー
を表示します。
.. _Understanding the ManagementForm:
``ManagementForm`` を理解する
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
上の例でフォームセットに与えた初期値には、追加のデータが入っていたことに気
付いたでしょうか。これはフォームセットで内部的に処理されているフォーム、
``ManagementForm`` で扱うためのデータです。追加のデータ抜きでフォームセット
を使おうとすると、例外が送出されます::
>>> data = {
... 'form-0-title': u'Test',
... 'form-0-pub_date': u'',
... }
>>> formset = ArticleFormSet(data)
Traceback (most recent call last):
...
django.forms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
``ManagementForm`` のデータは、表示するフォームインスタンスの数を追跡するた
めに使われます。 JavaScript でフォームを動的に追加する場合、
``ManagementForm`` データのカウントも増やさねばなりません。
.. _Custom formset validation:
カスタムのフォームセットバリデーション
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Form`` クラスと同様、フォームセットには ``clean`` メソッドがあります。こ
のメソッドには、フォームセットレベルで扱う独自のバリデーションを定義します::
>>> from django.forms.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()`` メソッドで取り出せます。
.. _Dealing with ordering and deletion of forms:
フォームの並び順や削除の扱い
------------------------------
フォームセットを扱う上でよくあるユースケースは、フォームインスタンスの並び
順や削除の処理です。フォームセットはこれらの処理を実行してくれます。
``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()
| |
| |
| |
| |
| |
| |
| |
| |
| |
このオプションを指定すると、各フォームにフィールドが追加されます。フィール
ドの名前は ``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()
| |
| |
| |
| |
| |
| |
| |
| |
| |
``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'}]
.. _Adding additional fields to a formset:
フォームセットにフィールドを追加する
-------------------------------------
フォームセットには、追加のフィールドを簡単に追加できます。フォームセットの
ベースクラスは ``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()
| |
| |
| |
.. _Using a formset in views and templates:
ビューやテンプレートでフォームセットを使う
----------------------------------------------
ビュー内でのフォームセットの扱いは簡単で、普通の ``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`` テンプレートは以下のようになります:
.. code-block:: html+django
ただし、下記のようなショートカットを使えば、フォームセット自体に管理フォー
ムを扱わせられます:
.. code-block:: html+django
このショートカットは、フォームセットの ``as_table`` を呼び出した時と同じ内
容を出力します。
複数のフォームセットを一つのビュー内で使う
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
お望みなら、複数のフォームセットを一つのビューで扱えます。フォームセットは
フォームとほとんど同じく動作します。つまり、 ``prefix`` を使ってフォームセッ
トのフォームフィールド名にプレフィクスをつければ、複数のフォームセットを
一つのビューに入れても問題なく動作するのです。複数のフォームセットがどのよ
うに動作するか、以下の例で示しましょう::
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
BookFormSet = formset_factory(BookForm)
if request.method == 'POST':
article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
if article_formset.is_valid() and book_formset.is_valid():
# do something with the cleaned_data on the formsets.
else:
article_formset = ArticleFormSet(prefix='articles')
book_formset = BookFormSet(prefix='books')
return render_to_response('manage_articles.html', {
'article_formset': article_formset,
'book_formset': book_formset,
})
これで、フォームセットは通常通りレンダできます。重要なのは、 ``prefix`` を
POST リクエストの場合とそうでない場合の両方に指定して、正しくレンダさせるこ
とです。