# Django 5.2.8 ドキュメント

# フォームセット (Formset)

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)
    ...
    <div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
    <div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>

見てのとおり、空のフォームがひとつだけ表示されました。表示される空のフォームの数は `extra` パラメータで制御できます。デフォルトでは、 `formset_factory()` は空のフォームを1つ定義します。次の例では、フォームセットクラスを作成して空のフォームを2つ表示します:

    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)

Formsets can be iterated and indexed, accessing forms in the order they were created. You can reorder the forms by overriding the default `iteration` and `indexing` behavior if needed.

## フォームセットで初期データを指定する

初期データはフォームセットの使いやすさを左右します。上で示したように、追加フォームの数を定義できます。これは、フォームセットが初期データから生成するフォームに加え、追加で表示するフォームの数を指定することを意味します。例を見てみましょう:

    >>> 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)
    ...
    <div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></div>
    <div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2023-02-11" id="id_form-0-pub_date"></div>
    <div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" id="id_form-1-title"></div>
    <div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></div>
    <div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
    <div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>

上の例では、今度は 3 つのフォームが表示されました。初期データとして渡された 1 つと、2 つの追加フォームです。初期データとして、辞書のリストを渡していることにも注意してください。

フォームセットを描画するために `initial` を使う場合、フォームセットの送信を処理するときに同じ `initial` を渡して、どのフォームがユーザによって変更されたかをフォームセットが検出できるようにしてください。例えば、`ArticleFormSet(request.POST, initial=[...])` のようになるでしょう。

参考

モデルフォームセットを使ってモデルからフォームセットを作る.

## フォームの最大表示数を制限する

`formset_factory()` に `max_num` パラメータを渡すと、フォームセットが表示するフォームの数を制限できます:

    >>> 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)
    ...
    <div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
    <div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>

もし、`max_num` の値が初期データ内に存在するオブジェクトの合計より大きい場合、 `extra` を上限として空のフォームがフォームセットに追加されます。 フォームの合計の長さは `max_num` を超えることはできません。例えば、`extra=2` と `max_num=2`、そしてフォームセットが 1 つの `initial` 項目で初期化される場合、この初期項目のフォームと 1 つの空のフォームが表示されます。

初期データ内の項目数が `max_num` を超える場合、`max_num` の値に関わらず全ての初期データのフォームが表示され、追加フォームは 1 つも表示されません。例えば、`extra=3` と `max_num=1`、そしてフォームセットが 2 つの初期項目で初期化される場合、2 つのフォームが初期データとともに表示されます。

`max_num` の値が `None` (デフォルト) だった場合、表示されるフォームの上限は大きな数になります (1000)。この数は、実際には制限がないと見なせるでしょう。

By default, `max_num` only affects how many forms are displayed and does not affect validation. If `validate_max=True` is passed to the `formset_factory()`, then `max_num` will affect validation. See validate_max.

## インスタンス化できるフォームの数を制限する

`formset_factory()` の `absolute_max` パラメータによって、 `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` データに存在することが期待されます。

ManagementFormは、フォームセット自体の属性として使用できます。 テンプレートでフォームセットをレンダリングするときは、`{{ my_formset.management_form }}` (my_formsetは適切な名前に置き換えます)をレンダリングすることで、すべての管理データを含めることができます。

注釈

ここで例として示した `form-TOTAL_FORMS` と `form-INITIAL_FORMS` フィールドの他に、管理フォームには `form-MIN_NUM_FORMS` と `form-MAX_NUM_FORMS` フィールドがあります。これらは管理フォームの残りの部分と一緒に出力されますが、これはクライアント側のコードの利便性のためだけのものです。これらのフィールドは必須ではないので、例の `POST` データには表示されていません。

### `total_form_count` と `initial_form_count`

`BaseFormSet` には、`ManagementForm`、`total_form_count`、`initial_form_count` と密接に関わる 2 つのメソッドがあります。

`total_form_count` は、対象のフィールドセット内のフォームの合計数を返します。`initial_form_count` は、記入前のフォームセット内のフォームの数を返し、またいくつのフォームが必須なのかを決めるためにも使われます。通常、これらのメソッドをオーバーライドする必要はありませんが、もし必要な場合はメソッドの動作を理解してからオーバーライドしてください。

### `empty_form`

`BaseFormSet` には追加の属性 `empty_form` があり、`__prefix__` というプレフィックスとともにフォームのインスタンスを返します。これにより、JavaScript で動的にフォームを操作することが容易となります。

### `error_messages`

引数 `error_messages` で、フォームセットが表示するデフォルトのメッセージを上書きできます。上書きしたいエラーメッセージにマッチするキーを持つ辞書を渡します。エラーメッセージのキーには `'too_few_forms'`, `'too_many_forms'`, `'missing_management_form'` があります。 `too_few_forms'` と `too_many_forms'` のエラーメッセージには `%(num)d` を含めることができ、それぞれ `min_num` と `max_num` に置き換えられます。

