このページでは、Django におけるモデルフォームについて解説していきます!
このサイトでは、下記ページで Django のモデルの基本について、

下記ページでは Django のフォームの基本について解説しています。

モデルフォームとは、名前の通り『モデル』に基づいた『フォーム』のことになります。これを利用することで、フォームクラスの定義を効率的に行うことができます。
このページでは、まずモデルフォームの基本的な事柄について解説を行い、その後モデルフォームの扱い方について解説していきたいと思います。
Contents
モデルフォームの基本
まず、モデルフォームの基本的な事柄について解説していきたいと思います!
モデルとフォーム
モデルとは、下記ページでも解説しているように、データベースの管理を役割とするモジュールです。

モデルを利用することで、データベースにデータを保存したり、データベースからデータを取得したりすることができるようになります。
それに対し、フォームとは、下記ページでも解説しているようにフォームの管理を役割とするモジュールとなります。

フォームを利用することで、ユーザーはウェブアプリに対してデータの送信を行うことができるようになります。
フォームによって送信されてきたデータの使い道はウェブアプリによって様々ですが、その使い道の1つとして『送信されてきたデータをデータベースに保存する』が挙げられます。
例えば掲示板アプリでの『コメント投稿フォーム』の例で考えると、このフォームから送信されてきたコメントのデータは一旦データベースに保存されることになります。そして、次にユーザーが掲示板を表示したときにデータベースに保存されているコメントを取得して表示することで、保存されたコメントを後から表示することができるようになります。
このように、フォームから送信されてきたデータは一旦データベースに保存し、後から利用するという使い方が多いです。
スポンサーリンク
モデルフォーム
この時にデータベースに保存するデータの多くは、フォームでユーザーがフィールドに入力したデータとなります。そして、入力受付を行うフィールドの種類や数はフォームクラスの定義によって決まります。
また、データベースに保存するデータの種類や数はテーブルの持つフィールド(カラム)によって決まります。そして、テーブルの持つフィールドの種類や数はモデルクラスの定義によって決まります(実際にはプライマリーキーとなる id
フィールドも存在しますが、図では省略しています)。
更に、フィールドに入力されたデータを全てデータベースに保存することを考えれば、『フォームのフィールドの種類や数』は『テーブルのフィールドの種や数類』と同様のものである必要があります。つまり、この場合、フォームクラスで定義するフィールドと、モデルクラスで定義するフィールドは同様のものである必要があることになります。
同様のフィールドを定義する必要があるのであれば、フォームクラスとモデルクラスを別々に定義するよりも、一方のクラスから他方のクラスを自動的に作成する方が楽ですよね。同じようなフィールドの定義を個別に行うのは効率が悪いです。
この「一方のクラスから他方のクラスを自動的に作成する」ことを実現するのがモデルフォームとなります。
このモデルフォームを利用することで、モデルクラスの定義からフォームクラスを自動的に定義することが可能となります。つまり、モデルクラスさえ定義してやれば、このモデルクラスの持つフィールドと同様のフィールドを持つフォームクラスを簡単に定義できるようになります。
ここではモデルクラスとフォームクラスとが同様のフィールドを持つことを想定した説明を行なっていますが、あくまでもモデルフォームはモデルクラスをベースとしてフォームを定義する仕組みであり、フォームクラスに持たせるフィールドはモデルクラスの持つフィールドの中から必要なもののみ選択することもできますし、逆にモデルクラスの持たないフィールドをフォームに新たなフィールドとして追加するようなことも可能です。
モデルフォームクラスの定義
通常のフォームと同様に、モデルフォームに関してもクラスを定義して利用することになります。このクラスのことを、以降ではモデルフォームクラスを呼ばせていただきます。
次は、このモデルフォームクラスの定義方法について解説していきます。このモデルフォームクラスはフォームクラスの一種であり、フォームクラス同様に forms.py
に定義を行います。
ModelForm
を継承する
通常のフォームクラスを定義する場合は Form
というクラスを継承する必要がありましたが、モデルフォームクラスを定義する場合は ModelForm
というクラス(もしくは ModelForm
のサブクラス)を継承する必要があります。
from django import forms
class クラス名(forms.ModelForm):
model
を指定する
更に、ベースとするモデルを決めるため、モデルフォームクラスを定義する場合は model
属性を指定する必要があります。この model
属性は下記のように class Meta:
ブロックの中で指定する必要があり、model
には モデルクラス
を指定します。
class クラス名(forms.ModelForm):
class Meta:
model = モデルクラス
model
に指定するモデルクラスは事前に import
しておく必要がある点に注意してください。
fields
or exclude
を指定する
model
属性の指定によって、モデルフォームクラスの基となるモデルクラスが設定されたことになります。
次は、このモデルフォームクラスで扱いたいフィールドを、指定したモデルクラスのフィールドの中から選択します。
この選択は fields
属性もしくは exclude
属性の指定によって実現できます。この fields
属性 or exclude
属性に関しても、class Meta:
ブロックの中で指定を行う必要があります。
fields
属性を指定する場合は、モデルフォームクラスで “扱いたい” フィールドを指定します。基本的にはリストやタプル形式で指定を行い、各要素にはモデルクラスの持つフィールドのフィールド名(クラス変数名)を文字列で指定します。
class クラス名(forms.ModelForm):
class Meta:
model = モデルクラス
fileds = ['フィールド名1', 'フィールド名2',....]
モデルクラスの持つ全てのフィールドをモデルフォームクラスで扱いたい場合は、リストやタプルではなく文字列で '__all__'
を指定します。
また、exclude
属性を指定する場合は、モデルフォームクラスで “扱いたくない” フィールドをリストやタプル形式で指定します。各要素にはモデルクラスの持つフィールドのフィールド名(クラス変数名)を文字列で指定します。空のリストを指定した場合は、モデルクラスの持つ全てのフィールドを扱うことになります。
class クラス名(forms.ModelForm):
class Meta:
model = モデルクラス
exclude = ['フィールド名1', 'フィールド名2',....]
fileds = '__all__'
や exclude
を指定した場合、モデルクラス側へのフィールドの追加がモデルフォームクラス側に自動的に反映されることになり、モデルクラスの変更に強いフォームを実現することができます。
そのため、fileds
に手動で1つずつフィールド名を指定するよりは、fileds = '__all__'
や exclude
の指定を積極的に利用することをオススメします。
新たなフィールドを追加する
ここまで説明した ModelForm
の継承、model
の指定、fields
or exclude
の指定に関しては、モデルフォームクラスを定義する際に必須の手順となります。
それに対し、ここから説明する手順に関しては必須ではなく、必要に応じて行えば良い手順となります。
前述の通り、fields
属性や exclude
属性の指定によって、model
に指定したモデルクラスの持つフィールドをモデルフォームクラスに持たせることができます。ただし、model
に指定したモデルクラスの持つフィールド以外のフィールドをフォームに持たせたいような場合もあると思います。
その場合は、下記のように通常のフォームの時と同様に Field
のサブクラスを利用してフィールドを追加してやれば良いです。
class クラス名(forms.ModelForm):
フィールド名 = forms.Fieldのサブクラス()
class Meta:
model = モデルクラス
exclude = ['フィールド名1', 'フィールド名2',....]
各種フィールドをカスタマイズする
また、モデルフォームクラスに定義した各種フィールドをカスタマイズするようなことも可能です。
例えば、class Meta
のブロックの中に labels
を辞書形式で指定してやれば、各種フィールドのラベル名を変更することが可能です。labels
に指定する辞書では、キー
にモデルフォームクラスの持つフィールド名、値
にそのフィールドのラベル名を指定します。
他にも、help_texts
を指定して各種フィールドの説明文を設定したり、widgets
を指定して各種フィールドのウィジェットを設定するようなことも可能です。これらに関しても、辞書形式で指定を行う必要があります。
例えば、モデルフォームクラスの基になるモデルに username
と email
と self_introduction
が存在する場合、下記のようにモデルフォームクラスを定義すれば各種フィールドのラベル名を変更し、さらに self_introduction
フィールドのウィジェットを Textarea
(テキスト入力向けのウィジェット)に設定することができます。
from django import forms
from .models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
labels = {
'username': 'ユーザー名',
'email': 'メールアドレス',
'self_introduction': '自己紹介文'
}
widgets = {
'self_introduction': forms.Textarea
}
実際に、上記によってカスタマイズした結果の各種フィールドは下の図のようになります。
モデルフォームクラスの定義例
次は、具体例でフォームクラスの定義の仕方について解説していきます。
今回は、下記のようなモデルクラスと同様のフィールドを持つフォームを例として、モデルフォームクラスの定義の仕方について説明していきます。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=30)
email = models.EmailField()
age = models.IntegerField()
height = models.FloatField()
weight = models.FloatField()
通常のフォームクラスでの定義例
通常のフォームクラスの場合、つまり Form
を継承するクラスの場合、モデルとの関連性がないため別途必要なフィールドをフォームクラスに定義する必要があります。
したがって、上記の User
クラスと同様のフィールドを持つフォームクラスは下記のようにして定義する必要があります。
from django import forms
class UserForm(forms.Form):
name = forms.CharField(max_length=30)
email = forms.EmailField()
age = forms.IntegerField()
height = forms.FloatField()
weight = forms.FloatField()
モデルクラス側と同様のフィールドを持たせるために、フォームクラス側でも同様のフィールドの定義を行う必要があって実装量が多くなります。また、モデルクラス側のフィールドを変更してしまうとフォームクラス側の変更も必要となり、メンテナンス性も悪いです。
モデルフォームクラスでの定義例
それに対し、モデルフォームクラスを利用する場合、つまり ModelForm
を継承するクラスを利用する場合、モデルクラスに基づいてクラスを定義することができ、モデルクラスの持つフィールドを自動的にフォーム側に持たせることができます。
上記の User
クラスと同様のフィールドを持つフォームクラスは下記のようにして定義することができます。exclude = []
の部分は fields = '__all__'
にしても同様のフォームクラスが得られます。
from django import forms
class UserModelForm(forms.ModelForm):
class Meta:
model = User
exclude = []
model
に指定したモデルクラスに応じてフィールドが自動的に設定されることになるため実装量も減りますし、モデルクラスの変更も自動的に反映されるためメンテナンス性も良くなります。
スポンサーリンク
モデルフォームクラスでフォームを扱う
モデルフォームクラスの定義方法については理解していただけたでしょうか?
次は、モデルフォームクラスの扱いについてのポイントを解説していきます。フォームクラスの時同様、モデルフォームクラスを扱うのはビューとなります(テンプレートから参照することも可能)。
次は、モデルフォームクラスのビューからの扱いについて解説していきたいと思います。
基本的な扱い方はフォームクラスと同じ
まず、フォームクラスもモデルフォームクラスも両方とも BaseForm
というクラスのサブクラスとなります。同じクラスを継承しており、基本的な扱い方はフォームクラスの場合もモデルフォームクラスの場合も同様になります。
例えば、これらのクラスのインスタンスはコンストラクタを引数なしで実行することでも生成することができますし、コンストラクタの引数に request.POST
を指定して実行することで生成することもできます。後者の場合は、フォームから送信されてきたデータが各種フィールドにセットされた状態のインスタンスを生成することができます。
また、is_valid
メソッドでフォームから送信されてきたデータの妥当性の検証を行うことができる点も、通常のフォームクラスと同様になります。ただ、これに関しては少し注意事項がありますので、後述の 妥当性の検証はモデルクラスに基づいて実行される で詳細を解説します。
save
メソッドでデータベースへの保存が可能
ここからは、通常のフォームクラスには無い、モデルフォームクラスならではの機能について解説していきます。その機能の1つがデータベースへの保存になります。
モデルフォームクラスには save
メソッドが用意されており、モデルフォームクラスのインスタンスに save
メソッドを実行させることで、フォームの各種フィールドにセットされているデータをレコードとしてデータベースのテーブルに直接保存することができます。
保存先はクラスの定義時に model
に指定したモデルクラスに対応するテーブルとなります。
通常のフォームクラスには save
メソッドは用意されておらず、フォームから送信されてきたデータをデータベースに保存するためには、各種フィールドのデータを取得してモデルのインスタンスにセットし、さらにモデルのインスタンスに save
メソッドを実行させる必要があります。
また、各種フィールドのデータを取得する際に妥当性の検証を行うことを考えると、request.POST
をコンストラクの引数に指定してフォームクラスのインスタンスを生成し、is_valid
メソッド実行後にインスタンスの cleaned_data
からデータを取得する必要もあります。
それに対し、モデルフォームクラスの場合は、request.POST
をコンストラクの引数に指定してインスタンスを生成し、そのインスタンスに is_valid
メソッドおよび save
メソッドを実行させるだけでデータベースへの保存を実現することが可能となります。つまり、わざわざ各種フィールドのデータをモデルクラスのインスタンスにセットする必要がなくなります。
補足しておくと、フォーム自体はデータベース管理の役割を持ちません。これはモデルフォームクラスの場合も同様です。そのため、モデルフォームクラスのインスタンスに save
メソッドを実行させた際には、その save
メソッドの中で結局はモデルクラスの save
メソッドが実行されることになります。
なので、内部的な処理も考慮すれば、モデルフォームクラスの場合も通常のフォームクラスの場合も結局は同じような処理が行われることになります。ですが、ビューへの実装を考えると、モデルフォームクラスのインスタンスからの save
メソッド実行のみでデータベースへの保存が行えるため実装量は減り、より簡潔な処理でデータベースへの保存を実現することができることになります。
スポンサーリンク
モデルクラスのインスタンスが取得可能
また、モデルフォームクラスのインスタンスからモデルクラスのインスタンスを取得することも可能です。
ここではインスタンスの取得方法として2つの方法を取得します。
save
メソッドの返却値として取得する
1つ目は save
メソッドの返却値として取得する方法になります。save
メソッドを実行した際には、返却値として model
に指定したモデルクラスのインスタンスが得られます。モデルフォームクラスのインスタンスの各種フィールドにデータがセットされている場合は、それらのデータが各種フィールドにセットされた状態のモデルクラスのインスタンスが返却されることになります。
例えば下記は、モデルフォームクラスの定義例 で示した UserModelForm
のインスタンスに save
メソッドを実行させ、その返却値として モデルフォームクラスの定義例 で示した User
のインスタンスを取得する例になります。
from .forms import UserForm
from .models import User
def form(request):
if request.method == 'POST':
form = UserModelForm(request.POST)
if form.is_valid():
user = form.save() # Userのインスタンスを取得
前述の通り、モデルフォームクラスのインスタンスに save
メソッドを実行させた場合はデータベースへの保存が行われます。逆に言えば、上記の方法ではデータベースへの保存を行わないとモデルフォームクラスのインスタンスが取得できないことになります。
ですが、データベースの保存を行う前にモデルフォームクラスのインスタンスを取得したいようなケースも多々あります。そのような場合は、save
メソッドの引数に commit=False
を指定します。これにより、save
メソッド実行時のデータベースへの保存がスキップされ、データベースへの保存なしにモデルクラスのインスタンスが取得できるようになります。
例えば、モデルフォームクラスの定義例 で示した User
に必須項目として bmi
フィールドを追加したとしましょう。さらに UserModelForm
には bmi
フィールドを持たせないものとします。つまり、bmi
フィールドはユーザーから入力受付するのではなく、アプリ内で自動的に計算してフィールドにセットする必要があります。
BMI は身長と体重から算出される、肥満度を表す指標値となります
この場合、bmi
フィールドをセットする前にインスタンスを取得し、さらにそのインスタンスに bmi
フィールドをセットしてからインスタンスの保存を行う必要があります。このような流れの処理は下記のように実装することで実現することができます。
from .forms import UserForm
from .models import User
def form(request):
if request.method == 'POST':
form = UserModelForm(request.POST)
if form.is_valid():
user = form.save(commit=False) # Userのインスタンスを取得
bmi = user.weight / ((user.height /100) * (user.height / 100))
user.bmi = bmi # モデルクラスのインスタンスにセット
user.save() # モデルクラスからデータベースに保存
上記において、bmi
フィールドは必須項目である(null=True
が指定されていない)、かつ、UserModelForm
で bmi
フィールドの入力受付は行なっていないため、form.save(commit=False)
の箇所を form.save()
に変更すると、save
メソッド実行時に例外が発生することになります。これは、必須項目のフィールドにデータが設定されていない状態でデータベースへのレコード保存が行われることになるためです。
こういった、必須項目がまだ未設定の場合にモデルフォームから save
メソッドでインスタンスを取得したいような場合は、データベースへのレコード保存をスキップするよう commit=False
を指定しておく必要があります。
データ属性 instance
から取得する
2つ目はモデルフォームクラスのインスタンスのデータ属性 instance
から取得する方法になります。
通常のフォームクラスの場合は is_valid
メソッドの実行によってインスタンスにデータ属性 cleaned_data
が追加されますが、このデータは辞書形式のデータでした。モデルフォームクラスの場合は is_valid
メソッドの実行によって、インスタンスにデータ属性 cleaned_data
だけでなくデータ属性 instance
も追加されることになります。
この instance
は model
に指定したモデルクラスのインスタンスであり、このデータ属性から直接モデルクラスのインスタンスを取得することができます。この場合も、データベースへの保存を行う前にモデルクラスのインスタンスを取得することが可能です。
from .forms import UserForm
from .models import User
def form(request):
if request.method == 'POST':
form = UserModelForm(request.POST)
if form.is_valid():
user = form.instance # Userのインスタンスを取得
モデルからモデルフォームのインスタンスが生成可能
また、モデルフォームクラスのインスタンスは、モデルクラスのインスタンスから生成することも可能です。
モデルフォームクラスのコンストラクタでは引数に instance
を指定することが可能です。この instance
にモデルクラスのインスタンスを指定した場合、そのインスタンスからモデルフォームクラスのインスタンスを生成することができます。この生成されるインスタンスの各種フィールドには、モデルクラスのインスタンスの各種フィールドにセットされたデータがセットされることになります。
このようなインスタンスの生成の仕方は、既にデータベースに保存されているレコードの内容を反映させた状態のフォームを表示したい場合に有効です。
レコードの更新
また、このようなインスタンスの生成の仕方はデータベースのレコードを更新する際にも便利です。
例えば、あるユーザーが自身の登録情報を変更したい場合、まずはそのユーザーの登録情報(レコード)をフィールドに反映したフォームを表示してあげるのが親切です。
前述の通り、レコードの各種フィールドのデータを反映したフォームは、モデルクラスのインスタンスからモデルフォームクラスのインスタンスを生成することで簡単に実現することができます。
例えばですが、ユーザーの登録情報をフィールドに反映したフォームの表示は下記のような views.py
の update
関数によって実現することができます。app/update.html
はフォームを表示するためのテンプレートファイルであることを想定しています。また、ユーザーを表すモデルクラスを User
とし、さらに登録情報を変更しようとしているユーザーの id
は引数 id
で渡されるものとして関数を定義しています。
from django.shortcuts import render
from .forms import UserModelForm
from .models import User
def update(request, id):
user = User.objects.get(id=id) #Userのインスタンスを取得
if request.method != 'POST':
form = UserModelForm(instance=user)
context = {
'form' : form
}
return render(request, 'app/update.html', context)
上記では、ユーザーの登録情報をフィールドに反映したフォームの表示を実現しようとしているだけなので、'GET'
メソッドのリクエストを受け取った場合の処理のみを実装しています。
UserModelForm()
の引数に instance=user
を指定しているため、user
の各種フィールドにセットされたデータが、生成される UserModelForm
のインスタンスの各種フィールドにセットされることになります。したがって、このインスタンスのフォームをページに埋め込んで表示すれば、各種フィールドに user
の情報が入力された状態のフォームが表示されることになります。
次は、上記の update
関数を変更してレコードの更新が行えるようにしていきたいと思います。具体的には、表示されるフォームの各種フィールドにユーザーが更新したいデータを入力し、さらにフォームから更新後のデータが送信されてきた際に、その送信されてきたデータで先ほどデータベースから取得したレコードを上書きするような処理を実現していきます。
このようなレコードの上書きは、モデルフォームクラスのコンストラクタに request.POST
と instance=上書き先のレコードに対応するインスタンス
を指定し、これによって生成されるモデルフォームクラスのインスタンスに save
メソッドを実行させることで実現することができます。
先程の update
関数の例で考えれば、'POST'
メソッドのリクエストを受け取った際(下記の else
節)に下記のような処理を行えば良いことになります。
from django.shortcuts import render
from .forms import UserModelForm
from .models import User
def update(request, id):
user = User.objects.get(id=id) #Userのインスタンスを取得
if request.method != 'POST':
form = UserModelForm(instance=user)
else:
form = UserModelForm(request.POST, instance=user)
if form.is_valid():
form.save()
context = {
'form' : form,
}
return render(request, 'app/update.html', context)
context = {
'form' : form
}
return render(request, 'app/update.html', context)
コンストラクタに指定した request.POST
はフォームから送信されてきたデータになります。コンストラクタに request.POST
のみを引数に指定してインスタンスを生成し、このインスタンスに save
メソッドを実行させた場合はデータベースへのレコード新規追加による保存処理が行われることになります。
それに対し、コンストラクタに対して instance
を追加で引数指定した場合、instance
によって上書き先のレコードが指定されることになるため、生成されたモデルフォームクラスのインスタンスに save
メソッドを実行させれば、レコードが新規追加されるのではなく instance
引数に指定したインスタンスに対応するレコードを上書きする形で保存処理が行われることになります。
このように、モデルフォームクラスのコンストラクタに指定する引数によって、save
メソッド実行時の動作が異なることになるので注意してください。
ここまで説明してきたように、モデルフォームクラスは通常のフォームクラスと同様の機能を利用するのに加え、データベースの管理も行うことができるクラスとなります。
フォームから送信されてきたデータをデータベースに保存したい場合や、データベースに保存されているデータをフォームに反映したい場合などはモデルフォームクラスを使った方が簡潔に処理が記述できると思います。
ただし、フォームから送信されてきたデータをデータベースに保存しないような場合は、通常のフォームクラスを利用することになります。モデルフォームクラスと通常のフォームクラスの違いを理解し、適切に使い分けるようにしましょう!
妥当性の検証はモデルクラスに基づいて実行される
解説の最後に、モデルフォームにおける妥当性の検証について解説しておきます。
ここまで示してきたコードでも既に使用例を示していますが、通常のフォームクラスと同様に、モデルフォームクラスのインスタンスからも is_valid
メソッドを実行することができます。そして、このメソッドの実行により、フォームの各種フィールドに入力されたデータの妥当性の検証を行うことができます。
妥当性の検証の設定は基本的にはモデルクラスで行う
ただし、通常のフォームクラスの場合、妥当性の検証時にはフォームクラスの各種フィールドに基づいて妥当であるかどうかの判断が行われていました。それに対し、モデルフォームクラスの場合、この判断は基本的にはモデルクラスの各種フィールドに基づいて行われることになります。
例えば、通常のフォームクラスの場合、フォームクラスの各種フィールドにおける Field
のサブクラスの種類に応じた妥当性の検証が行われていました。Field
のサブクラスが IntegerField
の場合は整数以外が入力された際に妥当でないと判断されるといった感じです。
モデルフォームクラスの場合、前述の通りモデルクラスの各種フィールドに基づいて妥当性の検証が行われることになります。例えば先程の例で考えれば、フォームクラスではなくモデルクラスの各種フィールドにおける Field
のサブクラスの種類によって妥当性の検証が行われることになります。
また、Field
のサブクラスの種類に応じてクライアント側(ウェブブラウザ)でも妥当性の検証が行われる点もフォームクラスと同様の点になります。
さらに、フォームクラス同様にモデルクラスにバリデーターをセットすることもでき、これによってアプリ独自の判断基準で妥当性の検証が行われるようにすることも可能です。
このように、モデルフォームを利用する場合、妥当性の検証は、基本的にはモデルクラスに基づいて行われます。モデルクラスのフィールドの Field
のサブクラスの種類に応じて妥当性の検証が行われますし、より細かな妥当性の検証を行いたいのであれば、モデルクラスのフィールドに対してバリデーターをセット指定やれば良いです。要は、フォームクラスのフィールドで行なっていた妥当性の検証に関する設定を、モデルクラス側に移行してやれば良いです。
フィールドに指定可能な引数の違いに注意
ただ、1つ厄介な点があって、それはフォームクラスとモデルクラスとで Field
のサブクラスのコンストラクタに指定可能な引数が異なるという点になります。
例えば、forms.IntegerField
に関しては引数として min_value
と max_value
が指定可能であり、これらの引数によってフィールドに入力可能な最小値と最大値を設定することができます。そして、このフィールドを持つフォームをテンプレートファイルに埋め込めば、この引数に応じてフィールド要素のタグの属性が設定されることになります(この場合は min
と max
属性が自動的に付加される)。
そして、このタグの属性によってクライアント側(ウェブブラウザ)で妥当性の検証が行われることになります。さらに、この引数に応じてサーバー側でも妥当性の検証が行われることになります。要は、IntegerField()
の引数の指定によって妥当性の検証時の判断基準を決めることができました。
それに対し、models.IntegerField
の場合は引数として min_value
と max_value
は指定不可です。そのため、forms.IntegerField
の時のような引数指定による最小値と最大値に対する妥当性の検証は実現できないことになります。もちろん、バリデーターを別途用意して最小値と最大値を定義し、それ以外の値が入力された際に妥当でないと判断されるようにしてしまえば良いのですが、これだとクライアント側での妥当性の検証が実施されません。これは、バリデーターを指定したとしても HTML のフィールド要素のタグ属性への反映が行われないためです。
そのため、フィールド要素のタグ属性の設定を行うためには、それ専用の処理を行う必要が出てきます。具体的には、モデルフォームクラスにコンストラクタを用意し、そのコンストラクタの中でタグ属性の設定を行います。
具体例を考えていきましょう!
例えば、フォームクラスを利用して下記のようなフォームを定義したとします。
from django import forms
class UserForm(forms.ModelForm):
age = forms.IntegerField(min_value=0, max_value=200)
このフォームクラスのインスタンスをテンプレートに埋めんで HTML を生成すれば、クライアント側のウェブブラウザでも入力値の妥当性の検証が行われ、引数で指定した min_value
と max_value
の範囲外の値が入力された場合は下の図のような警告文が表示されることになります。
さらに、クライアント側で妥当性の検証が行われなかった場合、引数で指定した min_value
と max_value
の範囲外の値がサーバー側に送信されてくることになります。その場合もサーバー側で min_value
と max_value
に基づいて妥当性の検証が行われ、これらの範囲外の値が送信されてきた際にはデータが妥当でないと判断され、再度フォームを表示した際に、そのことを示す注意文を表示するようなことも可能です。
これと同様のフォームをモデルクラスフォームを利用して実現する際には、次のような手順を踏む必要があります。
まずはモデルクラスの定義を行います。前述の通り、モデルフォームクラスにおいてはモデルクラスに基づいて妥当性の検証が行われることになりますが、これも前述したとおり models.IntegerField
への min_value
と max_value
の指定は不可です。そのため、下記のように MinValueValidator
と MaxValueValidator
を利用してサーバー側での妥当性の検証へ最小値と最大値に対する判断基準追加します。
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
min_age = 0
max_age = 200
class User(models.Model):
age = models.IntegerField(validators=[MinValueValidator(min_age), MaxValueValidator(max_age)])
続いて、下記のようにモデルクラスフォームを定義します。下記ではモデルクラスフォームのコンストラクタを定義し、このコンストラクタの中で、age
フィールドのタグ属性の設定を行うようにしています。これにより、テンプレートファイルにこのフォームを埋め込んだ際、HTML の age
フィールド要素に対し、ここで設定したタグ属性が自動的に付加されるようになります。
from django import forms
from .models import User, max_age, min_age
class UeerForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['age'].widget.attrs.update({
'min': min_age,
'max': max_age,
})
上記のようにモデルクラスとモデルフォームクラスを定義すれば、前述で示したフォームクラスと同等の妥当性の検証を実現することができることになります。
特にモデルフォームクラスのコンストラクタを作成する必要があるあたりが、正直ちょっと面倒ですよね…。ただ、min_value
と max_value
のようなフォームのフィールドでのみ指定可能な引数は少ないと思いますし、妥当性の検証で特に重要になるのはサーバー側で行われる検証になります。これは、下記ページでも解説していますがクライアント側での妥当性の検証が行われるとは限らないためです。結局サーバー側での妥当性の検証が必要になります。

