モデルからフォームを作成する¶
ModelForm
¶
データベース中心のアプリケーションを作成している場合、Django モデルに密接にマップするフォームを使うことになるでしょう。例えば、BlogComment
モデルを持っていて、閲覧者がコメントを送信できるフォームを作成したくなったとしましょう。この場合、すでにモデル内にフィールドが定義されているので、フォーム内にフィールドタイプを改めて定義するのは冗長でしょう。
このためDjango には、Django モデルから Form
クラスを生成できるようなヘルパークラスを用意してあります。
例えば次のようにします:
>>> from django.forms import ModelForm
>>> from myapp.models import Article
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ["pub_date", "headline", "content", "reporter"]
...
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
フィールドの型¶
生成された Form
クラスは、指定された全てのモデルフィールドに対して、fields
属性で指定された順番でフォームフィールドを有します。
各モデルフィールドは、対応するデフォルトのフォームフィールドを持ちます。例えば、モデル上の CharField
はフォーム上で CharField
として表現されます。モデル ManyToManyField
は MultipleChoiceField
として表現されます。 以下は、各フィールドの対応表です:
モデルフィールド |
フォームフィールド |
---|---|
フォーム上では表示されません。 |
|
フォーム上では表示されません。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
フォーム上では表示されません。 |
|
|
|
お気付きかもしれませんが、 ForeignKey
と ManyToManyField
の 2 つのモデルフィールドは特殊なケースとなります:
ForeignKey
はdjango.forms.ModelChoiceField
によって表現され、モデルのQuerySet
を選択肢として持つChoiceField
です。ManyToManyField
はdjango.forms.ModelMultipleChoiceField
によって表現され、モデルのQuerySet
を選択肢として持つMultipleChoiceField
です。
加えて、生成されたフォームフィールドはそれぞれ以下の通り属性がセットされます:
モデルフィールドに
blank=True
が設定されている場合、フォームフィールド上でrequired
がFalse
にセットされます。それ以外の場合はrequired=True
となります。フォームフィールドの
label
はモデルフィールドのverbose_name
の最初の文字を大文字にしたものがセットされます。フォームフィールドの
help_text
はモデルフィールドのhelp_text
がセットされます。モデルフィールドに
choices
が設定されている場合、フォームフィールドのwidget
はSelect
にセットされ、選択肢にはモデルフィールドのchoices
が使われます。通常、選択肢には空欄の選択肢が追加され、デフォルトで選択されることになります。フィールドが required の場合、ユーザは選択肢を選ぶ必要があります。モデルフィールドにblank=False
および明示的なdefault
値が設定されている場合、空欄の選択肢は表示されません (代わりにdefault
値が選択された状態で表示されます) 。
与えられたモデルフィールドに対して使われるフォームフィールドをオーバーライドすることもできます。後述の Overriding the default fields を参照してください。
完全な具体例¶
以下のような一連のモデルを考えていきましょう:
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 __str__(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
fields = ["name", "title", "birth_date"]
class BookForm(ModelForm):
class Meta:
model = Book
fields = ["name", "authors"]
これらのモデルでは、上記の 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())
ModelForm
の検証 (バリデーション)¶
ModelForm
のバリデーションには、以下の 2 つのステップが存在します:
通常のフォームのバリデーションと同じように、モデルフォームのバリデーションは is_valid()
が呼ばれたときや errors
属性にアクセスしたとき、暗黙的に実行されます。また、実際にはあまり使うメソッドではありませんが、明示的に full_clean()
を呼んだときにもバリデーションが実行されます。
Model
バリデーション (Model.full_clean()
) は、フォームバリデーションのステップ内でフォームの clean()
メソッドが呼ばれたすぐ直後に実行されます。
警告
クリーニングのプロセスは、様々な方法で ModelForm
コンストラクタに渡されたモデルのインスタンスを修正します。例えば、モデル上のあらゆる日付フィールドは、実際の date オブジェクトに変換されます。失敗したバリデーションは、一貫性のない状態でモデルインスタンスを元のままにしておくため、これを再利用するのはお勧めできません。
clean()
メソッドをオーバーライドする¶
通常のフォームと同じ方法でモデルフォーム上の clean()
メソッドをオーバーライドして、バリデーションの動作を追加できます。
モデルのオブジェクトに付属したモデルフォームのインスタンスは、instance
属性を持ち、メソッドが特定のモデルのインスタンスにアクセスできるようにします。
警告
ModelForm.clean()
メソッドは、モデルバリデーション に、unique
、unique_together
または unique_for_date|month|year
としてマークされたモデルフィールドの一意性を検証させます。
clean()
メソッドをオーバーライドして、なおかつこのバリデーションを維持したい場合、親クラスの clean()
メソッドを呼ぶ必要があります。
モデルバリデーションとのやり取り¶
バリデーションプロセスの一部として、ModelForm
フォーム上で対応するフィールドを持つモデルのそれぞれのフィールドの clean()
メソッドを呼び出します。モデルフィールドを除外した場合、これらのフィールドに対してはバリデーションが実行されません。フィールドのクリーニングとバリデーションの詳しい働き方については、フォームバリデーション ドキュメントを参照してください。
モデルの clean()
メソッドは一意性のチェックがされる前に呼び出されます。モデルの clean()
フックのより詳しい情報については、オブジェクトのバリデーションを行う を参照してください。
モデルの error_messages
についての注意¶
フォームフィールド
や フォーム Meta のレベルで定義されたエラーメッセージは、常に モデルフィールド
のレベルで定義されたエラーメッセージより優先されます。
モデルフィールド
で定義されたエラーメッセージは、モデルバリデーション のステップの間に ValidationError
が発生したときに、対応するエラーメッセージがフォームのレベルで定義されていない場合にのみ使用されます。
NON_FIELD_ERRORS
キーを Meta
クラス内の error_messages
ディクショナリに追加することで、モデルバリデーションによって発生する NON_FIELD_ERRORS
に対するエラーメッセージをオーバーライドできます。
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
"unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
save()
メソッド¶
全ての ModelForm
は save()
メソッドも持っています。このメソッドは、フォームに結びつけられたデータからデータベースオブジェクトを生成して保存します。ModelForm
のサブクラスはキーワード引数 instance
として既存のモデルインスタンスを受け取ることができます; これが指定されると、save()
はそのインスタンスを更新するようになります。 指定されない場合は、save()
は指定されたモデルの新しいインスタンスを生成します:
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
フォームが バリデーションされていない 場合、save()
を呼び出すと、form.errors
をチェックすることでこれを行います。フォーム内のデータが適正ではない場合は ValueError
が発生します -- 例えば、form.errors
が True
だと評価された場合です。
オプションのフィールドがフォームのデータに表示されない場合、結果のモデルインスタンスはモデルフィールドの default
を使用します。この動作は、 CheckboxInput
, CheckboxSelectMultiple
, または SelectMultiple
(または value_omitted_from_data()
メソッドが常に False
を返すカスタムウィジェット) を使用するフィールドには適用されません。チェックされていないチェックボックスと、選択されていない <select multiple>
は HTML フォーム送信のデータには表示されないからです。 API を設計していて、これらのウィジェットを使用するフィールドのデフォルトのフォールバック動作が必要な場合は、カスタムフォームフィールドまたはウィジェットを使用します。
この save()
には、省略可能な commit
キーワード引数があり、True
ないし False
を取ります。save()
を commit=False
で呼び出した場合、データベースにまだ保存されていないオブジェクトを返します。この場合、結果のモデルインスタンスで save()
を呼び出すかどうかはあなた次第です。これは、保存前にオブジェクトで独自のプロセスを実行したい場合、もしくは特別な モデル保存オプション を使いたい場合に有用です。 commit
はデフォルトでは True
です。
commit=False
を使う際のもう 1 つの副作用は、モデルに他のモデルとの多対多の関係がある場合に見られます。フォームを save するときにモデルに多対多の関係があり commit=False
を指定した場合、Django は多対多の関係に対してフォームのデータを即座に保存することができません。これは、インスタンスがデータベース上に存在するようになるまで、インスタンスに対して多対多のデータを保存することが不可能だからです。
この問題を扱うために、commit=False
を使ったフォームを save するたびに、Django は save_m2m()
メソッドをあなたの ModelForm
サブクラスに追加します。フォームによって生成されたインスタンスを手動で save した後に、多対多のフォームデータを保存するには save_m2m()
を実行します。例えば:
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = "some_value"
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
save_m2m()
を呼び出す必要があるのは save(commit=False)
を使ったときだけです。フォーム上で save()
を使用すると、多対多のデータを含むすべてのデータが保存されます。 たとえば:
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
save()
と save_m2m()
メソッドのほかは、ModelForm
は forms
のフォームとまったく同じ方法で動作します。例えば、is_valid()
メソッドは妥当性をチェックするために使用される、is_multipart()
メソッドはフォームがマルチパートのファイルアップロードを必要とするかどうかを (ゆえに request.FILES
がフォームに渡される必要があるかどうかも) 決める、などです。詳しくは アップロードされたファイルをフォームにバインドする を参照してください。
使うフィールドを選択する¶
fields
属性を使って、フォームで編集すべき全てのフィールドを明示的に指定することを強くお勧めします。そうしないと、フォームで予期せず特定のフィールドを設定できるようになり、セキュリティ上の問題が発生しやすくなります。特に新しいフィールドがモデルに追加されたときに起こりがちです。フォームのレンダリング方法によっては、Webページで問題が目に見えないことさえあります。
別のアプローチとしては、すべてのフィールドを自動的に含めるか、一部のフィールドだけを削除する方法があります。この基本的なアプローチは安全性がかなり低いことが知られており、主要なウェブサイトで深刻な悪用が行われています (GitHub など)。
しかし、これらのセキュリティ上の懸念があなたに当てはまらないと保証できる場合には、2 つのショートカットが利用可能です:
fields
属性に、特殊な値'__all__'
をセットして、モデル内の全てのフィールドが使われるように指定します。例えば:from django.forms import ModelForm class AuthorForm(ModelForm): class Meta: model = Author fields = "__all__"
ModelForm
の中のMeta
クラスのexclude
属性に、フォームから除外されるフィールドのリストをセットします。例:
class PartialAuthorForm(ModelForm): class Meta: model = Author exclude = ["title"]
Author
モデルは 3 つのフィールドname
、title
、birth_date
を持っているため、これでname
とbirth_date
がフォームに表示されることになります。
このどちらかが使われた場合、フォーム内で表示されるフィールドの順番はモデル内でフィールドが定義された順番となり、ManyToManyField
インスタンスは最後に表示されます。
加えて、Django は以下のルールも適用します: モデルフィールドで editable=False
をセットした場合、ModelForm
を通じて生成された 全ての フォームはそのフィールドを含みません。
注釈
上記のロジックでフォームに含まれない全てのフィールドは、フォームの save()
メソッドでセットされません。また、除外したフィールドを手動でフォームに追加し直した場合、モデルインスタンスから初期化されることはありません。
Django は不完全なモデルを保存しないようにするため、モデルの設定でフィールドを空にすることを許容しておらず、かつ除外されたフィールドに対するデフォルト値を指定しなかった場合、除外されたフィールドを含む ModelForm
を 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)
の使用について、詳しくは section on saving forms を参照してください。
デフォルトのフィールドをオーバライドする¶
前述の Field types テーブルで説明したとおり、デフォルトのフィールドタイプは実用的なデフォルトです。モデルに DateField
がある場合、フォーム内で DateField
として表示したいことでしょう。しかし、ModelForm
では、与えられたモデルに足してフォームフィールドを変更する柔軟性があります。
フィールドに対して独自のウィジェットを指定するには、内部の Meta
クラスの widgets
を使用してください。これは、フィールド名としジェットのクラスやインスタンスをマッピングするディクショナリです。
例えば、Authoer
の name
属性に対する CharField
を、デフォルトの <input type="text">
の代わりに <textarea>
で表示したい場合、フィールドのウィジェットをオーバーライドします:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
widgets = {
"name": Textarea(attrs={"cols": 80, "rows": 20}),
}
widgets
辞書はウィジェットのインスタンス(例: Textarea(...)
)またはクラス(例: Textarea
)を受け付けます。空でない choices
属性を持つモデルフィールドでは widgets
辞書は無視されることに注意してください。この場合、フォームフィールドをオーバーライドして別のウィジェットを使用する必要があります。
同様に、フィールドをさらにカスタマイズしたい場合、内部の Meta
クラスの labels
、help_texts
、error_messages
と言った属性を指定できます。
例えば、name
フィールドに対する文字列に直面する全てのユーザの文章をカスタマイズしたい場合:
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
labels = {
"name": _("Writer"),
}
help_texts = {
"name": _("Some useful help text."),
}
error_messages = {
"name": {
"max_length": _("This writer's name is too long."),
},
}
また、 field_classes
や formfield_callback
を指定して、フォームがインスタンス化するフィールドのタイプをカスタマイズすることもできます。
例えば、slug
フィールドに対して MySlugFormField
を使いたい場合、以下のようにできます:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
field_classes = {
"slug": MySlugFormField,
}
もしくは:
from django.forms import ModelForm
from myapp.models import Article
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
formfield_callback = formfield_for_dbfield
最後に、フィールドを完全に -- タイプ、バリデータ、必須かどうかなどを含めて -- コントロールしたい場合、通常の Form
で行うのと同じようにフィールドを宣言的に指定することで実現できます。
フィールドのバリデータを指定したい場合、フィールドを宣言的に定義して validators
パラメータを設定することで実現できます:
from django.forms import CharField, ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
注釈
以下のように明示的にフォームフィールドをインスタンス化したとき、ModelForm
と 通常の Form
が連動する方法を理解することが重要となります。
ModelForm
は、特定のフィールドを自動的に生成できる通常の Form
です。自動的に生成されるフィールドは、Meta
クラスの内容とどのフィールドが宣言的に定義されているかに依存します。基本的に、ModelForm
は フォームから 除外された フィールド、言い換えると宣言的に定義されなかったフィールド のみ を生成します。
宣言的に定義されたフィールドはそのままになります。したがって、Meta
属性に対するカスタマイズ、例えば widgets
、labels
、help_texts
、error_messages
は無視されます; これらは自動的に生成されたフィールドにのみ適用されます。
同様に、宣言的に定義されたフィールドは、対応するモデルから引き継がれた max_length
や required
のような属性を描画しません。モデル内で定義された動作を引き継ぎたい場合は、フォームフィールドを宣言する際に明示的に関連する属性をセットする必要があります。
例えば、Article
モデルは以下のようになります:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text="Use puns liberally",
)
content = models.TextField()
そして、指定されたとおりに blank
と help_text
が維持されている一方で、headline
に対して独自のバリデーションを実施するために、ArticleForm
を以下のように定義します:
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text="Use puns liberally",
)
class Meta:
model = Article
fields = ["headline", "content"]
フォームフィールドのタイプを使用して、対応するモデルフィールドの内容を設定できることを確認する必要があります。 互換性がない場合、暗黙的な変換が行われないので、 "ValueError" を取得します。
フィールドとその属性について、詳しくは フォームフィールドのドキュメンテーション を参照してください。
フィールドのローカライズを有効にする¶
デフォルトでは、ModelForm
内のフィールドはデータをローカライズしません。フィールドのデータをローカライズするには、Meta
クラスの localized_fields
属性が使用できます。
>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
... class Meta:
... model = Author
... localized_fields = ['birth_date']
localized_fields
が特殊な値 '__all__'
にセットされた場合、全てのフィールドがローカライズされます。
フォームの継承¶
基本的なフォームと同様に、 ModelForms
を継承して拡張したり再利用したりすることができます。これは、モデルから派生した多くのフォームで使用するために、親クラスに追加フィールドや追加メソッドを宣言する必要がある場合に便利です。たとえば、以前の ArticleForm
クラスを使います:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self): ...
...
これは ArticleForm
と同じように動作するフォームを作成しますが、pub_date
フィールドのバリデーションとクリーニングが追加されます。
Meta.fields
や Meta.exclude
のリストを変更したい場合は、親の Meta
インナークラスをサブクラス化することもできます:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ["body"]
...
これは EnhancedArticleForm
からの追加メソッドを追加し、1つのフィールドを削除するようにオリジナルの ArticleForm.Meta
を修正します。
ただ、注意すべき点がいくつかあります。
通常の Python の名前解決ルールが適用されます。内部クラス
Meta
を宣言している基底クラスが複数ある場合、最初のものだけが使用されます。つまり、子クラスのMeta
が存在する場合はそれが使用され、そうでない場合は最初の親のMeta
などが使用されます。Form
とModelForm
の両方から同時に継承することは可能ですが、必ずModelForm
が MRO (Method Resolution Order) の中で最初に現れるようにする必要があります。なぜなら、これらのクラスは異なるメタクラスに依存しており、一つのクラスは一つのメタクラスしか持つことができないからです。親クラスから継承した
Field
を宣言的に削除するには、サブクラスで名前をNone
に設定します。このテクニックを使用できるのは、親クラスによって宣言的に定義されたフィールドからオプトアウトする場合のみです。
ModelForm
メタクラスがデフォルトフィールドを生成するのを防ぐことはできません。デフォルトのフィールドからオプトアウトするには、 使うフィールドを選択する を参照してください。
初期値を指定する¶
通常のフォームと同様に、フォームのインスタンスを作成する際に initial
パラメータを指定することで、フォームの初期データを指定できます。この方法で指定された初期値は、フォームフィールドからの初期値と、アタッチされたモデルインスタンスからの値の両方を上書きします。たとえば、下記のようにします:
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={"headline": "Initial headline"}, instance=article)
>>> form["headline"].value()
'Initial headline'
ModelForm factory 関数¶
クラス定義ではなく、スタンドアロン関数 modelform_factory()
を使って、与えられたモデルからフォームを作成することもできます。あまりカスタマイズをしないなら、こちらの方が便利でしょう:
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=["author", "title"])
これは既存のフォームを修正するためにも使用できます。たとえば、指定されたフィールドに使用するウィジェットを指定できます:
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
含むフィールドは fields
や exclude
のキーワード引数、もしくは ModelForm
内部の Meta
クラスの対応する属性を使って指定できます。ModelForm
使うフィールドを選択する ドキュメントを参照してください。
... 特定のフィールドのローカライズを有効にすることもできます:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
モデルのフォームセット¶
- class models.BaseModelFormSet¶
通常のフォームセット のように、 Django は Django モデルでの作業をより便利にするために、いくつかの拡張 フォームセットクラスを提供しています。上の Author
モデルを再利用してみましょう:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
fields
を使用すると、フォームセットが指定されたフィールドだけを使うように制限されます。または、除外するフィールドを指定する "オプトアウト" アプローチをとることもできます:
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
これは Author
モデルに関連付けられたデータを扱うことができるフォームセットを作成します。通常のフォームセットと同じように動作します:
>>> 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-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div>
<div><label for="id_form-0-title">Title:</label><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></div>
注釈
modelformset_factory()
は formset_factory()
を使ってフォームセットを生成します。つまり、モデルのフォームセットは基本フォームセットを拡張したもので、特定のモデルとどのように相互作用するかを知っています。
注釈
マルチテーブル継承 を使用している場合、フォームセットファクトリによって生成されたフォームは id
フィールドの代わりに親リンクフィールド (デフォルトでは <parent_model_name>_ptr
) を含みます。
クエリセットを変更する¶
デフォルトでは、モデルからフォームセットを作成する場合、フォームセットはモデル内のすべてのオブジェクトを含むクエリセットを使用します (例 Author.objects.all()
) 。 queryset
引数を使用することで、この動作をオーバーライドできます:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
もしくは、__init__
の中で self.queryset
をセットするサブクラスを作成できます:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith="O")
そして、ファクトリ関数に BaseAuthorFormSet
クラスを渡します:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=["name", "title"], formset=BaseAuthorFormSet
... )
既存のモデルのインスタンスを 一切 含まないフォームセットを返したい場合は、空の QuerySet を指定します:
>>> AuthorFormSet(queryset=Author.objects.none())
フォームを変更する¶
デフォルトでは、 modelformset_factory
を使うと、 modelform_factory()
を使ってモデルフォームが作成されます。多くの場合、カスタムモデルフォームを指定すると便利です。たとえば、カスタムバリデーションを持つカスタムモデルフォームを作成できます:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
...
次に、モデルフォームをファクトリ関数に渡します:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
必ずしもカスタムモデルフォームを定義する必要はありません。 modelformset_factory
関数には modelform_factory
に渡されるいくつかの引数があります。以下で説明します。
widgets
を使って、フォームで使用するウィジェットを指定する¶
パラメータ widgets
を使うことで、特定のフィールドに対して ModelForm
のウィジェットクラスをカスタマイズするための値の辞書を指定できます。これは ModelForm
の内部 Meta
クラスの widgets
辞書と同じように動作します:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
... )
localized_fields
でフィールドのローカライズを有効にする¶
localized_fields
パラメータを使って、フォームのフィールドのローカライズを有効にできます。
>>> AuthorFormSet = modelformset_factory(
... Author, fields=['name', 'title', 'birth_date'],
... localized_fields=['birth_date'])
localized_fields
が特殊な値 '__all__'
にセットされた場合、全てのフィールドがローカライズされます。
初期値を指定する¶
通常のフォームセットと同様に、 modelformset_factory()
によって返されるモデルフォームセットクラスをインスタンス化する際に initial
パラメータを指定することで、 初期データ をフォームセット内のフォームに指定できます。しかし、モデルフォームセットでは、初期値は既存のモデルインスタンスにアタッチされていない余分なフォームにだけ適用されます。もし initial
の長さが余分なフォームの数を超えた場合、余分な初期データは無視されます。初期データを持つ余分なフォームがユーザーによって変更されなかった場合、それらはバリデーションも保存もされません。
フォームセット内のオブジェクトを保存する¶
ModelForm
と同様に、データをモデルオブジェクトとして保存できます。これはフォームセットの save()
メソッドで行います:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
save()
メソッドはデータベースに保存されたインスタンスを返します。指定したインスタンスのデータがバインドされたデータで変更されなかった場合、そのインスタンスはデータベースに保存されず、戻り値 (上記の例では instances
) にも含まれません。
フォームにフィールドがない場合 (例えば除外されている場合など)、これらのフィールドは save()
メソッドではセットされません。通常の ModelForms
にも適用されるこの制限についての詳細は Selecting the fields to use を参照してください。
未保存のモデルインスタンスを返すには commit=False
を渡します:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
...
これにより、データベースに保存する前にインスタンスにデータを付加できます。フォームセットに ManyToManyField
が含まれている場合は、 formset.save_m2m()
を呼び出して、多対多のリレーションシップが適切に保存されるようにする必要があります。
save()
を呼び出すと、モデルのフォームセットにはフォームセットの変更を表す3つの新しい属性が追加されます:
- models.BaseModelFormSet.changed_objects¶
- models.BaseModelFormSet.deleted_objects¶
- models.BaseModelFormSet.new_objects¶
編集可能なオブジェクトの数を制限する¶
通常のフォームセットと同様に、 modelformset_factory()
の max_num
と extra
パラメータを使って、表示される追加のフォームの数を制限できます。
max_num
は既存のオブジェクトが表示されないようにするものではありません:
>>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
また、 extra=0
は新しいモデルインスタンスの作成を妨げるものではありません。 JavaScriptでフォームを追加 したり、追加のPOSTデータを送信したりすることができます。この方法については 新しいオブジェクトの作成を防ぐ を参照してください。
もし max_num
の値が既存の関連オブジェクトの数よりも大きい場合、フォームの総数が max_num
を超えない限り、最大 extra
個の空白のフォームがフォームセットに追加されます:
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-name">Name:</label><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"></div>
<div><label for="id_form-1-name">Name:</label><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"></div>
<div><label for="id_form-2-name">Name:</label><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"></div>
<div><label for="id_form-3-name">Name:</label><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"></div>
max_num
の値が None
(デフォルト) だった場合、表示されるフォームの上限は大きな数になります (1000)。この数は、実際には制限がないと見なせるでしょう。
新しいオブジェクトの作成を防ぐ¶
edit_only
パラメータを使用すると、新しいオブジェクトを作成しないようにすることができます:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... edit_only=True,
... )
ここでは、フォームセットは既存の Author
インスタンスのみを編集します。他のオブジェクトは作成も編集もされません。
ビューでモデルフォームセットを使う¶
モデルのフォームセットはフォームセットにとても似ています。例えば、Author
モデルのインスタンスを編集するフォームセットを表示したいとしましょう:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, "manage_authors.html", {"formset": formset})
見てのとおり、モデルフォームセットのビューロジックは、「通常の」フォームセットと大きく異なるものではありません。唯一の違いは、 formset.save()
を呼び出してデータをデータベースに保存できることです。(これは上記の フォームセット内のオブジェクトを保存する で説明しています)。
ModelFormSet
の clean()
をオーバーライドする¶
モデルフォームの ModelForms
と同様に、デフォルトでは ModelFormSet
の clean()
メソッドは、フォームセット内のアイテムがモデルのユニーク制約 (unique
, unique_together
または unique_for_date|month|year
) に違反していないことを検証します。 もし ModelFormSet
の clean()
メソッドをオーバーライドし、この検証を維持したい場合は、親クラスの clean
メソッドを呼び出す必要があります:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
また、このステップに到達するまでに、各 Form
に対して個別のモデルインスタンスが既に作成されていることに注意してください。 form.cleaned_data
の値を変更するだけでは、保存された値に影響を与えることはできません。 ModelFormSet.clean()
の値を変更したい場合は、 form.instance
を変更する必要があります:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
name = form.cleaned_data["name"].upper()
form.cleaned_data["name"] = name
# update the instance value.
form.instance.name = name
カスタムのクエリセットを使う¶
前述のように、モデルのフォームセットで使われるデフォルトのクエリセットはオーバーライドできます:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
この例では POST
と GET
の両方で queryset
引数を渡していることに注意してください。
テンプレートでフォームセットを使う¶
Django テンプレートでフォームセットをレンダリングするには 3 つの方法があります。
1つめは、フォームセットにほとんどの作業を任せる方法です:
<form method="post">
{{ formset }}
</form>
2 つめは、フォームセットを手動でレンダリングして、フォーム自身に処理させる方法です:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
手動でフォームをレンダリングする場合は、必ず上記のように管理フォームをレンダリングしてください。 管理フォームのドキュメント を参照してください。
3 つめは、各フィールドを手動でレンダリングする方法です:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
この3つめのメソッドを使用し、 {% for %}
ループでフィールドをイテレートしない場合、プライマリキーフィールドをレンダリングする必要があります。たとえば、モデルの name
フィールドと age
フィールドをレンダリングする場合、以下のようにします:
<form method="post">
{{ 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
という名前ではない独自のプライマリキーを明示的に定義している場合は、それが確実にレンダリングされるようにしてください)。
インラインフォームセット¶
- class models.BaseInlineFormSet¶
インラインフォームセットは、モデルフォームセット上の小さな抽象化レイヤーです。これを使うと、外部キーで関連するオブジェクトを操作することが簡単になります。以下の 2 つのモデルがあるとします:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
特定の著者の本を編集できるフォームセットを作成したい場合、下記のように書くことができます:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=["title"])
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
BookFormSet
の prefix は 'book_set'
(<model name>_set
) です。ただし、 Book
の Author
への ForeignKey
に related_name
がある場合、その名前が代わりに使用されます。
注釈
inlineformset_factory()
は、 modelformset_factory()
を使い、can_delete=True
をセットします。
InlineFormSet
のメソッドをオーバーライドする¶
InlineFormSet
のメソッドをオーバーライドするには、 BaseModelFormSet
ではなく、 BaseInlineFormSet
をサブクラス化します。
例えば、 clean()
をオーバーライドするとします:
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
ModelFormSet の clean() をオーバーライドする も参照してください。
そして、インラインフォームセットを作成するときに、オプションの引数 formset
を渡します:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(
... Author, Book, fields=["title"], formset=CustomInlineFormSet
... )
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
複数の外部キーをひとつのモデルが持っている場合¶
同じモデルが 1 つよりも多くの外部キーを持っている場合、 fk_name
を使い手動で曖昧さを解消しなければなりません。例として、次のモデルについて考えます:
class Friendship(models.Model):
from_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="from_friends",
)
to_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="friends",
)
length_in_months = models.IntegerField()
これを解決するには、 inlineformset_factory()
に fk_name
を使用します:
>>> FriendshipFormSet = inlineformset_factory(
... Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
... )
ビューでインラインフォームセットを使う¶
モデルに紐付いたオブジェクトを編集する機能をユーザーに提供するビューを作るには、こうします:
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"])
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something. Should generally end with a redirect. For example:
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render(request, "manage_books.html", {"formset": formset})
POST
と GET
いずれにおいても instance
を渡していることに注目してください
インラインフォームにウィジェットを指定する¶
inlineformset_factory
は modelformset_factory
を使っており、多くの引数は modelformset_factory
に渡されます。これは modelformset_factory
と同様に、 widgets
引数を渡すことができることを意味しています。詳細は、上記 Specifying widgets to use in the form with widgets を参照してください。