| revision-up-to: | 11321 (1.1) unfinished |
|---|
データベース駆動のアプリケーションを構築しているのなら、 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 |
お気付きかもしれませんが、 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 つの方法があります:
モデルフィールドに editable=False を定義します。その結果、 モデルフォームを使って生成したフォームは 全て このフィールドを含ま なくなります。
ModelForm サブクラスで、内部クラス Meta の fields 属性を 設定します。この属性にはフィールド名からなるリストを設定します。設定 すると、指定されたフィールドだけを含むフォームを生成します。
The form will render the fields in the same order they are specified in the fields attribute.
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 つ のフィールドしかないので、上のフォームは全く同じフォームフィールド を持ちます。
ノート
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) については、 「フォームの保存」 も参照してく ださい。
上の フィールド型 で説明したデフォルトのフィールド型は、いわゆる気の利い たデフォルト値にすぎません。モデル上で 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
By default, a ModelForm will render fields in the same order that they are defined on the model, with ManyToManyField instances appearing last. If you want to change the order in which fields are rendered, you can use the fields attribute on the Meta class.
The fields attribute defines the subset of model fields that will be rendered, and the order in which they will be rendered. For example given this model:
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
the author field would be rendered first. If we wanted the title field to be rendered first, we could specify the following 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 に指定した内容に従って、オブジェクトの一意性をチェック します。従って、 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 フィールドを除去しています。
ただし、フォームクラスの継承にはいくつか注意すべき点もあります。
これらの注意点は、サブクラスを作成する際に何かトリッキーなことをしない限り は当てはまらないはずです。
通常のフォームセット と同様、モデル由来のフォー ムをうまく扱えるように拡張された特殊なフォームセットクラスがあります。上の 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>
ノート
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):
self.queryset = Author.objects.filter(name__startswith='O')
super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
ファクトリ関数を呼ぶときに、 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.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() は、 フォームセット内のオブジェクトを保存する で解説しています。
Just like with ModelForms, by default the clean() method of a model_formset will validate that none of the items in the formset violate the unique constraints on your model (either unique, unique_together or unique_for_date|month|year). If you want to overide the clean() method on a model_formset and maintain this validation, you must call the parent class's clean method:
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
As stated earlier, you can override the default queryset used by the model formset:
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,
})
Note that we pass the queryset argument in both the POST and GET cases in this example.
There are three ways to render a formset in a Django template.
First, you can let the formset do most of the work:
<form method="POST" action="">
{{ formset }}
</form>
Second, you can manually render the formset, but let the form deal with itself:
<form method="POST" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form }}
{% endfor %}
</form>
When you manually render the forms yourself, be sure to render the management form as shown above. See the management form documentation.
Third, you can manually render each field:
<form method="POST" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{% for field in form %}
{{ field.label_tag }}: {{ field }}
{% endfor %}
{% endfor %}
</form>
If you opt to use this third method and you don't iterate over the fields with a {% for %} loop, you'll need to render the primary key field. For example, if you were rendering the name and age fields of a model:
<form method="POST" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
Notice how we need to explicitly render {{ form.id }}. This ensures that the model formset, in the POST case, will work correctly. (This example assumes a primary key named id. If you've explicitly defined your own primary key that isn't called id, make sure it gets rendered.)
Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key. Suppose you have these two models:
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)
ノート
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")
You may want to provide a view that allows a user to edit the related objects of a model. Here's how you can do that:
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,
})
Notice how we pass instance in both the POST and GET cases.
Aug 19, 2010