モデルからフォームを生成する

revision-up-to:17812 (1.4) unfinished

ModelForm

class ModelForm

データベース駆動のアプリケーションを構築しているのなら、 Django のモデルに 対応したフォームが必要な場合があるでしょう。例えば、 BlogComment モデル を作っていて、読者がコメントを入力できるようなフォームを作成したいような場 合です。こうしたケースでは、すでにモデルにフィールドを定義しているので、新 たにフォームクラス用にフィールドを定義するのは無駄な作業でしかありません。

この理由から、 Django はモデルからフォームクラスを生成するためのヘルパクラ スを提供しています。

以下に例を示します:

>>> from django.forms import ModelForm

# フォームクラスを生成
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article

# 記事を追加するためのフォームを作成
>>> form = ArticleForm()

# 既存の記事を変更するためのフォームを作成
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)

フィールド型

モデルから生成されるフォームクラスは、モデルの各フィールドに対応したフォー ムフィールドを持ちます。モデルフィールドには、それぞれデフォルトのフォーム フィールド型が定義されています。例えば、モデルの CharField 型には、 CharField 型のフォームフィールドが対応しています。モデルの ManyToManyField には、 MultipleChoiceField が対応しています。以下に 対応の一覧表を示します:

モデルフィールド型 フォームフィールド型
AutoField フォーム上では表現されていません
BigIntegerField IntegerFieldmin_value が -9223372036854775808 、 max_value が 9223372036854775807 にセットされています。
BooleanField BooleanField
CharField CharFieldmax_length にはモ デルフィールドの max_length 値が設 定されます。
CommaSeparatedIntegerField CharField
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
EmailField EmailField
FileField FileField
FilePathField CharField
FloatField FloatField
ForeignKey ModelChoiceField (下記参照)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField (下記参照)
NullBooleanField CharField
PhoneNumberField USPhoneNumberField (django.contrib.localflavor.us)
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallIntegerField IntegerField
TextField widget=TextareaCharField
TimeField TimeField
URLField URLFieldverify_exists には モデルフィールドの verify_exists 値が設定されます。
BigIntegerField は Django 1.2 で新たに定義されました。

お気付きかもしれませんが、 ForeignKeyManyToManyField といったモ デルフィールドは特別扱いされています:

  • ForeignKeydjango.forms.ModelChoiceField で表現されて います。 ModelChoiceField は、選択肢がモデルの QuerySet であ るような ChoiceField です。
  • ManyToManyFielddjango.forms.ModelMultipleChoiceField で表現されています。 ModelMultipleChoiceField は、選択肢がモデル の QuerySet であるような MultipleChoiceField です。

さらに、生成されるフォームフィールドには、以下の属性が付加されます:

  • モデルフィールドに blank=True が設定されていると、フォームフィー ルドの requiredFalse に設定されます。それ以外の場合には、 required=True です。
  • フォームフィールドの label にはモデルフィールドの verbose_name の値を使います。このとき先頭の文字は大文字に変換され ます。
  • フォームフィールドの help_text にはモデルフィールドの help_text の値を使います。
  • モデルフィールドに choices が設定されている場合、フォームフィール ドの widgetSelect に設定されます。また、選択肢にはモデル フィールドの choices の値を使います。通常、選択肢の中には、空の 選択肢が加えられ、フォームの表示時にデフォルトで選択されています。 フィールドが必須のフィールドである場合、ユーザは必ず空の選択肢以外か ら選択せねばなりません。モデルフィールドが blank=False で、かつ default 値が明に設定されている場合、空の選択肢は表示されません (default の値が選択された状態で表示されます)。

あるモデルからフォームを作成する場合、モデルのフォームフィールドに対応する フォームフィールドをオーバライドできます。後述の デフォルトのフィールド型をオーバライドする を参照してください。

詳細な例

以下のような一連のモデルを考えましょう:

from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = (
    ('MR', 'Mr.'),
    ('MRS', 'Mrs.'),
    ('MS', 'Ms.'),
)

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)

    def __unicode__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author

class BookForm(ModelForm):
    class Meta:
        model = Book

上に挙げた例中の ModelForm サブクラスは、以下のフォームクラスとほぼ同じ になります (違いは save() メソッドの動作だけです。これについてはすぐ後 で説明します):