ですので、上記のようにモデルフォームクラスのコンストラクタを作成してタグの属性の設定を行うようなことは毎回行わなくても良いと思いますし、上記のコンストラクタで行っている処理内容もしっかり理解しておく必要はないとは思っています。ですが、上記のようにコンストラクタを設けてタグの設定を行うことができること自体は頭の片隅に置いておくと良いと思います。
また、別に HTML のタグの属性によって妥当性の検証を行わなくても、JavaScript を利用して各フィールドに対して妥当性の検証を行うようなことも可能です。このことも是非覚えておいてください。
スポンサーリンク
掲示板アプリでモデルフォームを利用してみる
では、いつも通り、ここまで説明してきた内容を踏まえて、実際にモデルフォームの利用例を示していきたいと思います。
この Django 入門に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでリレーションを利用してみる で作成したウェブアプリに対してモデルフォームを導入する形で、モデルフォームの利用例を示していきたいと思います。

上記ページで紹介しているウェブアプリでは単なるフォームを利用していますが、今回はこれをモデルフォームを利用する形に変更していきます。具体的には、現状 RegisterForm
と PostForm
を用意しており、これらはそれぞれ User
と Comment
のインスタンスを生成するための情報の入力受付を行うフォームとなっています。これらを単なるフォームクラスではなく、モデルフォームクラスを利用するように変更していきます。
これにより、フォームで別途フィールドの定義を行わなくても、フォームはモデルに合わせたフィールドを自動的に持つようにすることができます。今回は、単純にモデルフォームクラスを利用するための変更を行うため、機能追加を行うものではなく、効率的な開発につなげるための変更となります。
変更方針
前述の通り、現状 RegisterForm
と PostForm
を用意しており、これらはそれぞれ User
と Comment
のインスタンスを生成するための情報の入力受付を行うフォームとなっています。
そのため、RegisterForm
に関しては User
をベースとして生成するモデルフォームクラスに、PostForm
に関しては Comment
をベースとして生成するモデルフォームクラスに変更していきます。
モデルクラスの変更
早速モデルフォームクラス側の実装を行なっていきたいところではあるのですが、今までのアプリと同等の妥当性の検証が行われるように、まずはモデルクラスの変更を行います。妥当性の検証はモデルクラスに基づいて実行される で解説したように、今までフォームクラスで行っていた妥当性の検証の設定はモデルクラス側に移行してやる必要があります。
フォームクラスでの妥当性の検証の設定に関しては、下記ページの 掲示板アプリでフォームを利用してみる で行なっています。

