DjangoのModelFormとformの使い分け
Djangoで、ユーザーから受け取るデータを扱う時forms.ModelFormやforms.Formを使うと便利です。
しかし、modelを絡めて扱おうと思った時、
FormとModelFormってどう違うの?
という疑問にハマったので、忘備録として残しておきます。
説明の為に、簡単なアプリを作ります。
都市名(都市コード)
という形で空港を記録していくアプリです。
1. Airportというモデルに、
2. html上のpost formからもらったデータを保存する
という機能を持たせたいと思います。
2.の部分でforms.Formとforms.ModelFormを使い分けてみます。
仮想的な環境として、
python3.8
django3.1.1
を使います。
アプリの構成
アプリは次のような構成になっています。
.gitignore
│ db.sqlite3
│ manage.py
│
├─airline
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│
└─flights
│ admin.py
│ apps.py
│ forms.py
│ models.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
├─migrations
│
├─templates
│ └─flights
│ index.html
│ layout.html
│ without_model.html
│ with_model.html
実際に触ってみたい人は、githubをどうぞ。
https://github.com/msamunetogetoge/note/tree/form_vs_modelform
flights/models.py は以下のようになっています。
from django.db import models
from django.forms import ModelForm
# Create your models here.
class Airport(models.Model):
code = models.CharField(max_length=3 )
city = models.CharField(max_length=64)
def __str__(self):
return f"{self.city} ({self.code})"
class AirportModelForm(ModelForm):
class Meta:
model = Airport
fields = '__all__'
Airport モデルにcode=都市コード, city=都市名という形でデータを記録します。ModelFormも作っています。
Formはflights/forms.pyにあります。
from django import forms
class AirportForm(forms.Form):
code = forms.CharField(max_length=3 )
city = forms.CharField(max_length=64 )
大事なファイルはこれだけです。
htmlでは次のように表示できます。
見た目は同じです。
htmlの要素としてどう違うか、内部での処理はどう違うか調べます。
まずは、FormとModelFormがhtmlの要素としてどのように受け取られるのか調べます。
htmlとしてのFormとModelForm
FormやModelFormがhtmlでどう受け取られるか調べます。
shellに入り、FormやModelFormを呼び出して、print()することで調べる事が出来ます。AirportFormは次のようになります。
python manage.py shell
from flights.forms import AirportForm
f = AirportForm()
print(f)
<tr><th><label for="id_code">Code:</label></th><td><input type="text" name="code" maxlength="3" required id="id_code"></td></tr>
<tr><th><label for="id_city">City:</label></th><td><input type="text" name="city" maxlength="64" required id="id_city"></td></tr>
特に何も指定しないでおくと、テーブルの要素として認識されます。
大事なのは<input>部分ですが。
オプションでf.as_ul()などが出来ますが、そうすると<th>が<li>に変わります。
print(f.as_ul())
<li><label for="id_code">Code:</label> <input type="text" name="code" maxlength="3" required id="id_code"></li>
<li><label for="id_city">City:</label> <input type="text" name="city" maxlength="64" required id="id_city"></li>
次に、ModelFormがどうなるか調べます。
from flights.models import AirportModelForm
f2 = AirportModelForm()
print(f2)
<tr><th><label for="id_code">Code:</label></th><td><input type="text" name="code" maxlength="3" required id="id_code"></td></tr>
<tr><th><label for="id_city">City:</label></th><td><input type="text" name="city" maxlength="64" required id="id_city"></td></tr>
Formと同じ出力が得られるようです。
FormとModelFormは同じものなのでしょうか?
答えは、
htmlのオブジェクトとしては同じもの
です。
この意味はデータをやり取りしてみると分かります。
FormとModelFormの違い
ModelFormとFormの違いは、modelにデータを保存しようと思った時に出て来ます。
views.pyで確認します。
def index(request):
return render(request, "flights/index.html")
def form_without_model(request):
if request.method == "POST":
code = request.POST["code"]
city = request.POST["city"]
A = Airport(code=code, city=city)
A.save()
return render(request, "flights/without_model.html",{
"form": AirportForm,
"airport": Airport.objects.last()
})
else:
return render(request, "flights/without_model.html",{
"form": AirportForm
})
def form_with_model(request):
if request.method == "POST":
form = AirportModelForm(request.POST)
if form.is_valid():
form.save()
return render(request, "flights/with_model.html",{
"form": AirportModelForm,
"airport": Airport.objects.last()
})
else:
return render(request, "flights/with_model.html",{
"form": AirportModelForm
})
form_without_model とform_with_modelを見比べます。
どちらもpostリクエストを受け取って、そこからデータを抽出してモデルに保存する関数です。
if request.method == "POST":
までは同じですが、次のモデルにデータを保存する過程が少し違います。
form_without_modelの方は
<input name="code">や<input name="city">の情報を使って、モデルインスタンスを作成し、それを保存しています。
code = request.POST["code"]
city = request.POST["city"]
A = Airport(code=code, city=city) #インスタンス作成
A.save() #データベースに保存
一方、form_with_model の方は、
AirportModelForm というModelFormに直接postを与えています。
次に、データがモデルに適合しているかどうか検査し、適合していればそのままsave()を使ってデータをモデルに保存しています。
form = AirportModelForm(request.POST) #モデルインスタンス作成
if form.is_valid(): #データの検査
form.save() #データベースに保存
・モデルに直接アクセスできる。
・データを検査できる。
のがModelFormとFormの違いです。
もしも、変な値が入力されていると、form.is_valid()=Falseとなり、form.errors["city"]などにエラーメッセージが出力されます。
models.pyで、validatorを設定している時は便利です。
これを利用して、htmlページに警告文を出力したりできます。
全部htmlの中でも出来ますが、modelを使おうと思ったらModelFormを使った方が楽だったりします。
同じような説明が公式ドキュメントにもあります。
文字でなく、ファイルを扱いたい場合はimagefield やfilefieldを使います。
そ の場合も、大体同じようにデータをやり取りすることが出来ます。
公式ドキュメントに説明があります。
まとめ
・FormとModelFormは、htmlのオブジェクトとしては同じ
・Form は、requestの情報からインスタンスを作成しなおす
・ModelFormは、requestの情報をそのままモデルに反映できる
・ModelFormに入力されたデータは.is_valid()で検査できる
・モデルへの保存は.save()