モデルからフォームを作成する
ModelForm
- class 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モデルのフィールドタイプは特殊なケースです。
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から選択されます。 選択肢には通常、デフォルトで選択されている空白の選択肢が含まれます。 フィールドが必須の場合、これによりユーザーは選択を強制されます。 モデルフィールドに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 __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コンストラクターに渡されるモデルインスタンスをさまざまな方法で変更します。 たとえば、モデルの日付フィールドはすべて実際の日付オブジェクトに変換されます。 検証に失敗すると、基になるモデルインスタンスが一貫性のない状態になる可能性があるため、再利用することはお勧めしません。
clean()メソッドのオーバーライド
モデルフォームのclean()メソッドをオーバーライドして、通常のフォームと同じ方法で追加の検証を提供できます。
モデルオブジェクトにアタッチされたモデルフォームインスタンスには、その特定のモデルインスタンスへのアクセスをメソッドに与えるinstance属性が含まれます。
警告
ModelForm.clean()メソッドは、モデル検証ステップで、unique、unique_together、またはunique_for_date|month|year。
clean()メソッドをオーバーライドしてこの検証を維持する場合は、親クラスのclean()メソッドを呼び出す必要があります。
モデル検証との相互作用
検証プロセスの一環として、ModelFormは、フォームに対応するフィールドがあるモデルの各フィールドのclean()メソッドを呼び出します。 モデルフィールドを除外した場合、それらのフィールドに対して検証は実行されません。 フィールドクリーニングと検証の仕組みの詳細については、フォーム検証のドキュメントを参照してください。
モデルのclean()メソッドは、一意性チェックが行われる前に呼び出されます。 モデルのclean()フックの詳細については、オブジェクトの検証を参照してください。
モデルのerror_messagesに関する考慮事項
フォームフィールドレベルまたはフォームメタレベルで定義されたエラーメッセージは、モデルフィールドレベルで定義されたエラーメッセージよりも常に優先されます。
モデルフィールドで定義されたエラーメッセージは、モデル検証ステップ中にValidationErrorが発生し、対応するエラーメッセージがフォームレベルで定義されていない場合にのみ使用されます。
ModelFormの内部Metaクラス:
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をチェックすることで検証されることに注意してください。 フォーム内のデータが検証されない場合、つまりform.errorsがTrueと評価された場合、ValueErrorが発生します。
オプションのフィールドがフォームのデータに表示されない場合、結果のモデルインスタンスは、モデルフィールド default (存在する場合)をそのフィールドに使用します。 この動作は、 CheckboxInput 、 CheckboxSelectMultiple 、または SelectMultiple (または value_omitted_from_data()メソッドを持つカスタムウィジェット)を使用するフィールドには適用されません。チェックボックスがオフで、選択されていない<select multiple>は、HTMLフォーム送信のデータに表示されないため、常にFalse)を返します。 APIを設計していて、これらのウィジェットの1つを使用するフィールドのデフォルトのフォールバック動作が必要な場合は、カスタムフォームフィールドまたはウィジェットを使用します。
このsave()メソッドは、オプションのcommitキーワード引数を受け入れます。これは、TrueまたはFalseのいずれかを受け入れます。 commit=Falseを指定してsave()を呼び出すと、データベースにまだ保存されていないオブジェクトが返されます。 この場合、結果のモデルインスタンスでsave()を呼び出すのはあなた次第です。 これは、オブジェクトを保存する前にカスタム処理を実行する場合、または特殊なモデル保存オプションのいずれかを使用する場合に役立ちます。 commitはデフォルトでTrueです。
commit=Falseを使用することの別の副作用は、モデルが別のモデルと多対多の関係にある場合に見られます。 モデルに多対多の関係があり、フォームを保存するときにcommit=Falseを指定した場合、Djangoは多対多の関係のフォームデータをすぐに保存できません。 これは、インスタンスがデータベースに存在するまで、インスタンスの多対多のデータを保存できないためです。
この問題を回避するために、commit=Falseを使用してフォームを保存するたびに、Djangoはsave_m2m()メソッドをModelFormサブクラスに追加します。 フォームによって生成されたインスタンスを手動で保存した後、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ページに表示されない場合もあります。
別のアプローチは、すべてのフィールドを自動的に含めるか、一部のみを削除することです。 この基本的なアプローチは安全性がはるかに低いことが知られており、主要な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モデルにはname、title、birth_dateの3つのフィールドがあるため、フィールドnameとbirth_dateがフォームに表示されています。
これらのいずれかが使用されている場合、フィールドがフォームに表示される順序は、フィールドがモデルで定義されている順序になり、ManyToManyFieldインスタンスが最後に表示されます。
さらに、Djangoは次のルールを適用します。モデルフィールドにeditable=Falseを設定すると、モデルからModelFormを介して作成された任意のフォームにはそのフィールドが含まれません。
ノート
上記のロジックによってフォームに含まれていないフィールドは、フォームのsave()メソッドによって設定されません。 また、除外されたフィールドを手動でフォームに追加し直すと、モデルインスタンスから初期化されません。
Djangoは不完全なモデルを保存しようとする試みを防ぐので、モデルが欠落しているフィールドを空にすることを許可せず、欠落しているフィールドのデフォルト値を提供しない場合、save() a ModelFormフィールドが欠落していると失敗します。 この失敗を回避するには、欠落しているが必須のフィールドの初期値を使用してモデルをインスタンス化する必要があります。
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)の使用の詳細については、フォームの保存に関するセクションを参照してください。
デフォルトフィールドのオーバーライド
上記のフィールドタイプの表で説明されているように、デフォルトのフィールドタイプは適切なデフォルトです。 モデルにDateFieldがある場合、フォームでDateFieldとして表現したい場合があります。 ただし、ModelFormを使用すると、特定のモデルのフォームフィールドを柔軟に変更できます。
フィールドのカスタムウィジェットを指定するには、内部のMetaクラスのwidgets属性を使用します。 これは、フィールド名をウィジェットクラスまたはインスタンスにマッピングする辞書である必要があります。
たとえば、Authorの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を指定して、フォームによってインスタンス化されるフィールドのタイプをカスタマイズすることもできます。
たとえば、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,
}
最後に、フィールドのタイプ、バリデーター、必須などを含めて、フィールドを完全に制御する必要がある場合。 –通常の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は、フォームから欠落している、つまり宣言的に定義されていないフィールドをのみ生成します。
宣言的に定義されたフィールドはそのまま残されるため、widgets、labels、help_texts、error_messagesなどのMeta属性にカスタマイズが加えられます。 ]は無視されます。 これらは、自動的に生成されるフィールドにのみ適用されます。
同様に、宣言的に定義されたフィールドは、対応するモデルから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()
headlineのカスタム検証を行い、blankとhelp_textの値を指定どおりに維持したい場合は、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と同じように動作するフォームが作成されます。
Meta.fieldsまたはMeta.excludeリストを変更する場合は、親のMeta内部クラスをサブクラス化することもできます。
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ('body',)
これにより、EnhancedArticleFormから余分なメソッドが追加され、元のArticleForm.Metaが変更されて1つのフィールドが削除されます。
ただし、注意すべき点がいくつかあります。
通常のPython名前解決ルールが適用されます。
Meta内部クラスを宣言する複数の基本クラスがある場合、最初のクラスのみが使用されます。 これは、子のMetaが存在する場合はそれを意味し、存在しない場合は最初の親のMetaなどを意味します。FormとModelFormの両方から同時に継承することは可能ですが、ModelFormがMROの最初に表示されることを確認する必要があります。 これは、これらのクラスが異なるメタクラスに依存しており、クラスが持つことができるメタクラスは1つだけであるためです。サブクラスで名前を
Noneに設定することにより、親クラスから継承されたFieldを宣言的に削除することができます。この手法を使用できるのは、親クラスによって宣言的に定義されたフィールドからオプトアウトする場合のみです。
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ファクトリ関数
クラス定義を使用する代わりに、スタンドアロン関数 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">
<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>---------</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"></td></tr>
ノート
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)
モデルの any の既存のインスタンスを含まないフォームセットを返す場合は、空の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にも当てはまりますが、使用するフィールドの選択を参照してください。
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
編集可能なオブジェクトの数を制限する
通常のフォームセットと同様に、max_numおよびextraパラメーターを modelformset_factory()に使用して、表示される追加のフォームの数を制限できます。
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データを送信したりできるため、新しいモデルインスタンスの作成を妨げることはありません。 フォームセット :ticket: `まだ機能を提供していません<26142>` 新しいインスタンスの作成を防ぐ「編集専用」ビューの場合。
max_numの値が既存の関連オブジェクトの数よりも大きい場合、フォームの総数が[を超えない限り、extraまでの追加の空白のフォームがフォームセットに追加されます。 X204X] :
>>> 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.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(デフォルト)では、表示されるフォームの数に上限があります(1000)。 実際には、これは制限なしと同等です。
ビューでモデルフォームセットを使用する
モデルフォームセットはフォームセットと非常によく似ています。 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'))
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(request, 'manage_authors.html', {'formset': formset})
この例では、POSTとGETの両方のケースでqueryset引数を渡すことに注意してください。
テンプレートでフォームセットを使用する
Djangoテンプレートでフォームセットをレンダリングする方法は3つあります。
まず、フォームセットにほとんどの作業を任せることができます。
<form method="post">
{{ formset }}
</form>
次に、フォームセットを手動でレンダリングできますが、フォームはそれ自体を処理します。
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
自分でフォームを手動でレンダリングする場合は、必ず上記のように管理フォームをレンダリングしてください。 管理フォームのドキュメントを参照してください。
第三に、各フィールドを手動でレンダリングできます。
<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のプレフィックスは'book_set'(<model name>_set)です。 BookのForeignKeyからAuthorに related_name がある場合は、代わりにそれが使用されます。
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)
同じモデルへの複数の外部キー
モデルに同じモデルへの複数の外部キーが含まれている場合は、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()
これを解決するには、fk_nameから inlineformset_factory()を使用できます。
>>> 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に渡します。 つまり、widgetsパラメーターは、modelformset_factoryに渡すのとほぼ同じ方法で使用できます。 上記のウィジェット付きフォームで使用するウィジェットの指定を参照してください。