まず、RegisterForm
の username
フィールドに対してバリデーターを設定し、そのバリデーターで username
が入力データが半角英数字のみから構成されている&先頭が半角英字である場合のみ妥当であると判断するようにしていますので、これに関しては、RegisterForm
のベースとなる User
の username
フィールドで同様のバリデーターを設定するようにしてやれば良いです。
また、RegisterForm
の age
フィールドに関しては、forms.IntegerField
に min_value
引数と max_value
引数を指定して最小値と最大値を設定するようにしています。ただし、妥当性の検証はモデルクラスに基づいて実行される で実例を挙げたように、models.IntegerField
に関しては min_value
引数と max_value
引数を指定することができないため、代わりにバリデーターを設定して同様の最小値と最大値を設定できるようにしたいと思います。
ですが、これも 妥当性の検証はモデルクラスに基づいて実行される で説明したように、バリデーターを設定しても、その設定はテンプレートへのフォームの埋め込み時に生成される HTML のタグには反映されないため、RegisterForm
にコンストラクタを定義し、そこで最小値と最大値の設定を行うようにしたいと思います。
長々と説明してしまいましたが、上記の内容を踏まえ、まずは models.py
を下記のように変更したいと思います。
from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
min_age = 0
max_age = 200
def check_username(username):
if not username.isalnum() or not username.isascii():
raise ValidationError(_('usernameにはアルファベットと数字のみ入力可能です'))
if not username[0].isalpha():
raise ValidationError(_('usernameの最初の文字はアルファベットにしてください'))
class User(models.Model):
username = models.CharField(max_length=32, validators=[check_username])
email = models.EmailField()
age = models.IntegerField(validators=[MinValueValidator(min_age), MaxValueValidator(max_age)])
def __str__(self):
return self.username
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='comments')
text = models.CharField(max_length=256)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.text[:10]
check_username
関数は、元々 forms.py
に定義していたものを移動させていただけになります。
また、age
フィールドを見ていただければ分かる通り、最小値や最大値を設定するためには validators
引数に MinValueValidator(最小値)
と MaxValueValidator(最大値)
を指定してやれば良いです。
スポンサーリンク
モデルフォームクラスの定義
続いて、このページの本題となるモデルフォームクラスを定義していきたいと思います。下記ページで示したアプリでは、ユーザー登録フォーム RegisterForm
とコメント投稿フォーム PostForm
を定義しています。

