revision-up-to: | 17812 (1.4) unfinished |
---|
ModelForm
¶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 |
IntegerField 。
min_value が -9223372036854775808 、
max_value が 9223372036854775807
にセットされています。 |
BooleanField |
BooleanField |
CharField |
CharField 。 max_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=Textarea の CharField |
TimeField |
TimeField |
URLField |
URLField 。 verify_exists には
モデルフィールドの verify_exists
値が設定されます。 |
BigIntegerField
は Django 1.2 で新たに定義されました。お気付きかもしれませんが、 ForeignKey
や ManyToManyField
といったモ
デルフィールドは特別扱いされています:
ForeignKey
は django.forms.ModelChoiceField
で表現されて
います。 ModelChoiceField
は、選択肢がモデルの QuerySet
であ
るような ChoiceField
です。ManyToManyField
は django.forms.ModelMultipleChoiceField
で表現されています。 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 __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
¶最初に ModelForm
の is_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 つの方法があります:
editable=False
を定義します。その結果、
モデルフォームを使って生成したフォームは 全て このフィールドを含ま
なくなります。ModelForm
サブクラスで、内部クラス Meta
の fields
属性を
設定します。この属性にはフィールド名からなるリストを設定します。設定
すると、指定されたフィールドだけを含むフォームを生成します。
fieldsに指定された名前の順番はフォームがレンダーされる時に尊重されます。ModelForm
サブクラスで、内部クラス Meta
の exclude
属性を
設定します。この属性にはフィールド名からなるリストを設定します。設定
すると、指定されたフィールドを含まないフォームを生成します。例えば、 (上で定義した) Author
モデルから、 name
と
title
フィールドだけを含むようなフォームを作成したければ、以下の
ようにして 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
を使ってフォームを生成するときに fields
や
exclude
を指定すると、生成されたフォームの 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
属性を使います。 これはフィールド名をウィジェットクラスあるいは
インスタンスにマッピングさせるディクショナリです。
例えば、 Author
の CharField
の name
属性を、
デフォルトの <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_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
フィールドとその引数に関しての詳細な情報は、 フォームフィールドドキュメント を参照してください。
デフォルトの動作としては、 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()
メソッド
を通常のフォームと同じ方法でオーバライドできます。
In this regard, model forms have two specific characteristics when compared to forms:
デフォルトの``clean()`` メソッドは、モデルの unique
や unique_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.fields
や Meta.excludes
リストを変更できます:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ('body',)
上記のコードは、 EnhancedArticleForm
を追加して、 ArticleForm.Meta
フィールドを変更することで、 body
フィールドを除去しています。
ただし、フォームクラスの継承にはいくつか注意すべき点もあります。
Meta
を持つ複数の基底クラスを持つようなサブクラスを定義す
ると、最初のクラスの Meta
だけを使います。従って、 Meta
の解決は
まずサブクラスの Meta
、そして最初の親クラスの Meta
の順に行われ
ます。ModelForm
と Form
を同時に継承で
きません。これらの注意点は、サブクラスを作成する際に何かトリッキーなことをしない限り は当てはまらないはずです。
バリデーションプロセスの一部として、 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_factory
は formset_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())
fields
や exclude
で制御する¶デフォルトでは、モデルフォームセットは、モデル中の 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_num
と extra
パラメータを使って
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_formset
の clean()
をオーバーライドする¶ModelFroms
と同様に、デフォルトの動作として model_formset
の
clean()
メソッドはフォームセットの中のフォームがモデルのユニーク属性
( unique
、 unique_togeter
、あるいは unique_for_date|month|year
)
を侵害していないことを検証します。
model_formset
の clean()
メソッドをオーバーライドした上でこの検証の
仕組みを維持したいのであれば、 親クラスの 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,
})
この例では POST
と GET
の両方において 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 %}
ループでフィールドを反復したくない
のであれば、 プライマリキーフィールドをレンダリングする必要があります。
例えば、 モデルの name
と age
フィールドをレンダリングするのであれば:
<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
以外の
プライマリキーを明示的に定義してあるのならば、 必ずそれをレンダリングして下さい)。
See also
インラインフォームセット(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_factory
は modelformset_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_name
を inlineformset_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,
})
POST
と GET
の両方のケースで instance
を渡していることに注目
してください。
Oct 26, 2017