モデルからフォームを作成する
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
に渡すのとほぼ同じ方法で使用できます。 上記のウィジェット付きフォームで使用するウィジェットの指定を参照してください。