フォームセット
- class BaseFormSet
フォームセットは、同じページ上の複数のフォームを操作するための抽象化レイヤーです。 データグリッドと比較するのが最適です。 次のフォームがあるとします。
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
ユーザーが一度に複数の記事を作成できるようにすることができます。 ArticleForm
からフォームセットを作成するには、次のようにします。
>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
これで、ArticleFormSet
という名前のフォームセットクラスが作成されました。 フォームセットをインスタンス化すると、フォームセット内のフォームを反復処理して、通常のフォームの場合と同じように表示することができます。
>>> formset = ArticleFormSet()
>>> for form in formset:
... 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>
ご覧のとおり、空のフォームは1つしか表示されていません。 表示される空のフォームの数は、extra
パラメーターによって制御されます。 デフォルトでは、 formset_factory()は1つの追加フォームを定義します。 次の例では、2つの空白のフォームを表示するフォームセットクラスを作成します。
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
フォームセットを反復処理すると、フォームは作成された順序でレンダリングされます。 __iter__()
メソッドの代替実装を提供することにより、この順序を変更できます。
フォームセットにインデックスを付けることもできます。これにより、対応するフォームが返されます。 __iter__
をオーバーライドする場合、動作を一致させるには、__getitem__
もオーバーライドする必要があります。
フォームセットでの初期データの使用
初期データは、フォームセットの主なユーザビリティを推進するものです。 上に示したように、追加のフォームの数を定義できます。 これが意味するのは、初期データから生成されるフォームの数に加えて、表示する追加のフォームの数をフォームセットに指示しているということです。 例を見てみましょう:
>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(initial=[
... {'title': 'Django is now open source',
... 'pub_date': datetime.date.today(),}
... ])
>>> for form in formset:
... 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つは追加のフォームです。 また、初期データとして辞書のリストを渡していることにも注意してください。
フォームセットの表示にinitial
を使用する場合は、フォームセットの送信を処理するときに同じinitial
を渡して、ユーザーが変更したフォームをフォームセットが検出できるようにする必要があります。 たとえば、ArticleFormSet(request.POST, initial=[...])
のようなものがあります。
フォームの最大数を制限する
max_num
パラメーターを formset_factory()に設定すると、フォームセットに表示されるフォームの数を制限できます。
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormSet()
>>> for form in formset:
... 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
の値が初期データの既存のアイテムの数よりも大きい場合、フォームの総数が追加される限り、最大extra
の追加の空白のフォームがフォームセットに追加されます。 max_num
を超えないようにしてください。 たとえば、extra=2
とmax_num=2
で、フォームセットが1つのinitial
アイテムで初期化されている場合、最初のアイテムのフォームと1つの空白のフォームが表示されます。
初期データの項目数がmax_num
を超えると、max_num
の値に関係なく、すべての初期データフォームが表示され、追加のフォームは表示されません。 たとえば、extra=3
とmax_num=1
で、フォームセットが2つの初期アイテムで初期化されている場合、初期データを含む2つのフォームが表示されます。
max_num
の値None
(デフォルト)では、表示されるフォームの数に上限があります(1000)。 実際には、これは制限なしと同等です。
デフォルトでは、max_num
は表示されるフォームの数にのみ影響し、検証には影響しません。 validate_max=True
が formset_factory()に渡されると、max_num
が検証に影響します。 validate_max を参照してください。
インスタンス化されるフォームの最大数を制限する
バージョン3.2の新機能。
absolute_max
パラメーターを formset_factory()に設定すると、POST
データを提供するときにインスタンス化できるフォームの数を制限できます。 これにより、偽造されたPOST
要求を使用したメモリ枯渇攻撃から保護されます。
>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
>>> data = {
... 'form-TOTAL_FORMS': '1501',
... 'form-INITIAL_FORMS': '0',
... }
>>> formset = ArticleFormSet(data)
>>> len(formset.forms)
1500
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Please submit at most 1000 forms.']
absolute_max
がNone
の場合、デフォルトでmax_num + 1000
になります。 (max_num
がNone
の場合、デフォルトで2000
になります)。
absolute_max
がmax_num
より小さい場合、ValueError
が発生します。
フォームセットの検証
フォームセットを使用した検証は、通常のForm
とほぼ同じです。 フォームセットにはis_valid
メソッドがあり、フォームセット内のすべてのフォームを検証する便利な方法を提供します。
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True
フォームセットにデータを渡さなかったため、有効なフォームになりました。 フォームセットは、変更されていない余分なフォームを無視するのに十分スマートです。 無効な記事を提供した場合:
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
... 'form-0-title': 'Test',
... 'form-0-pub_date': '1904-06-16',
... 'form-1-title': 'Test',
... 'form-1-pub_date': '', # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
ご覧のとおり、formset.errors
は、フォームセット内のフォームに対応するエントリを持つリストです。 2つのフォームのそれぞれに対して検証が実行され、2番目の項目に予期されるエラーメッセージが表示されます。
通常のForm
を使用する場合と同様に、フォームセットのフォームの各フィールドには、ブラウザの検証用にmaxlength
などのHTML属性を含めることができます。 ただし、フォームセットのフォームフィールドにはrequired
属性が含まれません。これは、フォームを追加および削除するときに検証が正しくない可能性があるためです。
- BaseFormSet.total_error_count()
フォームセットに含まれるエラーの数を確認するには、total_error_count
メソッドを使用できます。
>>> # Using the previous example
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> len(formset.errors)
2
>>> formset.total_error_count()
1
フォームデータが初期データと異なるかどうかを確認することもできます(つまり、 フォームはデータなしで送信されました):
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
... 'form-0-title': '',
... 'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False
ManagementFormを理解する
上記のフォームセットのデータに必要な追加データ(form-TOTAL_FORMS
、form-INITIAL_FORMS
)に気付いたかもしれません。 このデータはManagementForm
に必要です。 このフォームは、フォームセットに含まれるフォームのコレクションを管理するためにフォームセットによって使用されます。 この管理データを提供しない場合、フォームセットは無効になります。
>>> data = {
... 'form-0-title': 'Test',
... 'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
表示されているフォームインスタンスの数を追跡するために使用されます。 JavaScriptを介して新しいフォームを追加する場合は、このフォームのカウントフィールドもインクリメントする必要があります。 一方、JavaScriptを使用して既存のオブジェクトの削除を許可している場合は、POST
データにform-#-DELETE
を含めて、削除するオブジェクトが適切に削除対象としてマークされていることを確認する必要があります。 POST
データには、関係なくすべてのフォームが存在することが予想されます。
管理フォームは、フォームセット自体の属性として使用できます。 テンプレートでフォームセットをレンダリングする場合、テンプレート:My formset.management form
をレンダリングすることですべての管理データを含めることができます(フォームセットの名前を適宜置き換えます)。
ノート
この例に示されているform-TOTAL_FORMS
およびform-INITIAL_FORMS
フィールドに加えて、管理フォームにはform-MIN_NUM_FORMS
およびform-MAX_NUM_FORMS
フィールドも含まれています。 これらは残りの管理フォームとともに出力されますが、これはクライアント側のコードの便宜のためだけです。 これらのフィールドは必須ではないため、POST
データの例には示されていません。
バージョン3.2での変更: formset.is_valid()
は、管理フォームが見つからないか改ざんされた場合に例外を発生させるのではなく、False
を返すようになりました。
total_form_countおよびinitial_form_count
BaseFormSet
には、ManagementForm
、total_form_count
、およびinitial_form_count
に密接に関連するいくつかのメソッドがあります。
total_form_count
は、このフォームセット内のフォームの総数を返します。 initial_form_count
は、事前に入力されたフォームセット内のフォームの数を返します。また、必要なフォームの数を決定するためにも使用されます。 これらのメソッドのいずれかをオーバーライドする必要はおそらくないので、オーバーライドする前に、それらが何をするのかを必ず理解してください。
empty_form
BaseFormSet
は、JavaScriptを使用した動的フォームで簡単に使用できるように、プレフィックスが__prefix__
のフォームインスタンスを返す追加の属性empty_form
を提供します。
error_messages
バージョン3.2の新機能。
error_messages
引数を使用すると、フォームセットが生成するデフォルトのメッセージを上書きできます。 オーバーライドするエラーメッセージに一致するキーを使用して辞書を渡します。 たとえば、管理フォームがない場合のデフォルトのエラーメッセージは次のとおりです。
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. You may need to file a bug report if the issue persists.']
そして、ここにカスタムエラーメッセージがあります:
>>> formset = ArticleFormSet({}, error_messages={'missing_management_form': 'Sorry, something went wrong.'})
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Sorry, something went wrong.']
カスタムフォームセットの検証
フォームセットには、Form
クラスのメソッドと同様のclean
メソッドがあります。 ここで、フォームセットレベルで機能する独自の検証を定義します。
>>> from django.core.exceptions import ValidationError
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def clean(self):
... """Checks that no two articles have the same title."""
... if any(self.errors):
... # Don't bother validating the formset unless each form is valid on its own
... return
... titles = []
... for form in self.forms:
... if self.can_delete and self._should_delete_form(form):
... continue
... title = form.cleaned_data.get('title')
... if title in titles:
... raise ValidationError("Articles in a set must have distinct titles.")
... titles.append(title)
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
... 'form-0-title': 'Test',
... 'form-0-pub_date': '1904-06-16',
... 'form-1-title': 'Test',
... 'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Articles in a set must have distinct titles.']
フォームセットclean
メソッドは、すべてのForm.clean
メソッドが呼び出された後に呼び出されます。 エラーは、フォームセットでnon_form_errors()
メソッドを使用して検出されます。
フォームセット内のフォームの数を検証する
Djangoは、送信されたフォームの最小数または最大数を検証するためのいくつかの方法を提供します。 フォーム数のよりカスタマイズ可能な検証が必要なアプリケーションでは、カスタムフォームセット検証を使用する必要があります。
validate_max
validate_max=True
が formset_factory()に渡された場合、検証では、データセット内のフォームの数から削除のマークが付けられたものを差し引いた数が [以下であることも確認されます。 X185X]。
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
... 'form-0-title': 'Test',
... 'form-0-pub_date': '1904-06-16',
... 'form-1-title': 'Test 2',
... 'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at most 1 form.']
validate_max=True
は、提供された初期データの量が多すぎたためにmax_num
を超えた場合でも、max_num
に対して厳密に検証します。
ノート
validate_max
に関係なく、データセット内のフォームの数がabsolute_max
を超えると、フォームはvalidate_max
が設定されているかのように検証に失敗し、さらに最初の[ X175X] フォームが検証されます。 残りは完全に切り捨てられます。 これは、偽造されたPOST要求を使用したメモリ枯渇攻撃から保護するためです。 インスタンス化されたフォームの最大数の制限を参照してください。
validate_min
validate_min=True
が formset_factory()に渡された場合、検証では、データセット内のフォームの数から削除のマークが付けられたものを差し引いた数が [以上であることも確認されます。 X188X]。
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
... 'form-0-title': 'Test',
... 'form-0-pub_date': '1904-06-16',
... 'form-1-title': 'Test 2',
... 'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at least 3 forms.']
ノート
validate_min
に関係なく、フォームセットにデータが含まれていない場合、extra + min_num
の空のフォームが表示されます。
フォームの注文と削除の処理
formset_factory()は、フォームセット内のフォームの順序付けとフォームセットからのフォームの削除に役立つ2つのオプションのパラメーターcan_order
とcan_delete
を提供します。
can_order
- BaseFormSet.can_order
デフォルト:False
注文できるフォームセットを作成できます。
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(initial=[
... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
... 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="number" 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="number" 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="number" name="form-2-ORDER" id="id_form-2-ORDER"></td></tr>
これにより、各フォームにフィールドが追加されます。 この新しいフィールドはORDER
という名前で、forms.IntegerField
です。 初期データから取得したフォームには、自動的に数値が割り当てられました。 ユーザーがこれらの値を変更するとどうなるかを見てみましょう。
>>> data = {
... 'form-TOTAL_FORMS': '3',
... 'form-INITIAL_FORMS': '2',
... 'form-0-title': 'Article #1',
... 'form-0-pub_date': '2008-05-10',
... 'form-0-ORDER': '2',
... 'form-1-title': 'Article #2',
... 'form-1-pub_date': '2008-05-11',
... 'form-1-ORDER': '1',
... 'form-2-title': 'Article #3',
... 'form-2-pub_date': '2008-05-01',
... 'form-2-ORDER': '0',
... }
>>> formset = ArticleFormSet(data, initial=[
... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': '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': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}
BaseFormSet は、 ordering_widget 属性と、 can_order で使用されるウィジェットを制御する get_ordering_widget()メソッドも提供します。
ordering_widget
- BaseFormSet.ordering_widget
デフォルト: NumberInput
ordering_widget
を設定して、can_order
で使用するウィジェットクラスを指定します。
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... ordering_widget = HiddenInput
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
get_ordering_widget
- BaseFormSet.get_ordering_widget()
can_order
で使用するウィジェットインスタンスを提供する必要がある場合は、get_ordering_widget()
をオーバーライドします。
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_ordering_widget(self):
... return HiddenInput(attrs={'class': 'ordering'})
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
can_delete
- BaseFormSet.can_delete
デフォルト:False
削除するフォームを選択する機能を備えたフォームセットを作成できます。
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
... 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-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': '3',
... 'form-INITIAL_FORMS': '2',
... 'form-0-title': 'Article #1',
... 'form-0-pub_date': '2008-05-10',
... 'form-0-DELETE': 'on',
... 'form-1-title': 'Article #2',
... 'form-1-pub_date': '2008-05-11',
... 'form-1-DELETE': '',
... 'form-2-title': '',
... 'form-2-pub_date': '',
... 'form-2-DELETE': '',
... }
>>> formset = ArticleFormSet(data, initial=[
... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': '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': 'Article #1'}]
ModelFormSet を使用している場合、formset.save()
を呼び出すと、削除されたフォームのモデルインスタンスが削除されます。
formset.save(commit=False)
を呼び出しても、オブジェクトは自動的に削除されません。 実際に削除するには、各 formset.deleted_objects でdelete()
を呼び出す必要があります。
>>> instances = formset.save(commit=False)
>>> for obj in formset.deleted_objects:
... obj.delete()
一方、プレーンなFormSet
を使用している場合は、フォームセットのsave()
メソッドでformset.deleted_forms
を処理するのはあなた次第です。フォームを削除することを意味します。
can_delete_extra
バージョン3.2の新機能。
- BaseFormSet.can_delete_extra
デフォルト:True
can_delete=True
の設定時に、can_delete_extra=False
を指定すると、余分なフォームを削除するオプションが削除されます。
フォームセットへのフィールドの追加
フォームセットにフィールドを追加する必要がある場合、これは簡単に実行できます。 フォームセットの基本クラスは、add_fields
メソッドを提供します。 このメソッドをオーバーライドして、独自のフィールドを追加したり、順序フィールドと削除フィールドのデフォルトのフィールド/属性を再定義したりすることもできます。
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def add_fields(self, form, index):
... super().add_fields(form, index)
... form.fields["my_field"] = forms.CharField()
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
>>> for form in formset:
... 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>
フォームセットフォームへのカスタムパラメータの受け渡し
フォームクラスがMyArticleForm
などのカスタムパラメータを受け取る場合があります。 フォームセットをインスタンス化するときに、次のパラメータを渡すことができます。
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class MyArticleForm(ArticleForm):
... def __init__(self, *args, user, **kwargs):
... self.user = user
... super().__init__(*args, **kwargs)
>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={'user': request.user})
form_kwargs
は、特定のフォームインスタンスにも依存する場合があります。 フォームセットの基本クラスは、get_form_kwargs
メソッドを提供します。 このメソッドは、単一の引数(フォームセット内のフォームのインデックス)を取ります。 empty_form のインデックスはNone
です。
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> class BaseArticleFormSet(BaseFormSet):
... def get_form_kwargs(self, index):
... kwargs = super().get_form_kwargs(index)
... kwargs['custom_kwarg'] = index
... return kwargs
フォームセットのプレフィックスのカスタマイズ
レンダリングされたHTMLでは、フォームセットの各フィールドの名前にプレフィックスが含まれています。 デフォルトでは、プレフィックスは'form'
ですが、フォームセットのprefix
引数を使用してカスタマイズできます。
たとえば、デフォルトの場合、次のように表示されます。
<label for="id_form-0-title">Title:</label>
<input type="text" name="form-0-title" id="id_form-0-title">
しかし、ArticleFormset(prefix='article')
を使用すると、次のようになります。
<label for="id_article-0-title">Title:</label>
<input type="text" name="article-0-title" id="id_article-0-title">
これは、ビューで複数のフォームセットを使用する場合に役立ちます。
ビューとテンプレートでフォームセットを使用する
ビュー内でフォームセットを使用することは、通常のForm
クラスを使用することと大差ありません。 注意したいのは、テンプレート内の管理フォームを使用することだけです。 サンプルビューを見てみましょう。
from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
if request.method == 'POST':
formset = ArticleFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
pass
else:
formset = ArticleFormSet()
return render(request, 'manage_articles.html', {'formset': formset})
manage_articles.html
テンプレートは次のようになります。
<form method="post">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
ただし、フォームセット自体に管理フォームを処理させることにより、上記のわずかなショートカットがあります。
<form method="post">
<table>
{{ formset }}
</table>
</form>
上記は、フォームセットクラスのas_table
メソッドを呼び出すことになります。
手動でレンダリングされたcan_deleteおよびcan_order
テンプレートのフィールドを手動でレンダリングする場合は、テンプレート:Form.DELETE
を使用してcan_delete
パラメーターをレンダリングできます。
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
<ul>
<li>{{ form.title }}</li>
<li>{{ form.pub_date }}</li>
{% if formset.can_delete %}
<li>{{ form.DELETE }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
同様に、フォームセットに注文機能(can_order=True
)がある場合は、テンプレート:Form.ORDER
でレンダリングできます。
ビューで複数のフォームセットを使用する
必要に応じて、ビューで複数のフォームセットを使用できます。 フォームセットは、その動作の多くをフォームから借用します。 そうは言っても、prefix
を使用して、フォームセットのフォームフィールド名に特定の値のプレフィックスを付け、名前が衝突することなく複数のフォームセットをビューに送信できるようにすることができます。 これがどのように達成されるかを見てみましょう。
from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm
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.
pass
else:
article_formset = ArticleFormSet(prefix='articles')
book_formset = BookFormSet(prefix='books')
return render(request, 'manage_articles.html', {
'article_formset': article_formset,
'book_formset': book_formset,
})
次に、フォームセットを通常どおりにレンダリングします。 正しくレンダリングおよび処理されるように、POSTケースと非POSTケースの両方でprefix
を渡す必要があることを指摘することが重要です。
各フォームセットのプレフィックスは、各フィールドのname
およびid
HTML属性に追加されるデフォルトのform
プレフィックスを置き換えます。