例えば、管理フォームがない場合のデフォルトのエラーメッセージは以下の通りです:

    >>> 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 = set()
    ...         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.add(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()` メソッドを使います。

フォーム以外のエラーは、フォーム固有のエラーと区別するために `nonform` という追加のクラスでレンダリングされます。たとえば、 `{{ formset.non_form_errors }}` は次のようになります:

    <ul class="errorlist nonform">
        <li>Articles in a set must have distinct titles.</li>
    </ul>

## フォームセット内のフォームの数を検証する

送信されたフォームの最小および最大数を検証するために、Django にはいくつかの方法が用意されています。フォームの数のバリデーションをさらにカスタマイズする必要があるときは、カスタムのフォームセットバリデーションを使用する必要があります。

### `validate_max`

`formset_factory()` に `validate_max=True` が渡された場合、データセット内のフォーム数から削除マークが付けられたフォーム数を除いた数が `max_num` 以下であるかどうかも検証されます。

    >>> 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` を超過した原因が初期データの量が多すぎたことだったとしてもです。

エラーメッセージは error_messages 引数に `'too_many_forms'` メッセージを渡すことでカスタマイズできます。

注釈

`validate_max` に関係なく、データセット内のフォームの数が `absolute_max` を超えた場合、 `validate_max` が指定されている場合と同様にフォームのバリデーションは失敗し、さらに最初の `absolute_max` 内のフォームのみがバリデートされます。残りは完全に切り捨てられます。これは POST リクエストを偽装してメモリを使い果たす攻撃から守るためです。詳細は インスタンス化できるフォームの数を制限する を参照してください。

### `validate_min`

`formset_factory()` に `validate_min=True` が渡された場合、バリデーションはデータセット内のフォームの数から削除マークが付けられたものを除いた数が `min_num` 以上であることもチェックします。

    >>> 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.']

エラーメッセージは error_messages 引数に `'too_few_forms'` メッセージを渡すことでカスタマイズできます。

注釈

`validate_min` に関係なく、フォームセットにデータが含まれていない場合、 `extra + min_num` 空のフォームが表示されます。

## フォームのソートと削除に対応させる

`formset_factory()` には、フォームセット内のフォームの順序およびフォームセットからのフォームの削除に役立つ 2 つのオプション引数があります。

### `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)
    ...
    <div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
    <div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
    <div><label for="id_form-0-ORDER">Order:</label><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></div>
    <div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
    <div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
    <div><label for="id_form-1-ORDER">Order:</label><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></div>
    <div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
    <div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
    <div><label for="id_form-2-ORDER">Order:</label><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></div>

これで、各フォームにフィールドが追加されます。この新しいフィールドは `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)},
    ...     ],
    ... )
    >>> for form in formset.ordered_forms:
    ...     print(form.cleaned_data)
    ...
    {'title': 'Article #3', 'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0}
    {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1}
    {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2}

`BaseFormSet` には `ordering_widget` 属性と `get_ordering_widget()` メソッドがあり、 `can_order` で使用するウィジェットを制御します。

#### `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)
    ...
    <div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
    <div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
    <div><label for="id_form-0-DELETE">Delete:</label><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></div>
    <div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
    <div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
    <div><label for="id_form-1-DELETE">Delete:</label><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></div>
    <div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
    <div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
    <div><label for="id_form-2-DELETE">Delete:</label><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></div>

`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]
    [{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'DELETE': True}]

`ModelFormSet` を使っている場合、 `formset.save()` を呼び出すと、削除されたフォームのモデルインスタンスは削除されます。

If you call `formset.save(commit=False)`, objects will not be deleted automatically. You'll need to call `delete()` on each of the `formset.deleted_objects` to actually delete them:

    >>> instances = formset.save(commit=False)
    >>> for obj in formset.deleted_objects:
    ...     obj.delete()
    ...

一方、普通の `FormSet` を使用している場合は、フォームを削除するという一般的な概念がないため、あなた自身でフォームセットの `save()` メソッドで `formset.deleted_forms` を処理することになります。

`BaseFormSet` には `deletion_widget` 属性と `get_deletion_widget()` メソッドもあり、 `can_delete` で使用するウィジェットを制御します。

#### `deletion_widget`

BaseFormSet.deletion_widget

デフォルト値: `CheckboxInput`

`delete_widget` をセットすると、 `can_delete` で使用するウィジェットクラスを指定できます:

    >>> from django.forms import BaseFormSet, formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     deletion_widget = HiddenInput
    ...

    >>> ArticleFormSet = formset_factory(
    ...     ArticleForm, formset=BaseArticleFormSet, can_delete=True
    ... )

#### `get_deletion_widget`

BaseFormSet.get_deletion_widget()

`can_delete` で使用するウィジェットのインスタンスを指定する必要がある場合は、 `get_deletion_widget()` をオーバーライドします:

    >>> from django.forms import BaseFormSet, formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     def get_deletion_widget(self):
    ...         return HiddenInput(attrs={"class": "deletion"})
    ...

    >>> ArticleFormSet = formset_factory(
    ...     ArticleForm, formset=BaseArticleFormSet, can_delete=True
    ... )

### `can_delete_extra`

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)
    ...
    <div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
    <div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
    <div><label for="id_form-0-my_field">My field:</label><input type="text" name="form-0-my_field" id="id_form-0-my_field"></div>

## フォームセットのフォームにカスタムパラメータを渡す

フォームクラスには、`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
    ...

    >>> ArticleFormSet = formset_factory(MyArticleForm, formset=BaseArticleFormSet)
    >>> formset = ArticleFormSet()

