Django v1.0 documentation

モデルからフォームを生成する

revision-up-to:8961 (1.0)

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 フォーム上では表現されていません
BooleanField BooleanField
CharField CharFieldmax_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=TextareaCharField
TimeField TimeField
URLField URLFieldverify_exists には モデルフィールドの verify_exists 値が設定されます。
XMLField widget=TextareaCharField

Note

FloatField および DecimalField は、モデルフィールド、フォーム フィールドともに開発版の Django で新たに定義されました。

お気付きかもしれませんが、 ForeignKeyManyToManyField といったモ デルフィールドは特別扱いされています:

  • ForeignKeydjango.forms.ModelChoiceField で表現されてお います。 ModelChoiceField は、選択肢がモデルの QuerySet であ るような ChoiceField です。

  • ManyToManyFielddjango.forms.ModelMultipleChoiceField

    で表現されています。 ModelMultipleChoiceField は、選択肢がモデル の QuerySet であるような MultipleChoiceField です。

さらに、生成されるフォームフィールドには、以下の属性が付加されます:

  • モデルフィールドに blank=True が設定されていると、フォームフィー ルドの requiredFalse に設定されます。それ以外の場合には、 required=True です。
  • フォームフィールドの label にはモデルフィールドの verbose_name の値を使います。このとき先頭の文字は大文字に変換され ます。
  • フォームフィールドの help_text にはモデルフィールドの help_text の値を使います。
  • モデルフィールドに choices が設定されている場合、フォームフィール ドの widgetSelect に設定されます。また、選択肢にはモデル フィールドの 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() メソッドの動作だけです。これについてはすぐ後 で説明します):

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())

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()

フォームの入力値の検証に失敗すると、 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 つの方法があります:

  1. モデルフィールドに editable=False を定義します。その結果、 モデルフォームを使って生成したフォームは 全て このフィールドを含ま なくなります。
  2. ModelForm サブクラスで、内部クラス Metafields 属性を 設定します。この属性にはフィールド名からなるリストを設定します。設定 すると、指定されたフィールドだけを含むフォームを生成します。
  3. ModelForm サブクラスで、内部クラス Metaexclude 属性を 設定します。この属性にはフィールド名からなるリストを設定します。設定 すると、指定されたフィールドを含まないフォームを生成します。

例えば、 (上で定義した) Author モデルから、 nametitle フィールドだけを含むようなフォームを作成したければ、以下の ようにして 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 を使ってフォームを生成するときに fieldsexclude を指定すると、生成されたフォームの 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() メソッド を通常のフォームと同じ方法でオーバライドできます。しかし、デフォルトの clean() メソッドは、モデルの uniqueunique_together に指定し た内容に従って、オブジェクトの一意性をチェックします。従って、 clean() をオーバライドして、なおかつデフォルトのバリデーションも行いたいときは、 親クラスの clean() メソッドを必ず呼び出してください。

フォームクラスの継承

フォームクラスと同様、 ModelForm も継承によって再利用できます。フォーム クラスの継承は、一つのモデルからいくつもフォームを導出して、フォーム毎にフィー ルドを追加したり、メソッドを追加したりする際に便利です:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

上記のコードは、独自に pub_date フィールドの検証とクリーニングを行うほ かは、 ArticleForm と全く同じフォームを生成します。

親クラスの内部クラス Meta をサブクラス化すれば、親クラスの Meta.fieldsMeta.excludes リストを変更できます:

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ['body']

上記のコードは、 EnhancedArticleForm を追加して、 ArticleForm.Meta フィールドを変更することで、 body フィールドを除去しています。

ただし、フォームクラスの継承にはいくつか注意すべき点もあります。

  • 属性の名前解決には、通常の Python の名前解決規則が適用されます。すなわち、 内部クラス Meta を持つ複数の基底クラスを持つようなサブクラスを定義す ると、最初のクラスの Meta だけを使います。従って、 Meta の解決は まずサブクラスの Meta 、そして最初の親クラスの Meta の順に行われ ます。
  • 技術的な理由から、サブクラスは ModelFormForm を同時に継承で きません。

これらの注意点は、サブクラスを作成する際に何かトリッキーなことをしない限り は当てはまらないはずです。

モデルフォームセット

通常のフォームセット と同様、モデル由来のフォー ムをうまく扱えるように拡張された特殊なフォームセットクラスがあります。上の 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_factoryformset_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)

フォームセットに含めるフィールドを fieldsexclude で制御する

デフォルトでは、モデルフォームセットは、モデル中の 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_factorymax_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 を使う

inlineformset_factory は外部キーのリレーション先オブジェクトを扱うとき によく使われるパターンを簡単に実行するためのヘルパです。このヘルパは、 modelformset_factory と同じオプションをとります。例えば、以下のように AuthorBook という二つのモデルがあるとしましょう:

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)

More than one foriegn key to the same model

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")