from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(max_length=3,
                widget=forms.Select(choices=TITLE_CHOICES))
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

is_valid() メソッドと errors

最初に ModelFormis_valid() を呼び出すか、 あるいは errors 属性にアクセスしたときに モデルのバリデーション と同様にフォームバリデーション が行われます。 これには ModelForm コンストラクタに渡したモデルをクリーニングするという 副作用があります。例えば、あなたのフォームに対して is_valid() を呼ぶと 日付フィールドが実際の日付フィールドに変換されてしまいます。

save() メソッド

ModelForm から生成されたフォームは save() メソッドを備えています。 このメソッドは、フォームに結びつけられたデータから、モデルオブジェクトを生 成してデータベースに保存します。 ModelForm のサブクラスは既存のモデルイ ンスタンスをキーワード引数 instance にしてインスタンス化できます。 instance を指定してモデルフォームを生成すると、モデルフォームの save() はこのインスタンスを更新して保存します。 instance を指定しな ければ、 save() はモデルフォームで指定しているモデルの新たなインスタン スを生成します:

# POST データから新たなフォームインスタンスを生成
>>> f = ArticleForm(request.POST)

# フォームデータから新たな Article オブジェクトを生成
>>> new_article = f.save()

# 既存の Article オブジェクトからフォームを生成
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(instance=a)
>>> f.save()

# 既存の Article オブジェクトを編集するためのフォームを作成します。
# ただし、 POST データを使ってフォームに値を設定します。
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

form.errors が True であると評価されると、 save()ValueError を送出す るので注意してください。

save() メソッドはオプション commit キーワード引数を持っています。こ の引数には True または False を指定します。 save()commit=False で呼び出すと、データベースに保存する前のモデルオブジェクト を返します。返されたオブジェクトに対して、最終的に save() を呼び出すか どうかは自由です。この機能は、オブジェクトを実際に保存する前に何らかの処理 を行いたい場合や、特殊なオプション付きで モデルオブジェクトを保存したい 場合に便利 です。 commit はデフォルトでは True に設定されています。

モデルが他のモデルに対する多対多のリレーションを持っている場合、 commit=False を使うともう一つの副作用があります。多対多のリレーションを 持つモデルから生成したフォームを保存する際、 Django は多対多のリレーション に対するデータをすぐに保存できません。なぜなら、 save(commit=False) の 対象であるオブジェクトはまだデータベースに保存されていないため、オブジェク トに対する多対多の関係を保存するためのテーブルを更新できないからです。

この問題を回避するために、Django は commit=False でフォームを保存した直 後に、 save_m2m() メソッドを ModelForm のサブクラスへ追加します。フォーム から生成したインスタンスを手動で保存した後で save_m2m() を呼び出せば、 多対多のフォームデータを保存できます。以下に例を示します:

# POST データから新たなフォームインスタンスを生成
>>> f = AuthorForm(request.POST)

# インスタンスを生成、ただし保存はしない
>>> new_author = f.save(commit=False)

# new_author のフィールド値を変更
>>> new_author.some_field = 'some_value'

# 新たなインスタンスを保存
>>> new_author.save()

# 最後に、多対多のフォームデータを保存
>>> f.save_m2m()

save_m2m() の呼び出しが必要なのは、 save(commit=False) を使った場合 だけです。単に save() を呼び出すだけなら、多対多のデータを含む全てのデー タが保存され、他に何らかのメソッドを呼び出す必要はありません。以下に例を示 します:

# POST データから新たなフォームインスタンスを生成
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# 新たな Author インスタンスを生成して保存。他にはなにもしなくてよい
>>> new_author = f.save()

save() および save_m2m() メソッド以外は、 ModelForm は他の forms で作られたフォームと全く同じように動作します。例えば、 データの検証は is_valid() で行えますし、 is_multipart() メソッドを使えばフォームがマルチパートのファイルアップロードを要求している かどうか (そして request.FILES をフォームに渡さなければならないかどうか) 判別できます。詳しくは、 アップロードされたファイルをフォームに結びつける を参照してください。

モデルの一部のフィールドだけからフォームを生成する

