見出し画像

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
を使います。

アプリの構成

アプリは次のような構成になっています。

  .gitignoredb.sqlite3manage.py
│
├─airline
│  │  asgi.py
│  │  settings.py
│  │  urls.py
│  │  wsgi.py
│  │  __init__.py
│  │
│
└─flightsadmin.pyapps.pyforms.pymodels.pytests.pyurls.pyviews.py
   │  __init__.py
   │
   ├─migrations
   │
   ├─templates
   │  └─flightsindex.htmllayout.htmlwithout_model.htmlwith_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では次のように表示できます。

画像2

画像1

見た目は同じです。
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()




いいなと思ったら応援しよう!