revision-up-to: | 8961 (1.0) |
---|
データベース駆動のアプリケーションを構築しているのなら、 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 | フォーム上では表現されていません |
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 |
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 値が設定されます。 |
XMLField | widget=Textarea の CharField |
Note
FloatField および DecimalField は、モデルフィールド、フォーム フィールドともに開発版の Django で新たに定義されました。
お気付きかもしれませんが、 ForeignKey や ManyToManyField といったモ デルフィールドは特別扱いされています:
ForeignKey は django.forms.ModelChoiceField で表現されてお います。 ModelChoiceField は、選択肢がモデルの QuerySet であ るような ChoiceField です。
で表現されています。 ModelMultipleChoiceField は、選択肢がモデル の QuerySet であるような MultipleChoiceField です。
さらに、生成されるフォームフィールドには、以下の属性が付加されます:
あるモデルからフォームを作成する場合、モデルのフォームフィールドに対応する フォームフィールドをオーバライドできます。後述の デフォルトのフィールド型をオーバライドする を参照してください。
以下のような一連のモデルを考えましょう:
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() メソッドの動作だけです。これについてはすぐ後 で説明します):
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 から生成されたフォームは 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()
フォームの入力値の検証に失敗すると、 save() は ValueError を送出す るので注意してください。
save() メソッドはオプション commit キーワード引数を持っています。こ の引数には True または False を指定します。 save() を commit=False で呼び出すと、データベースに保存する前のモデルオブジェクト を返します。返されたオブジェクトに対して、最終的に save() を呼び出すか どうかは自由です。この機能は、オブジェクトを実際に保存する前に何らかの処理 を行いたい場合に便利です。 commit はデフォルトでは True に設定され ています。
モデルが他のモデルに対する多対多のリレーションをはっている場合、 commit=False を使うともう一つの副作用があります。多対多のリレーションを 持つモデルから生成したフォームを保存する際、 Django は多対多のリレーション に対するデータをすぐに保存できません。なぜなら、 save(commit=False) の 対象であるオブジェクトはまだデータベースに保存されていないため、オブジェク トに対する多対多の関係を保存するためのテーブルを更新できないからです。
この問題を回避するために、Django は commit=False でフォームを保存した直 後に、モデルフォームクラス save_m2m() メソッドを追加します。フォームの から生成したインスタンスを手動で保存した後で 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 つの方法があります:
例えば、 (上で定義した) 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() は失敗します。これを避けるには、フォームの インスタンスを生成するときに、不足しているフィールドでかつ必須のフィー ルドに対する初期値を指定してやるか、 save(commit=False) を使って、 必須のフィールドの値を手動で設定してください:
instance = Instance(required_field='value')
form = InstanceForm(request.POST, instance=instance)
new_instance = form.save()
instance = form.save(commit=False)
instance.required_field = 'new value'
new_instance = instance.save()
save(commit=False) については、 「フォームの保存」 も参照してく ださい。
上の フィールド型 で説明したデフォルトのフィールド型は、いわゆる気の利い たデフォルト値にすぎません。モデル上で DateField として定義されている フィールドは、フォーム上でも DateField として表現されてほしいというのが 普通でしょう。とはいえ、 ModelForm は、あるモデルフィールドのフォーム フィールド型を変更できるという柔軟性を備えています。フォームフィールドの型 を変更するには、通常のフォームクラスでしているように、クラス属性としてフォー ムフィールドを宣言します。宣言されたフィールドは、 model 属性から生成さ れたデフォルトのフィールドをオーバライドします。
例えば、 pub_date フィールドに MyDateFormField を使いたければ、 以下のようにして実現できます:
>>> class ArticleForm(ModelForm):
... pub_date = MyDateFormField()
...
... class Meta:
... model = Article
フィールドのデフォルトウィジェットをオーバライドしたければ、フォームフィー ルドを宣言するときに widget パラメタを指定します:
>>> class ArticleForm(ModelForm):
... pub_date = DateField(widget=MyDateWidget())
...
... class Meta:
... model = Article
バリデーションの動作を追加するために、モデルフォームの clean() メソッド を通常のフォームと同じ方法でオーバライドできます。しかし、デフォルトの clean() メソッドは、モデルの unique や unique_together に指定し た内容に従って、オブジェクトの一意性をチェックします。従って、 clean() をオーバライドして、なおかつデフォルトのバリデーションも行いたいときは、 親クラスの clean() メソッドを必ず呼び出してください。
フォームクラスと同様、 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 フィールドを除去しています。
ただし、フォームクラスの継承にはいくつか注意すべき点もあります。
これらの注意点は、サブクラスを作成する際に何かトリッキーなことをしない限り は当てはまらないはずです。
通常のフォームセット と同様、モデル由来のフォー ムをうまく扱えるように拡張された特殊なフォームセットクラスがあります。上の 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" />
<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 を使っていて、 デフォルトでは can_delete=True です。
デフォルトでは、モデルからフォームセットを生成すると、そのクエリセットはモ デルの全てのオブジェクト、すなわち Author.objects.all() です。このクエ リセットは、以下のように変更できます:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
サブクラスを使ったアプローチでも指定できます:
from django.forms.models import BaseModelFormSet
class BaseAuthorFormSet(BaseModelFormSet):
def get_queryset(self):
return super(BaseAuthorFormSet, self).get_queryset().filter(name__startswith='O')
ファクトリ関数を呼ぶときに、 BaseAuthorFormSet を渡してベースクラスにし てください:
>>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)
デフォルトでは、モデルフォームセットは、モデル中の editable=False でな い全てのフィールドを使おうとします。ただし、フォームセットレベルでこの挙動 はオーバライドできます:
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
このように、 fields を使えば、フォームセットに組み込むフィールドを指定 したものだけに制限できます。一方:
>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
このように、 exclude を使えば、指定したフィールドをフォームセットから除 外できます。
ModelForm と同様、フォームセット内のデータからモデルへの保存を行えます。 保存するには、フォームセットの save() メソッドを呼び出します:
# POST データからフォームセットインスタンスを生成します。 >>> formset = AuthorFormSet(request.POST) # フォームデータが有効なものと課程して、データを保存します。 >>> instances = formset.save()
save() メソッドはデータベースに保存されたインスタンスを返します。インス タンスがフォームセットに結びつけられたデータで変更されなかった場合、そのイ ンスタンスはデータベースに保存されず、戻り値 (上の例の instances) にも 含まれません。
save() に commit=False を渡せば、データベースは触らずに、モデルイン スタンスだけを返させられます:
# データベースに保存しません >>> instances = formset.save(commit=False) >>> for instance in instances: ... # インスタンスごとに必要な操作を行います。 ... instance.save()
この方法を使えば、データベースにインスタンスを保存する前に、各々のインスタ ンスにデータを付加できます。また、フォームセットに ManyToManyField が含 まれている場合は、 formset.save_m2m() を使って多対多のリレーションを正 しく保存させる必要があるでしょう。
通常のフォームセットど同様、 modelfomset_factory に max_num パラメ タを指定すれば、表示されるフォームの数を制限できます。モデルフォームセット の場合、クエリを制限して、表示に必要なオブジェクトの上限数を設定します:
>>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
>>> AuthorFormSet = modelformset_factory(Author, max_num=2, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
クエリセットの返すモデルインスタンスの総数が max_num よりも大きければ、 追加のフォームも取得したインスタンスのデータで埋まります:
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset.forms:
... 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>
モデルフォームセットは、フォームセットと良く似ています。例として、 ユーザが Author モデルインスタンスを編集できるようなフォームセットを考 えましょう:
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author)
if request.POST == '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() は、 フォームセット内のオブジェクトを保存する で解説しています。
inlineformset_factory は外部キーのリレーション先オブジェクトを扱うとき によく使われるパターンを簡単に実行するためのヘルパです。このヘルパは、 modelformset_factory と同じオプションをとります。例えば、以下のように Author と Book という二つのモデルがあるとしましょう:
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'Orson Scott Card')
>>> formset = BookFormSet(instance=author)
If your model contains more than one foreign key to the same model you will need to resolve the ambiguity manually using fk_name. Given the following model:
class Friendship(models.Model):
from_friend = models.ForeignKey(Friend)
to_friend = models.ForeignKey(Friend)
length_in_months = models.IntegerField()
To resolve this you can simply use fk_name to inlineformset_factory:
>>> FrienshipFormSet = inlineformset_factory(Friend, Friendship, fk_name="from_friend")
Aug 31, 2012