場合によっては、フォームを生成するときに、モデルの一部のフィールドだけを表 示したいことでしょう。モデルフィールドの一部だけを使って ModelForm を作 るには、以下の 3 つの方法があります:

  1. モデルフィールドに editable=False を定義します。その結果、 モデルフォームを使って生成したフォームは 全て このフィールドを含ま なくなります。
  2. ModelForm サブクラスで、内部クラス Metafields 属性を 設定します。この属性にはフィールド名からなるリストを設定します。設定 すると、指定されたフィールドだけを含むフォームを生成します。 fieldsに指定された名前の順番はフォームがレンダーされる時に尊重されます。
  3. ModelForm サブクラスで、内部クラス Metaexclude 属性を 設定します。この属性にはフィールド名からなるリストを設定します。設定 すると、指定されたフィールドを含まないフォームを生成します。

例えば、 (上で定義した) Author モデルから、 nametitle フィールドだけを含むようなフォームを作成したければ、以下の ようにして fields または exclude を指定します:

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ('birth_date',)

Author モデルには 'name', 'title', 'birth_date' の 3 つ のフィールドしかないので、上のフォームは全く同じフォームフィールド を持ちます。

Note

ModelForm を使ってフォームを生成するときに fieldsexclude を指定すると、生成されたフォームの save() を呼び出した ときに、フォームに含まれていないフィールドのデータは設定されません。 また、除外フィールドをフォームに手動で追加し直してもモデルインスタンス から初期化されることはありません。

このため、フォームに含まれていないフィールドに対応するモデルフィールド が、空の値を許さないように定義されていて、かつデフォルトの値が指定され ていない場合、モデルインスタンスが不完全なために Django がオブジェクト の保存を抑止し、 save() は失敗します。これを避けるには、フォームの インスタンスを生成するときに、不足しているフィールドでかつ必須のフィー ルドに対する初期値を指定してください:

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

あるいは、 save(commit=False) を使って、必須のフィールドの値を手動 で設定してください:

form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

save(commit=False) については、 フォームを保存する も参照してください。

デフォルトのフィールド型をオーバライドする

widgets 属性はDjango 1.2で新たに導入されました。

上の フィールド型 で説明したデフォルトのフィールド型は、いわゆる気の利い たデフォルト値にすぎません。モデル上で DateField として定義されている フィールドは、フォーム上でも DateField として表現されてほしいというのが 普通でしょう。とはいえ、 ModelForm は、あるモデルフィールドのフォーム フィールド型を変更できるという柔軟性を備えています。

フィールドに対するカスタムウィジェットを指定するには、 Meta インナークラス の widgets 属性を使います。 これはフィールド名をウィジェットクラスあるいは インスタンスにマッピングさせるディクショナリです。

例えば、 AuthorCharFieldname 属性を、 デフォルトの <input type="text"> では無く <textarea> で表現したい のであれば、 フィールドのウィジェットをオーバーライドします

from django.forms import ModelForm, Textarea

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

widgets ディクショナリはウィジェットのインスタンス(たとえば、 Textarea(...) )かクラス (例えば、 Textarea) を指定可能です。

さらにフィールドをカスタマイズしたい – 型、ラベルなど – のであれば、 通常の Form と同様にフィールドの宣言によって行うことができます。 宣言されたフィールドは、 model 属性から生成さ れたデフォルトの フィールドをオーバライドします。

例えば、 pub_date フィールドに MyDateFormField を使いたければ、 以下のようにして実現できます:

>>> class ArticleForm(ModelForm):
...     pub_date = MyDateFormField()
...
...     class Meta:
...         model = Article

フィールドのデフォルトのラベルをオーバーライドしたいのであれば、 label パラメータをフォームフィールドで宣言します

>>> class ArticleForm(ModelForm):
...     pub_date = DateField(label=u'出版日')
...
...     class Meta:
...         model = Article

Note

このように明示的にフォームフィールドをインスタンス化すると、 Djangoはそのフィールドの振る舞いを完全に定義したいのだと判断します: 従って、(max_lengthrequired のような)デフォルトの属性が 対応するモデルから持ってこられません。 もしもモデルで定義された振る舞いを維持したいのであれば、 フォームフィールドを宣言する際に適切な引数を明示的に設定する必要があります。

例えば、 Article が次のように定義されているならば:

class Article(models.Model):
    headline = models.CharField(max_length=200, null=True, blank=True,
                                help_text="Use puns liberally")
    content = models.TextField()