前述の通り、RegisterForm
は User
をベースとするモデルフォームクラスに、PostForm
は Comment
をベースとするモデルフォームクラスに置き換えていきます。
モデルフォームクラスの定義 で解説したように、モデルフォームクラスは ModelForm
を継承して作成し、ベースとするモデルクラスを model
に指定する必要があります。あとは、フォームに持たせるフィールドの選択も行う必要がありますので、RegisterForm
には User
の持つフィールド全てを、PostForm
には Comment
の持つ date
以外のフィールド、つまり user
と text
フィールドを持たせるようにしたいと思います。date
を除外するのは、date
はレコード新規保存時に自動的に日時が設定されるフィールドになっているからです。
ちなみに、user
フィールドは、その Comment
のインスタンスからリレーションを構築する相手となる User
のインスタンスを選択するためのフィールドとなります。また、せっかくなので、text
フィールドは、文章入力用のウィジェットに変更してみようと思います。
さらに、下記ページの 掲示板アプリでフォームを利用してみる で作成したフォームと同様に、age
フィールドに関しては入力整数の妥当性を検証できるよう、入力フィールドのタグ属性に min
と max
を設定するようにしたいと思います。

上記のようなモデルフォームクラスは、次のように forms.py
を変更することで実現することができます。
from django import forms
from .models import User, Comment, max_age, min_age
class RegisterForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['age'].widget.attrs.update({
'min': min_age,
'max': max_age,
})
class PostForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ['date']
widgets = {
'text': forms.Textarea
}
ビューの変更
最後にビュー側の変更も行なっておきましょう!
現在、ビューでは register_view
関数と post_view
関数とでフォームを利用するようになっています。上記の変更によって、これらの関数から利用されるフォームがモデルフォームクラスのものに変化したことになります。
その変化に伴い、ビュー側も変更したいと思います。ただし、基本的な扱い方はフォームクラスと同じ で解説したように、フォームクラスもモデルフォームクラスも基本的な扱い方は同じなので、現状のビューでも正常に動作はしてくれるはずです。
ですが、save メソッドでデータベースへの保存が可能 で解説したように、モデルフォームクラスを利用するようになったことで、フォームから直接データベースへの保存が行えるようになっています。つまり、今までフォームからデータを取得し、それをモデルクラスのインスタンスにセットしてからデータベースへの保存を行なっていたのですが、このような段階的な処理が不要となり、フォームから保存を行えば良いだけになります。これを利用することで、ビューの実装を楽に行うことができるようになります。
この楽さを実感していただくため、ビューの変更を行なっていきたいと思います。
前述の通り、現状の views.py
においては register_view
と post_view
からフォームを利用していますので、この2つの関数のみの変更を行なっていきます。具体的には、views.py
の register_view
と post_view
を下記のように変更します。
def register_view(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
form.save()
return redirect('users')
else:
form = RegisterForm()
context = {
'form': form
}
return render(request, 'forum/register.html', context)
def post_view(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect('comments')
else:
form = PostForm()
context = {
'form': form,
}
return render(request, 'forum/post.html', context)
変更点は register_view
と post_view
とでほぼ同じなので、register_view
の方に注目して解説していきます。まず、元々の register_view
においては、is_valid
メソッドの返却値が True
の場合に実行するレコードの保存は下記のような処理で実現していました。。
if form.is_valid():
username = form.cleaned_data.get('username')
email = form.cleaned_data.get('email')
age = form.cleaned_data.get('age')
user = User(username=username, email=email, age=age)
user.save()
return redirect('users')
上記においては、まず、form
から各種フィールドのデータを取得し、さらに取得したデータからモデルクラスのインスタンスを生成しています。そして、その後にインスタンスの保存を save
メソッドで行うようになっています。ポイントは、通常のフォームクラスの場合、フォームとモデルは独立しているため、上記のように一旦フォームからデータを取得し、それをモデル側に設定する必要があるという点になります。
それに対し、モデルフォームクラスの場合は、モデルからフォームが生成されていて互いに関連性を持っているため、モデルフォームクラスのインスタンスに save
メソッドさえ実行させれば各種フィールドのデータがセットされた状態のインスタンス(レコード)がデータベースに保存されることになります。
なので、実装が下記のように簡素化され、実装が楽になります。特にフォームで扱うフィールドが多くなればなるほど、モデルフォームを利用することでビューの実装は楽になります。
if form.is_valid():
form.save()
return redirect('users')
また、通常のフォームクラスの場合、モデルクラスを変更すれば、それに伴ってフォームクラスの変更も必要となり、さらにそれに伴ってビューの変更も必要となります。例えば、フォームクラスにフィールドを追加すれば、その分ビューで行うフィールドからのデータの取得処理も追加が必要となります。
ですが、モデルフォームクラスの場合は、上記のようにモデルフォームクラスのインスタンスから save
メソッドを実行するようにしておけば、モデルクラスを変更してもビューの変更が不要となります。また、モデルフォームクラス自体に関しても、fields
属性に '__all__'
を指定している場合や exclude
属性を指定している場合は、モデルクラスにフィールドを追加すれば対応するフィールドがモデルフォームクラス側にも自動的に追加されることになります。
このように、モデルフォームクラスを利用することでビューの実装も楽になりますし、モデルクラスの変更に伴うフォームの変更に関しても最小限に抑えることが可能です。そして、これらはバグを減らしたりコードの再利用性を高めるメリットにつながりますので、特にモデルのフィールドと対応するフォームを作成するような場合は、積極的にモデルフォームの仕組みを利用することをオススメします。
動作確認
最後に、今回実装した内容に関して動作確認を行なっておきましょう!
マイグレーションの実行
今回は models.py
の変更を行なっていますので、最初にマイグレーションを実行しておきたいと思います。
マイグレーションは、プロジェクトフォルダの直下、つまり、manage.py
が存在するフォルダで下記コマンドを実行することで行うことができます。
% python manage.py makemigrations
% python manage.py migrate
今回は models.py
を変更して各種モデルクラスに妥当性の検証用の設定を追加しただけなので、エラーもなく正常に上記2つのコマンドが完了するのではないかと思います。
開発用ウェブサーバーの起動
マイグレーションが完了した後は、いつも通り Django 開発用ウェブサーバーの起動を行います。マイグレーションを実行した時と同じフォルダで下記コマンドを実行すれば、Django 開発用ウェブサーバーが起動します。
% python manage.py runserver
ユーザー登録フォームの確認
今回は、今まで通常のフォームクラスを利用していたものをモデルフォームクラスを利用するように変更しただけですので、ここではフォームの確認のみを行なっていきたいと思います。
まずはウェブブラウザから下記の URL にアクセスしてみてください。
http://localhost:8000/forum/register/
これにより、ユーザー登録フォームが表示されると思います。といっても、前回の動作確認時とフィールドや見た目は変わらないはずです。
まず、実際にユーザー登録を行い、登録したユーザーがユーザー一覧に表示されることを確認してみてください。
また、再度ユーザー登録フォームを表示し、次は username
に数字から始まるユーザー名を入力して送信ボタンを押してみてください。また、同様に、age
に 0
から 200
以外の整数を入力して送信ボタンを押してみてください。
前者の場合は、送信ボタンを押した後に再度フォームが表示され、そこに注意文が示されていること、さらに後者の場合はデータの送信が出来なくなっていることが確認できると思います。
これらはモデルクラス自体やモデルフォームクラスに設定された妥当性の検証の結果であり、これらの動作結果より、妥当性の検証も意図した通りに動作していることが確認できたことになります。
コメント投稿フォームの確認
次はウェブブラウザから下記の URL にアクセスしてみてください。
http://localhost:8000/forum/post/
これにより、コメント登録フォームが表示されると思います。
今回、forms.py
で text
フィールドのウィジェットとして Textarea
を指定しているため、text
フィールドの見た目が変化していることが確認できると思います。ですが、機能的には前回動作確認時と全く同じになっているはずです。user
で投稿者となるユーザーを選択し、適当に text
フィールドにテキストを入力してコメント投稿を行なってみてください。
こちらも、投稿したコメントが投稿一覧に表示されれば動作確認 OK となります。
スポンサーリンク
まとめ
このページでは Django におけるモデルフォームについて解説しました!
モデルフォームはフォームをモデルに基づいて自動的に定義する仕組みであり、モデルに基づいたフォームを作成できるようになることでフォームを楽に定義することができるようになりますし、モデル側の変更も自動的にフォーム側に反映されるようにもなります。
別に通常のフォームさえ利用できればモデルフォームを利用しなくてもアプリを開発することは可能ではありますが、効率的に開発を進めるという意味では開発者にとってモデルフォームは強力な仕組みとなると思います。特にモデルとフォームのフィールドは直結することも多いため、そういう場合はモデルフォームを積極的に利用してみましょう!
次の連載ではウェブアプリへのログイン機能の搭載手順について解説します。ウェブアプリではログイン機能を搭載しているものが多いですよね?!ログイン機能の搭載手順を知っていれば、そういったウェブアプリもあなた自身の手で開発することができるようになります。ぜひ次の連載のページも読んでみてください!