## フォームセットのプレフィックスをカスタマイズする

レンダリングされた 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">

これは ビュー の中で複数のフォームセットを使いたいときに便利です。

## ビューとテンプレートでフォームセットを使う

フォームセットには、レンダリングに関連する以下の属性とメソッドがあります:

BaseFormSet.renderer

  フォームセットに使用する レンダラー を指定します。デフォルトは `FORM_RENDERER` 設定で指定されたレンダラーです。

BaseFormSet.template_name

  `print(formset)` やテンプレート内で `{{ formset }}` を使ってフォームセットを文字列にキャストした場合にレンダリングされるテンプレートの名前です。

  デフォルトでは、レンダラーの `formset_template_name` の値を返すプロパティです。テンプレート名に文字列を指定することで、特定のフォームセットクラスのテンプレート名を上書きできます。

  このテンプレートは、フォームセットの管理フォームをレンダリングするために使用され、フォームセット内の各フォームは、フォームの `template_name` で定義されたテンプレートに従ってレンダリングされます。

BaseFormSet.template_name_div

  `as_div()` を呼び出す際に使用するテンプレートの名前です。デフォルトでは `"django/forms/formsets/div.html"` です。このテンプレートはフォームセットの管理フォームをレンダリングし、 フォームセット内の各フォームをフォームの `as_div()` メソッドに従ってレンダリングします。

BaseFormSet.template_name_p

  `as_p()` を呼び出す際に使用するテンプレートの名前です。デフォルトでは `"django/forms/formsets/p.html"` です。このテンプレートはフォームセットの管理フォームをレンダリングし、 フォームセット内の各フォームをフォームの `as_p()` メソッドに従ってレンダリングします。

BaseFormSet.template_name_table

  `as_table()` を呼び出す際に使用するテンプレートの名前です。デフォルトでは `"django/forms/formsets/table.html"` です。このテンプレートはフォームセットの管理フォームをレンダリングし、 フォームセット内の各フォームをフォームの `as_table()` メソッドに従ってレンダリングします。

BaseFormSet.template_name_ul

  `as_ul()` を呼び出す際に使用するテンプレートの名前です。デフォルトでは `"django/forms/formsets/ul.html"` です。このテンプレートはフォームセットの管理フォームをレンダリングし、 フォームセット内の各フォームをフォームの `as_ul()` メソッドに従ってレンダリングします。

BaseFormSet.get_context()

  テンプレート内のフォームセットをレンダリングするためのコンテキストを返します。

  利用可能なコンテキストは次のとおりです。

  - `formset`: フォームセットのインスタンス。

BaseFormSet.render(template_name=None, context=None, renderer=None)

  render メソッドは `str__` メソッド、 `as_div()` メソッド、 `as_p()` メソッド、 `as_ul()` メソッド、 `as_table()` メソッドから呼び出されます。すべての引数は省略可能で、デフォルトは下記です:

  - `template_name`: `template_name`

  - `context`: `get_context()` が返す値

  - `renderer`: `renderer` が返す値

BaseFormSet.as_div()

  フォームセットを `template_name_div` テンプレートでレンダリングします。

BaseFormSet.as_p()

  フォームセットを `template_name_p` テンプレートでレンダリングします。

BaseFormSet.as_table()

  フォームセットを `template_name_table` テンプレートでレンダリングします。

BaseFormSet.as_ul()

  フォームセットを `template_name_ul` テンプレートでレンダリングします。

ビュー内でフォームセットを使用するのは、通常の `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>

上記は、フォームセットクラスで `BaseFormSet.render()` メソッドを呼び出すことになります。これにより、`template_name` 属性で指定されたテンプレートを使用してフォームセットがレンダリングされます。フォームと同様に、デフォルトではフォームセットは `as_div` としてレンダリングされ、その他にも `as_p`, `as_ul`, `as_table` などの補助メソッドが使用可能です。フォームセットのレンダリングは、`template_name` 属性を指定することでカスタマイズできます。また、より一般的には デフォルトのテンプレートのオーバーライド によってカスタマイズできます。

### `can_delete` や `can_order` のときに手動でレンダリングする

テンプレート内で手動でフィールドをレンダリングする場合、 `can_delete` パラメータを `{{ form.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 }}` でレンダリングできます。

### ビューで複数のフォームセットを使う

必要に応じて、1 つのビューで複数のフォームセットを使用できます。フォームセットはその動作の多くをフォームから借用しています。そのため、`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 の場合とそうでない場合の両方で `prefix` を渡す必要があるということです。

各フォームセットの prefix は、各フィールドの `name` と `id` の HTML 属性に追加されるデフォルトの `form` プレフィックスを置き換えます。