headline に対するカスタムバリデーションを行う必要があると同時に blankhelp_text も維持する必要があるので、 ArticleForm は このようになります

class ArticleForm(ModelForm):
    headline = MyFormField(max_length=200, required=False,
                           help_text="Use puns liberally")

    class Meta:
        model = Article

フィールドとその引数に関しての詳細な情報は、 フォームフィールドドキュメント を参照してください。

フィールドの並びを変更する

デフォルトの動作としては、 ModelFrom はモデルに定義されたのと同じ順番で フィールドをレンダリングして、 ManyToManyField インスタンスに関しては 最後に現れるようにレンダリングします。 レンダリングされるフィールドの並びを変更したいのであれば、 fields 属性を Meta クラスで使います。

fields 属性はレンダリングされるモデルフィールドのサブセットと その並び順 を定義します。例えば次のようなモデルがあると、

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=100)

author フィールドが最初にレンダリングされます。 titleフィールドを最初にレンダリングしたいのであれば、以下の ModelForm を定義することができます。

>>> class BookForm(ModelForm):
...     class Meta:
...         model = Book
...         fields = ('title', 'author')

clean() メソッドのオーバライド

バリデーションの動作を追加するために、モデルフォームの clean() メソッド を通常のフォームと同じ方法でオーバライドできます。

In this regard, model forms have two specific characteristics when compared to forms:

デフォルトの``clean()`` メソッドは、モデルの uniqueunique_together 、あるいは unique_for_date|month|year に指定した内容に従って、オブジェクトの 一意性をチェックします。従って、 clean() をオーバライドして、なおかつ デフォルトのバリデーションも行いたいときは、親クラスの clean() メソッドを 必ず呼び出してください。

Also, a model form instance bound to a model object will contain a self.instance attribute that gives model form methods access to that specific model instance.

フォームクラスの継承

フォームクラスと同様、 ModelForm も継承によって再利用できます。フォーム クラスの継承は、一つのモデルからいくつもフォームを導出して、フォーム毎にフィー ルドを追加したり、メソッドを追加したりする際に便利です:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

上記のコードは、独自に pub_date フィールドの検証とクリーニングを行うほ かは、 ArticleForm と全く同じフォームを生成します。

親クラスの内部クラス Meta をサブクラス化すれば、親クラスの Meta.fieldsMeta.excludes リストを変更できます:

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ('body',)

上記のコードは、 EnhancedArticleForm を追加して、 ArticleForm.Meta フィールドを変更することで、 body フィールドを除去しています。

ただし、フォームクラスの継承にはいくつか注意すべき点もあります。

  • 属性の名前解決には、通常の Python の名前解決規則が適用されます。すなわち、 内部クラス Meta を持つ複数の基底クラスを持つようなサブクラスを定義す ると、最初のクラスの Meta だけを使います。従って、 Meta の解決は まずサブクラスの Meta 、そして最初の親クラスの Meta の順に行われ ます。
  • 技術的な理由から、サブクラスは ModelFormForm を同時に継承で きません。

これらの注意点は、サブクラスを作成する際に何かトリッキーなことをしない限り は当てはまらないはずです。

モデルのバリデーションとの相互作用

バリデーションプロセスの一部として、 ModelForm はフォームの各フィールドに 対応するモデルフィールドの clean() メソッドを呼び出します。 モデルフィールドを除外したのであれば、それらフィールドのバリデーションが 行われません。 フィールドのクリーニングとバリデーションがどのように行われて いるのかに関する詳細は フォームバリデーション ドキュメントを参照してください。 また、モデルの clean() メソッドは一意性のチェックが行われる前に呼ばれます。 モデルの clean() フックに関する詳細な情報については、 オブジェクトのバリデーション を参照してください。

モデルフォームセット

通常のフォームセット と同様、モデル由来のフォー ムをうまく扱えるように拡張された特殊なフォームセットクラスがあります。上の Author モデルを使って説明しましょう:

>>> from django.forms.models import modelformset_factory
>>> AuthorFormSet = modelformset_factory(Author)

これで、 Author モデルに関連づけられたデータを扱えるフォームセットが生 成されます。 AuthorFormSet は、個々のフォームが Form ではなく ModelForm なだけで、通常のフォームセットと同じように使えます:

