このページでは、Django におけるモデルフォームについて解説していきます!
このサイトでは、下記ページで Django のモデルの基本について、
【Django入門6】モデルの基本下記ページでは Django のフォームの基本について解説しています。
【Django入門5】フォームの基本モデルフォームとは、名前の通り『モデル』に基づいた『フォーム』のことになります。これを利用することで、フォームクラスの定義を効率的に行うことができます。
このページでは、まずモデルフォームの基本的な事柄について解説を行い、その後モデルフォームの扱い方について解説していきたいと思います。
Contents
モデルフォームの基本
まず、モデルフォームの基本的な事柄について解説していきたいと思います!
モデルとフォーム
モデルとは、下記ページでも解説しているように、データベースの管理を役割とするモジュールです。
【Django入門6】モデルの基本モデルを利用することで、データベースにデータを保存したり、データベースからデータを取得したりすることができるようになります。
それに対し、フォームとは、下記ページでも解説しているようにフォームの管理を役割とするモジュールとなります。
【Django入門5】フォームの基本フォームを利用することで、ユーザーはウェブアプリに対してデータの送信を行うことができるようになります。
フォームによって送信されてきたデータの使い道はウェブアプリによって様々ですが、その使い道の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
属性を指定する場合は、model
に指定した モデルクラス
の持つフィールドの中から、モデルフォームクラスで “扱いたい” フィールドを指定します。基本的にはリストやタプル形式で指定を行い、各要素にはモデルクラスの持つフィールドのフィールド名(クラス変数名)を文字列で指定します。
class クラス名(forms.ModelForm):
class Meta:
model = モデルクラス
fileds = ['フィールド名1', 'フィールド名2',....]
モデルクラスの持つ全てのフィールドをモデルフォームクラスで扱いたい場合は、リストやタプルではなく文字列で '__all__'
を指定します。
また、exclude
属性を指定する場合は、model
に指定した モデルクラス
の持つフィールドの中から、モデルフォームクラスで “扱いたくない” フィールドをリストやタプル形式で指定します。各要素にはモデルクラスの持つフィールドのフィールド名(クラス変数名)を文字列で指定します。空のリストを指定した場合は、モデルクラスの持つ全てのフィールドを扱うことになります。
class クラス名(forms.ModelForm):
class Meta:
model = モデルクラス
exclude = ['フィールド名1', 'フィールド名2',....]
以上が、最低限モデルフォームクラスを定義する上で必要となる手順となります。このような手順でクラスを定義すれば、そのクラスはモデルフォームクラスとして扱われ、このクラスは、model
属性に指定したモデルクラスの持つフィールドのうち、fileds
属性で指定したフィールドを持つフォームとして扱われます(もしくは exclude
属性で指定しなかったフィールドを持つ)。
そして、このモデルフォームクラスは、基本的には通常のフォームクラスと同様にしてビューやテンプレートから扱うことができます。例えば、テンプレートファイルにモデルフォームクラスのインスタンスを埋め込めば、そのインスタンスの持つフィールドを表示するためのタグが HTML に埋め込まれることになります。ただ、これらにも多少扱い方が異なる点はあるので、この点に関しては モデルフォームクラスでフォームを扱う で解説していきたいと思います。
新たなフィールドを追加する
前述のとおり、ここまで説明した 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
クラスと同様のフィールドを持つフォームクラスは下記のようにして定義することができます。
from django import forms
class UserModelForm(forms.ModelForm):
class Meta:
model = User
fields = ['name', 'email', 'age', 'height', 'weight']
fields
の指定等は必要になりますが、クラス変数等の定義が減って実装量が減り、コードの見た目もシンプルになったことが確認できると思います。
スポンサーリンク
モデルフォームクラスでフォームを扱う
モデルフォームクラスの定義方法については理解していただけたでしょうか?
次は、モデルフォームクラスの扱いについてのポイントを解説していきます。
前提として、フォームクラスもモデルフォームクラスも両方とも BaseForm
というクラスのサブクラスとなります。同じクラスを継承しており、基本的な扱い方はフォームクラスの場合もモデルフォームクラスの場合も同様になります。ただ、フォームクラスには存在しない、モデルフォームクラス特有の使い方や機能が存在しますので、このあたりを中心に解説していきます。
ここからは、ビューの関数がリクエストを受け取る引数が request
であることを前提に解説を進めていきます
インスタンスの生成
まず、モデルフォームクラスのインスタンスの生成の仕方について解説していきます。
モデルフォームクラスにおいても、フォームクラス同様に、コンストラクタを引数なし or 引数にrequest.POST
を指定して実行することでインスタンスを生成することが可能です。モデルフォームクラスのインスタンスの各種データ属性を初期値(空)の状態でインスタンスを生成したい場合は前者で、モデルフォームクラスのインスタンスの各種データ属性にフォームから送信されてきた各種フィールドのデータをセットした状態でインスタンスを生成したい場合は後者を利用することになります。
モデルからのモデルフォームクラスのインスタンスの生成
上記に加え、モデルフォームクラスのインスタンスに関しては、モデルクラスのインスタンスから生成することも可能です。
モデルフォームクラスのコンストラクタでは引数に instance
を指定することが可能で、この instance
にモデルクラスのインスタンスを指定した場合、そのインスタンスからモデルフォームクラスのインスタンスを生成することができます。この生成されるインスタンスの各種フィールドには、モデルクラスのインスタンスの各種フィールドにセットされたデータがセットされることになります。
このようなインスタンスの生成の仕方は、既にデータベースに保存されているレコードの内容を反映させた状態のフォームを表示したい場合に有効です。そして、このようなフォームの表示は、レコードの更新フォームの表示を実現する時によく利用されます。
更新フォームでは、ユーザーに更新前のレコードの中身を確認してもらうために、更新前の各種フィールドの値をセットした状態のフォームを表示することが多いです。そして、このようなフォームは、instance
引数へ “データベースから取得したレコード(モデルクラスのインスタンス)” を指定してモデルフォームクラスのコンストラクタを実行することで生成できます。あとは、このインスタンスを、通常のフォームクラスのとき同様にテンプレートファイルに埋め込めば、更新前の各種フィールドの値がセットされた状態のフォームを表示可能な HTML が生成されることになります。
例えば、UserModelForm
を User
というモデルクラスをベースとするモデルフォームクラスとした場合、User
に対応するレコードの更新フォームの表示は、下記のような update
関数によって実現することができます。app/update.html
はフォームを表示するためのテンプレートファイルであることを想定しています。さらに、更新対象のレコードの id
引数 id
で渡されるものとして関数を定義しています。
from django.shortcuts import render
from .forms import UserModelForm
from .models import User
def update(request, id):
# idが引数idと一致するUserのインスタンスを取得
user = User.objects.get(id=id)
# userからUserModelFormのインスタンスを生成
form = UserModelForm(instance=user)
# userの更新フォームを表示
context = {
'form' : form
}
return render(request, 'app/update.html', context)
UserModelForm()
の引数に instance=user
を指定しているため、user
の各種フィールドのデータがセットされた状態の UserModelForm
のインスタンスが生成されることになります。したがって、このインスタンスのフォームをテンプレートファイルに埋めば、各種フィールドに user
の情報が入力された状態のフォームを表示する HTML が生成されることになります。
request.POST
と instance
両方からのインスタンスの生成
また、モデルフォームクラスのコンストラクタは、request.POST
と instance
引数の両方を指定して実行することも可能です。これにより、instance
引数で指定したレコード(モデルクラスのインスタンス)の各種フィールドが、フォームから送信されてきたデータ(request.POST
)の各種フィールドの値で上書きされた状態のモデルフォームクラスのインスタンスが生成されることになります。
そして、このモデルフォームクラスのインスタンスに、次の save メソッドによるデータベースへの保存 の節で紹介する save
メソッドを実行させれば、データベース内の instance
引数で指定したレコードが更新されることになります。
なので、このようなインスタンスの生成は、先ほどと同様にレコードの更新フォームを実現する時、具体的には、レコードの更新フォームからデータが送信されてきたときに利用されることが多いです。
例えば、先程示した update
関数を下記のように変更すれば、メソッドが GET
以外の場合に、引数で指定された id
と一致する id
を持つレコードが、フォームから送信されてきたデータで更新されることになります。 ちなみに、メソッドが GET
の場合は、変更前の update
関数と同じ処理、すなわち、引数で指定された id
と一致する id
を持つレコードの更新フォームの表示が行われることになります。
from django.shortcuts import render
from .forms import UserModelForm
from .models import User
def update(request, id):
# idが引数idと一致するUserのインスタンスを取得
user = User.objects.get(id=id)
if request.method == 'GET':
# userからUserModelFormのインスタンスを生成
form = UserModelForm(instance=user)
else:
# userをrequest.POSTで更新したUserModelFormのインスタンスを生成
form = UserModelForm(request.POST, instance=user)
# データベースのuserを更新
if form.is_valid():
form.save()
return 略
# userの更新フォームを表示
context = {
'form' : form
}
return render(request, 'app/update.html', context)
ここでポイントになるのが、コンストラクタへの引数の指定の仕方によって save
メソッドの動作が異なるという点になります。
コンストラクタに request.POST
のみを引数に指定してインスタンスを生成し、このインスタンスに save
メソッドを実行させた場合はデータベースへのレコード新規追加による保存処理が行われることになります。
それに対し、コンストラクタに対して instance
を追加で引数指定した場合、instance
によって上書き先のレコードが指定されることになるため、生成されたモデルフォームクラスのインスタンスに save
メソッドを実行させれば、レコードが新規追加されるのではなく instance
引数に指定したインスタンスに対応するレコードを上書き、つまり更新する形で保存処理が行われることになります。
このように、モデルフォームクラスではフォームクラスに比べてコンストラクタの実行の仕方のバリエーションが多く、使い方によっては上記のようなレコードの更新等を簡単に実現することができます。まずは、instance
引数が指定可能であることは覚えておきましょう!
save
メソッドによるデータベースへの保存
次に、モデルフォームクラスの save
メソッドについて解説します。
通常のフォームクラスには save
メソッドは存在しないのですが、モデルフォームクラスには save
メソッドが用意されており、モデルフォームクラスのインスタンスに save
メソッドを実行させることで、そのインスタンスの各種フィールドにセットされているデータをレコードとしてデータベースのテーブルに直接保存することができます。保存先はクラスの定義時に model
に指定したモデルクラスに対応するテーブルとなります。
通常のフォームクラスには save
メソッドは用意されていないため、フォームから送信されてきたデータのデータベースへの保存はモデルクラスの save
メソッドで行う必要があります。そのため、下記のような手順でデータベースへの保存を行う必要がありました(下記はレコードの新規保存時の手順となります)。
request.POST
をコンストラクの引数に指定してフォームクラスのインスタンスを生成- フォームクラスのインスタンスで
is_valid
メソッドを実行 - フォームクラスのインスタンスのデータ属性
cleaned_data
から各種フィールドのデータを取得 - モデルクラスのインスタンスの各種データ属性に、3. で取得したデータをセット
- モデルクラスのインスタンスで
save
メソッドを実行
面倒なのは 3. と 4. で、これらの処理は各種フィールドに対して1つ1つ行う必要があります。そして、それを行ってからモデルクラスの save
メソッドで保存を実行することになります。
それに対し、モデルフォームクラスには save
メソッドが用意されており、フォームから送信されてきたデータのデータベースへの保存は、下記のように直接モデルフォームクラスの save
メソッドを実行することで実現できます。
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のインスタンスを取得
この方法でのインスタンス取得の注意点は、基本的にはデータベースへの保存を行った後にしかモデルクラスのインスタンスが取得できないという点になります。データベースへの保存を行う前にモデルクラスのインスタンスを取得したいのであれば、次の データ属性 instance から取得する で紹介する方法を採用すればよいです。もしくは、save
メソッドの引数に commit=False
を指定することでも、データベースへの保存無しにモデルクラスのインスタンスの取得を実現することが可能です。
ただし、commit=False
はデータベースへの保存をスキップするための引数指定となりますので、save
メソッドでデータベースへの保存が行われなくなります。したがって、データベースへの保存が必要であれば、commit=False
を引数に指定せずに、再度 save
メソッドを実行する必要があります。
データ属性 instance
から取得する
2つ目はモデルフォームクラスのインスタンスのデータ属性 instance
から取得する方法になります。
モデルフォームクラスのインスタンスはデータ属性 instance
を持っており、この instance
は model
に指定したモデルクラスのインスタンスとなります。
ですので、このデータ属性 instance
から直接モデルクラスのインスタンスを取得することができます。ただし、フォームから送信されてきたデータは、安全性を考慮すると妥当性の検証を行って “妥当である” と判断された場合のみ扱うようにする必要があるため、下記のように is_valid
メソッドを実行し、その結果が True
である場合のみデータ属性 instance
を取得するのが良いと思います。
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のインスタンスを取得
is_valid
メソッドによる妥当性の検証
ここまで紹介してきたソースコードでも利用例を示していますが、通常のフォームクラス同様に、モデルフォームクラスにも is_valid
メソッドが存在し、この is_valid
を実行することでモデルフォームクラスのインスタンスの各種フィールドにセットされたデータの妥当性の検証を実施することが可能です。
通常のフォームクラスの時と同様に、フォームから送信されてきたデータは、まずそのデータの妥当性を検証してから使用する必要があります。これは、フォームから送信されてきたデータを安全に扱うためです。そして、この妥当性の検証は、request.POST
を引数に指定してコンストラクタを実行してインスタンスを生成し(instance
引数も指定しても OK)、そのインスタンスに is_valid
メソッドを実行させることで行うことが出来ます。
妥当性の検証の設定は基本的にはモデルクラスで行う
ただし、モデルフォームクラスの場合、基本的には妥当性の検証はモデルクラスに基づいて実施されることになります。
通常のフォームクラスの場合、フォームクラス自体にフィールドを持たせるので、フォームクラスの持つフィールドに基づいて妥当性の検証を行わせることが可能です。ですが、モデルフォームクラスの場合は、model
に指定したモデルクラスのフィールドに従って自動的にフィールドが追加されることになるため、基本的には model
に指定したモデルクラスの各種フィールド(クラス変数)に基づいて妥当性の検証が行われることになります。
モデルフォームクラスのフィールドは model
に指定したモデルクラスに基づいて追加されることになりますが、新たなフィールドを追加する で説明したように、モデルクラスの持たないフィールドをモデルフォームクラス追加で持たせることもできます
このフィールドに関しても、is_valid
メソッド実行時に妥当性の検証が行われることになります
ただし、モデルクラスにおいても、フォームクラスと同様に、各種フィールドに指定する Field
のサブクラスの種類に従った妥当性の検証が行われることになります。例えば、下記のようにモデルクラスを定義した場合、”model = User
を指定したフォームモデルクラス” のインスタンスによる is_valid
メソッド実行時には、age
フィールドの値が整数の場合のみ “妥当である” と判断してくれることになりし、email
フィールドの値がメールアドレスの形式として適切である場合のみ “妥当である” と判断してくれることになります。
class User(models.Model):
age = models.IntegerField()
email = models.EmailField()
また、Field
のサブクラスのコンストラクタの引数に validators
を追加し、ウェブアプリ特有の妥当性の検証の判断基準を追加するようなことも可能になります。このあたりも通常のフォームクラスの時と同様です。
このように、Field
のサブクラスの種類によって適切にデータの妥当の検証が実施される点や、validators
引数の指定によって “妥当であることの判断基準” を開発者が指定可能である点は、フォームクラスでもモデルクラスでも同様となります。
なので、基本的には下記ページの 「妥当である」の判断基準 で解説している内容に基づき、ウェブアプリに必要となる妥当性の検証が行われるようにモデルクラスを定義していけばよいことになります。
【Django入門5】フォームの基本フィールドに指定可能な引数の違いに注意
ただ、1つ厄介な点があって、それはフォームクラスとモデルクラスとで Field
のサブクラスのコンストラクタに指定可能な引数が異なるという点になります。
フォームクラスでは、forms.Field
のサブクラスを利用してフィールドを定義し、モデルクラスでは models.Field
のサブクラスを利用してフィールドを定義することになります。つまり、両方とも Field
のサブクラスを利用してフィールドを定義するのですが、利用するサブクラスは異なるものになります。なので、それらのサブクラスのコンストラクタに指定可能な引数も異なります。
例えば、forms.IntegerField()
に関しては引数として min_value
と max_value
が指定可能であり、これらの引数によってフィールドに入力可能な最小値と最大値を設定することができます。そして、このフィールドを持つフォームをテンプレートファイルに埋め込めば、この引数に応じてフィールド要素のタグの属性が設定されることになります(この場合は min
と max
属性が自動的に付加される)。
例えば、下記のようなフォームクラスを定義したとします。
from django import forms
class UserForm(forms.Form):
age = forms.IntegerField(min_value=0, max_value=200)
このフォームクラスのインスタンスをテンプレートに埋めんで HTML を生成すれば、クライアント側のウェブブラウザでも入力値の妥当性の検証が行われ、引数で指定した min_value
と max_value
の範囲外の値が入力された場合は下の図のような警告文が表示されることになります。
また、クライアント側で妥当性の検証が行われなかったとしても、min_value
と max_value
の範囲外の値が入力された場合は、ウェブアプリ側で is_valid
メソッドが実行され、その時にデータが妥当でないと判断することができます。
それに対し、models.IntegerField()
の場合は引数として min_value
と max_value
は指定不可です。そのため、forms.IntegerField
の時と同様の引数指定では最小値と最大値に対する妥当性の検証が実現できないことになります。ただ、下記のように validators
引数および、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)])
この User
クラスを model
に指定したモデルフォームクラスを定義すれば、そのインスタンスで is_valid
メソッドを実行したときに、送信されてきたデータが 0
~ 200
の間の値でなければ妥当でないと判断することができます。
なので、引数の指定の仕方は違えど、is_valid
メソッドの実行時に同じ判断基準で妥当性の検証を実施することは可能です。
なんですが、上記のように models.IntegerField()
に引数を指定したとしても、forms.IntegerField()
の引数に min_value
と max_value
を指定したときのような、HTML のフィールド要素のタグの属性の設定は行われません。なので、下の図のような、クライアント側のウェブブラウザでの入力値の妥当性の検証は行われないことになります。
で、おそらく、models.IntegerField()
への引数の指定では、HTML のフィールド用のタグの属性の設定を行うことはできないと思います。つまり、forms.IntegerField
で定義するフィールドと models.IntegerField
で定義するフィールドとでは実現できることが異なります。こんな感じで、forms.Field
のサブクラスと models.Field
のサブクラスとでは、名前は似ていても、コンストラクタに指定可能な引数や実現できることが異なります。
ですが、前述のとおり、どちらの場合でも is_valid
メソッドでは同じ判断基準で妥当性の検証は行えるので、機能的には問題ないと考えて良いと思います。
フィールドの再定義による妥当性の判断基準の追加
ただ、どうしても forms.Field
のサブクラス側と全く同じ機能を実現したいのであれば、一応方法はあります。それは、モデルフォームクラスで、フィールドを再定義する方法になります。
新たなフィールドを追加する で説明した通り、モデルフォームクラスでもフィールドを定義することは可能です。そして、モデルフォームクラスでは、通常のフォームクラスと同様に、forms.Field
のサブクラスを利用してフィールドを定義することになります。
さらに、モデルフォームクラスで定義したフィールドのフィールド名が、モデルクラスの持つフィールド名と同じである場合、モデルフォームクラス側で定義したフィールドが優先されることになります。つまり、特定のフィールドを、forms.Field
のサブクラスを利用して定義するフィールドに上書きすることができます。で、これをフィールドの再定義と呼んでいます。
なので、どうしても forms.Field
のサブクラス側と全く同じ機能を持たせたいフィールドがあるのであれば、そのフィールドをモデルフォームクラスで再定義してやれば良いことになります。
例えば、先ほど話題に挙げた age
フィールドを再定義する例は下記のようになります。このように、フィールドを再定義することで、特定のフィールドを forms.Field
のサブクラスを利用して定義することができ、通常のフォームクラスで定義するフィールドと全く同じフィールドをモデルフォームクラスに持たせることが出来ます。
class UserForm(forms.ModelForm):
age = forms.IntegerField(min_value=0, max_value=200)
class Meta:
model = User
fields = ['age']
なので、先ほど例に挙げた、下の図のようなクライアント側での妥当性の検証も、モデルフォームクラスで実現できることになります。
ということで、このモデルフォームクラス側でのフィールドの再定義を利用すれば、妥当性の検証に関しても、通常のフォームクラス利用時と同じ引数指定で実現できますし、機能的にも全く同じものにすることができます。
ただ、モデルフォームクラス側でフィールドの再定義をたくさんすると、モデルフォームクラスを利用するメリットが減るので、基本的にはモデルクラス側で妥当性の検証の設定を行うようにした方が良いと思います。
掲示板アプリでモデルフォームを利用してみる
では、いつも通り、ここまで説明してきた内容を踏まえて、実際にモデルフォームの利用例を示していきたいと思います。
この Django 入門 に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでリレーションを利用してみる で作成したウェブアプリに対してモデルフォームを導入する形で、モデルフォームの利用例を示していきたいと思います。
【Django入門7】リレーションの基本上記ページで紹介しているウェブアプリでは単なるフォームを利用していますが、今回はこれをモデルフォームを利用する形に変更していきます。具体的には、現状 RegisterForm
と PostForm
を用意しており、これらはそれぞれ User
と Comment
のインスタンスを生成するための情報の入力受付を行うフォームとなっています。これらを単なるフォームクラスではなく、モデルフォームクラスを利用するように変更していきます。
具体的には、RegisterForm
に関しては User
をベースとして生成するモデルフォームクラスに、PostForm
に関しては Comment
をベースとして生成するモデルフォームクラスに変更していきます。
スポンサーリンク
掲示板アプリのプロジェクト一式の公開先
この Django 入門 の連載を通して開発している掲示板アプリのプロジェクトは GitHub の下記レポジトリで公開しています。
https://github.com/da-eu/django-introduction
また、前述のとおり、ここでは前回の連載の 掲示板アプリでリレーションを利用してみる で作成したプロジェクトをベースに変更を加えていきます。このベースとなるプロジェクトは下記のリリースで公開していますので、必要に応じてこちらからプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-relation
さらに、ここから説明していく内容の変更を加えたプロジェクトも下記のリリースで公開しています。ソースコードの変更等を行うのが面倒な場合など、必要に応じて下記からプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-modelform
モデルクラスの変更
早速モデルフォームクラス側の実装を行なっていきたいところではあるのですが、今までのアプリと同等の妥当性の検証が行われるように、まずはモデルクラスの変更を行います。is_valid メソッドによる妥当性の検証 で解説したように、今までフォームクラスで行っていた妥当性の検証の設定はモデルクラス側に移行してやる必要があります。
フォームクラスでの妥当性の検証の設定に関しては、下記ページの 掲示板アプリでフォームを利用してみる で行なっています。
【Django入門5】フォームの基本これと同様の妥当性の検証が行われるように、User
クラスのクラス変数の右辺側で実行している models.Field
のサブクラスのコンストラクタの引数の設定を行います。
ただ、is_valid メソッドによる妥当性の検証 で説明したように、age
の右辺の models.IntegerField()
に関しては min_value
引数と max_value
引数が指定できません。そのため、is_valid
メソッドで今までと同様の妥当性の検証が行われるように、引数 validators=[MinValueValidator(0), MaxValueValidator(200)]
の指定を行うようにします。これだと、今までのウェブアプリでは行われていた “クライアント側での最小値・最大値に対する妥当性の検証” が行われないようになってしまうのですが、ここは妥協したいと思います。
今回は実施しませんが、is_valid メソッドによる妥当性の検証 で説明しているように、モデルフォームクラスでのフィールドの再定義により、クライアント側での最小値・最大値に対する妥当性の検証も実現可能です
上記の内容を踏まえ、今までフォームクラスで行っていた妥当性の検証の設定をモデルクラスに移行するために、 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
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(0), MaxValueValidator(200)])
def __str__(self):
return self.username
class Comment(models.Model):
text = models.CharField(max_length=256)
date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='comments')
def __str__(self):
return self.text[:10]
check_username
関数は、元々 forms.py
に定義していたものを models.py
に移動させてきただけになります。
モデルフォームクラスの定義
続いて、このページの本題となるモデルフォームクラスを定義していきたいと思います。下記ページで示したアプリでは、ユーザー登録フォーム RegisterForm
とコメント投稿フォーム PostForm
をフォームクラスとして定義しています。
前述の通り、これらのフォームクラスをモデルフォームクラスに変更していきます。具体的には、RegisterForm
は User
をベースとするモデルフォームクラスに、PostForm
は Comment
をベースとするモデルフォームクラスに置き換えていきます。
モデルフォームクラスの定義 で解説したように、モデルフォームクラスは ModelForm
を継承して定義し、ベースとするモデルクラスを model
に指定する必要があります。あとは、フォームに持たせるフィールドの選択も行う必要がありますので、RegisterForm
には User
の持つフィールド全てを、PostForm
には Comment
の持つ date
以外のフィールド、つまり user
と text
フィールドを持たせるようにしたいと思います。date
を除外するのは、date
はレコード新規保存時に自動的に日時が設定されるフィールドになっており、フォームにフィールドを設けてユーザーに指定してもらう必要がないためです。
また、せっかく 各種フィールドをカスタマイズする でウィジェットのカスタマイズについて解説したので、text
フィールドは文章入力用のウィジェットに変更するようしたいと思います。
上記のようなモデルフォームクラスは、次のように forms.py
を変更することで実現することができます。
from django import forms
from .models import User, Comment
class RegisterForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email', 'age']
class PostForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['user', 'text']
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
メソッドを実行するようにしておくことで、モデルクラスを変更してもビューの変更が不要となります。
このように、モデルフォームクラスを利用することでビューの実装も楽になりますし、モデルクラスの変更に伴うビューの変更に関しても最小限に抑えることが可能です。そして、これらはバグを減らしたりコードの再利用性を高めるというメリットにつながりますので、特にモデルのフィールドと対応するフォームを作成するような場合は、積極的にモデルフォームの仕組みを利用することをオススメします。
動作確認
最後に、今回実装した内容に関して動作確認を行なっておきましょう!
マイグレーションの実行
今回は 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/
これにより、ユーザー登録フォームが表示されると思います。といっても、前回の動作確認時とフィールドや見た目は変わらないはずです。実際にユーザー登録を行い、今までのウェブアプリと同様に、登録したユーザーがユーザー一覧に表示されることを確認してみてください。
コメント投稿フォームの確認
次はウェブブラウザから下記の URL にアクセスしてみてください。
http://localhost:8000/forum/post/
これにより、コメント登録フォームが表示されると思います。
今回、forms.py
で text
フィールドのウィジェットとして Textarea
を指定しているため、text
フィールドの見た目が変化していることが確認できると思います。ですが、機能的には前回開発したウェブアプリと全く同じになっているはずです。user
で投稿者となるユーザーを選択し、適当に text
フィールドにテキストを入力してコメント投稿を行なってみてください。
こちらも、投稿したコメントが投稿一覧に表示されれば動作確認 OK となります。
まとめ
このページでは Django におけるモデルフォームについて解説しました!
モデルフォームはフォームをモデルに基づいて自動的に定義する仕組みであり、モデルに基づいたフォームを作成できるようになることでフォームを楽に定義することができるようになります。また、モデルフォームの導入により、ビューの実装も簡素化することができます。
別に通常のフォームさえ利用できればモデルフォームを利用しなくてもアプリを開発することは可能ではありますが、効率的に開発を進めるという意味では開発者にとってモデルフォームは強力な仕組みとなると思います。特にモデルとフォームのフィールドは直結することも多いため、そういう場合はモデルフォームを積極的に利用してみましょう!
次の連載ではウェブアプリへのログイン機能の搭載手順について解説します。ウェブアプリではログイン機能を搭載しているものが多いですよね?!ログイン機能の搭載手順を知っていれば、そういったウェブアプリもあなた自身の手で開発することができるようになります。ぜひ次の連載のページも読んでみてください!
【Django入門10】ログイン機能の実現