djangoのmodelFormクラスについてまとめてみた

今回のお題
-
今回はdjangoのformクラスについてまとめます。

Ruby on Railsではフォームタグ（正確にはformタグヘルパー）にmodelオプションというものがあるのですが、同じようなものはないかと探していたところmodelFormクラスにたどりつきました。

同じような用途で使えるものとしてformクラスというものもあるらしいのですが、そちらについては機会があればまとめます。

目次
-

- 基本方針
- forms.pyの編集
- views.pyの編集
- テンプレートの編集
- おまけ〜formModelクラスとformクラスの違い

基本方針
-
まずは基本方針をざっくりと話します。

djangoにはmodelFormクラスというクラスが最初から用意されています。

なのでこれを継承させてformクラスを新たに作成し、そこに表示したい内容をフィールドとして持たせればビューに表示することができます。

言葉だけではよくわからないと思うので、早速実例に移ります。
forms.pyの編集
-
まずはアプリケーションフォルダの直下にforms.pyを新規作成し、そこにPostモデルに対応するPostFormクラスを作成します。

```python:forms.py
class PostForm(ModelForm):
  class meta:
    model = Post
    fields = ["title", "image", "text"]
    # フィールドについては全件指定であれば以下の書き方でも可
    # fields = '__all__'
    labels = {
      "title": "タイトル",
      "image": "画像",
      "text": "説明文"
    }
```
基本的には鋳型になるモデルとそのモデルのどのフィールドをフォームに指定するかを記述します。
labelsについては必須ではありませんが、省略するとフィールド名がそのままテンプレート上のフォームの項目名になります。

では、modelFormができたので次にivewsを編集します。

views.pyの編集
-
ビューでフォームを扱う場面は大きく分けて4つあります。
新規作成及びレコードの更新のためのフォームに遷移する際と、フォームから情報を受け取ってテーブルに反映させる場合です。

新規作成画面への遷移時には何も情報が入っていない空のmodelFormが作られます。
一方で編集画面へ遷移したりフォームからデータを受け取ったりする場合には、テーブルないしはフォームの情報を適切に参照しつつmodelFormを操作する必要があります。

```python:myapp/views.py
# 新規作成
def new(request):
  # フォームへの遷移時
  if request.method == "GET":
    post_form = PostForm()
    return render(request, "myapp/new.html", { "post_form": post_form })
  # フォームの情報の受け取り
  elif request.method == "POST":
    post_form = PostForm(request.Post)
    if post_form.is_valid():
      post_form.save()
      posts = Post.objects.all()
      return render(request, "myapp/index.html", { "posts": posts })
    else:
      return render(request, "myapp/new.html", { "post_form": post_form })

# レコードの更新
def edit(request, pk):
  post = Post.objects.filter(id=pk)
  if request.method == "GET":
    post_form = PostForm(instance=post)
    return render(request, "myapp/edit.html", { "post": post, "post_form": post_form })
  elif request.method == "POST":
    post_form = PostForm(request.POST, instance=post)
    if post_form.is_valid()
      post_form.save()
      return render(request, "myapp/show.html", { "post": post })
    else:
      return render(request, "myapp/edit.html", { "post": post, "post_form": post_form })
```
ざっと書きましたが、modelFormに関する要点は以下の通りです。

```python:空のmodelFormの生成
post_form = PostForm()
```

```python:レコードを参照してのmodelForm作成
post = Post.objects.filter(id=pk)
post_form = PostForm(instance=post)
```
"instance="で参照したインスタンスの属性値がフォームにコピーされます。

```python:フォームの情報を元に新規作成
post_form = PostForm(request.POST)
post_form.save()
```
違和感があるかもしれませんが、post_formをsaveすることで対応するPostモデルのレコードがテーブルに保存されます。

```python:フォームの情報を元にレコードを更新
post = Post.objects.filter(id=pk)
post_form = PostForm(request.POST, instance=post)
post_form.save()
```
post_formを生成する際に"instance="として対応するレコードを指定しています。
この部分が抜けると上書きがなされずに新しいレコードが追加されてしまいます。

```python:フォームをそのままテンプレートに渡す
return render(request, "XXXX.html", { "post_form": post_form })
```
PostFormのインスタンスを、post_formというキーでテンプレートに渡しています。
これでビューでの処理は完了したので、これをテンプレートで表示していきます。

テンプレートの編集
-
テンプレートの編集は非常に簡単です。

```html:myapp/edit.html
<form ...省略...>
{{post_form}}
</form>
```
上記の書き方でフォームが作成され、必要に応じて初期値も表示されます。

外側のform要素は別途で必要になるので注意してください。

ちなみに、以下のように記述することである程度は形式を決めることが可能です。

```html
<!-- テーブル形式 -->
<form>
  <table>
    {{ post_form }}
  </table>
<form>

<!-- inputとlabelのセットをpタグで囲む -->
<form>
  {{ post_form.as_p }}
</form>

<!-- inputとlabelのセットを箇条書き形式にする -->
<form>
  {{ post_form.as_ul }}
</form>
```
また、このBootStrapを用いてこのフォームをスタイリングする方法については[別記事](https://qiita.com/keishi04hrikzira/items/e38d583f4a370315629c)でまとめているます。
よろしければご参照ください。

おまけ〜formクラスとmodelFormクラスの違い
-
最後におまけとして、冒頭でふれたformクラスとmodelFormクラスの違いを説明しておきます。

ざっくりいうと、

- modelFormクラスはmodelを指定して鋳型にするので、フィールドの定義などが簡単。
- modelFormクラスのインスタンスをsaveすれば、対応するモデルのレコードがDBに保存される。
- formクラスはモデルを鋳型にするわけではないので、フィールドの定義は自分でしなければならない。
- formクラスとモデルの相互変換もmodelFormクラスほど簡単ではないので、viewsでの処理も若干増える。

という感じになります。

なので、基本的にはmodelFormクラスの方が楽にはなります。

もちろん自分で定義するぶんカスタマイズ性が高くなるというメリットはありますが・・・