>>> formset = AuthorFormSet()
>>> print formset
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected="selected">---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select></td></tr>
<tr><th><label for="id_form-0-birth_date">Birth date:</label></th><td><input type="text" name="form-0-birth_date" id="id_form-0-birth_date" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>

Note

modelformset_factoryformset_factory を使ってフォームセット を生成します。つまり、モデルフォームセットは単に特定のモデルを扱えるよ うに拡張したフォームセットにすぎないのです。

クエリセットを変更する

デフォルトでは、モデルからフォームセットを生成すると、そのクエリセットはモ デルの全てのオブジェクト、すなわち Author.objects.all() です。このクエ リセットは、以下のように変更できます:

>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))

サブクラスを作って、 __init__ の中で self.queryset を設定する方法で も変更できます。:

from django.forms.models import BaseModelFormSet

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith='O')

ファクトリ関数を呼ぶときに、 BaseAuthorFormSet を渡してベースクラスにし てください:

>>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)

モデルについての 全ての 既存のインスタンスを含まないフォームセットを 返したいのであれば、 空の QuerySetを指定することができます

>>> AuthorFormSet(queryset=Author.objects.none())

フォームセットに含めるフィールドを fieldsexclude で制御する

デフォルトでは、モデルフォームセットは、モデル中の editable=False でな い全てのフィールドを使おうとします。ただし、フォームセットレベルでこの挙動 はオーバライドできます:

>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

このように、 fields を使えば、フォームセットに組み込むフィールドを指定 したものだけに制限できます。一方:

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

このように、 exclude を使えば、指定したフィールドをフォームセットから除 外できます。

初期値を用意する

リリースノートを参照してください

通常のフォームセットと同様に、 modelformset_factory が返すモデルフォームセット をインスタンス化するときに initial パラメターを指定することでフォームセット に含まれるフォームの 初期データを指定する ことが 可能です。 しかし、モデルフォームセットでは初期値は拡張フォームにのみ適用され、既存の オブジェクトインスタンスにはバインドされません。

フォームセット内のオブジェクトを保存する

ModelForm と同様、フォームセット内のデータからモデルへの保存を行えます。 保存するには、フォームセットの save() メソッドを呼び出します:

# POST データからフォームセットインスタンスを生成します。
>>> formset = AuthorFormSet(request.POST)

# フォームデータが有効なものと課程して、データを保存します。
>>> instances = formset.save()

save() メソッドはデータベースに保存されたインスタンスを返します。インス タンスがフォームセットに結びつけられたデータで変更されなかった場合、そのイ ンスタンスはデータベースに保存されず、戻り値 (上の例の instances) にも 含まれません。

フォームからフィールドが欠けている場合(例えば、excludedされているなどの 理由により) 、そのフィールドは save() メソッド でセットされません。 この制約は通常の ModelForms でも適用される物ですがこれに関するより詳細な 情報は モデルの一部のフィールドだけからフォームを生成する で見つけること ができます。

save()commit=False を渡せば、データベースは触らずに、モデルイン スタンスだけを返させられます:

# データベースに保存しません
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # インスタンスごとに必要な操作を行います。
...     instance.save()

この方法を使えば、データベースにインスタンスを保存する前に、各々のインスタ ンスにデータを付加できます。また、フォームセットに ManyToManyField が含 まれている場合は、 formset.save_m2m() を使って多対多のリレーションを正 しく保存させる必要があるでしょう。

編集可能なオブジェクトの数を制限する

リリースノートを参照してください

通常のフォームセットと同様に、 max_numextra パラメータを使って modelformset_factory 表示されるフォームの数を制限することができます。

max_num は既存のオブジェクトが表示されることを妨げることはしません:

>>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]

>>> AuthorFormSet = modelformset_factory(Author, max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
[u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman']

max_num の値が既存の関連するオブジェクトよりも大きい場合、 extra で指定 した数までの追加のブランクフォームがフォームセットに追加されますが、 フォームの 全数は max_num を超えることはありません:

>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print form.as_table()
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>
リリースノートを参照してください

max_num の値が None (デフォルト)だと、表示されるフォームの数には制限 が課せられません。

ビュー内でモデルフォームセットを使う

モデルフォームセットは、フォームセットと良く似ています。例として、 ユーザが Author モデルインスタンスを編集できるようなフォームセットを考 えましょう:

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author)
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    render_to_response("manage_authors.html", {
        "formset": formset,
    })

このように、ビューの構造は通常のフォームセットを使ったときとほとんど変わり ません。違うのは、 formset.save() を使って、データをデータベースに保存 していることくらいです。 formset.save() は、 フォームセット内のオブジェクトを保存する で解説しています。

model_formsetclean() をオーバーライドする

ModelFroms と同様に、デフォルトの動作として model_formsetclean() メソッドはフォームセットの中のフォームがモデルのユニーク属性 ( uniqueunique_togeter 、あるいは unique_for_date|month|year) を侵害していないことを検証します。 model_formsetclean() メソッドをオーバーライドした上でこの検証の 仕組みを維持したいのであれば、 親クラスの clean メソッドを呼ぶ必要が あります。:

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()
        # example custom validation across forms in the formset:
        for form in self.forms:
            # your custom formset validation

カスタムクエリセットを使う

前にも述べた通り、モデルフォームセットで使用されるデフォルトのクエリセットを オーバーライドすることができます

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author)
    if request.method == "POST":
        formset = AuthorFormSet(request.POST, request.FILES,
                                queryset=Author.objects.filter(name__startswith='O'))
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
    return render_to_response("manage_authors.html", {
        "formset": formset,
    })

この例では POSTGET の両方において queryset パラメータを 渡すことができます。

テンプレートでフォームセットを使う

Djangoテンプレートでフォームセットをレンダーするには3つの方法があります。

最初の方法はフォームセットにほとんどの作業を任せることです

<form method="POST" action="">
    {{ formset }}
</form>

第2の方法はフォームセットのレンダリングを手動で行いますが、フォーム 自体のレンダリングはそれらに任せる方法です

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset.forms %}
        {{ form }}
    {% endfor %}
</form>

フォームを手動でレンダリングするのであれば、上で示したように管理フォーム (management form) をレンダリングするしてください。 管理フォームドキュメント を 参照のこと。

第3の方法は、各フィールドを手動でレンダリングする方法です

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }}: {{ field }}
        {% endfor %}
    {% endfor %}
</form>

もしも第3の方法を使って {% for %} ループでフィールドを反復したくない のであれば、 プライマリキーフィールドをレンダリングする必要があります。 例えば、 モデルの nameage フィールドをレンダリングするのであれば:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

明示的に {{ form.id}} をレンダリングしなければならないことに注意してください。 これによりモデルフォームセットが POST の場合正しく動作します ( この例はプライマリキーが id であると仮定しています。 もしも id 以外の プライマリキーを明示的に定義してあるのならば、 必ずそれをレンダリングして下さい)。

インラインフォームセット

インラインフォームセット(inline formsets)はモデルフォームセットの頂点に位置する 小さな抽象化レイヤーです。これは外部キーで関連するオブジェクトを動作させること を簡素化しています。 以下の2つのモデルがある物とします

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=100)

特定の作者(author) の本を扱うフォームセットを生成したければ、以下のようにし ます:

>>> from django.forms.models import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book)
>>> author = Author.objects.get(name=u'Mike Royko')
>>> formset = BookFormSet(instance=author)

Note

inlineformset_factorymodelformset_factory を使っており、 can_delete=True がセットされています。

同じモデルに対して複数の外部キーを張っている場合

同じモデルに対する複数の外部キーを張っている場合、キー間のあいまいさを無く すために、 fk_name を手動で設定する必要があります。以下のモデルを例に考 えてみましょう:

class Friendship(models.Model):
    from_friend = models.ForeignKey(Friend)
    to_friend = models.ForeignKey(Friend)
    length_in_months = models.IntegerField()

このモデルの問題を解決するには、 fk_nameinlineformset_factory に設定してください:

>>> FrienshipFormSet = inlineformset_factory(Friend, Friendship, fk_name="from_friend")

ビューでインラインフォームセットを使う

ユーザーに対してモデルの関連オブジェクトを編集させるためのビューを提供したい ことがあるかと思います。 以下のようにすると可能です

def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book)
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = BookInlineFormSet(instance=author)
    return render_to_response("manage_books.html", {
        "formset": formset,
    })

POSTGET の両方のケースで instance を渡していることに注目 してください。