Django の ModelForm でモデルと連動したフォームを作る

通常の Form クラスではフィールドを 1 つずつ手書きしますが、ModelForm を使えばモデルの定義からフォームを自動生成できます。モデルのフィールドとフォームのフィールドが二重定義にならず、保存処理も .save() 一発で完了するのが大きな利点です。

ModelForm の基本

ModelFormforms.ModelForm を継承し、内部クラス Meta で対象モデルとフィールドを指定します。

from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'body', 'category']

Article モデルの titlebodycategory フィールドに対応するフォームフィールドが自動的に作られます。CharFieldforms.CharField に、ForeignKeyforms.ModelChoiceField に——というように、モデルのフィールド型から適切なフォームフィールドへの変換が行われます。

Form

フィールドを手動で定義する。モデルと無関係なフォーム(検索フォームなど)に向く

ModelForm

モデルからフィールドを自動生成する。CRUD 操作で威力を発揮する

fields と exclude

Meta クラスではフォームに含めるフィールドを明示的に指定します。

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'body', 'category']

fields = '__all__' と書くと全フィールドがフォームに含まれますが、セキュリティ上の理由から推奨されません。ユーザーに編集させたくないフィールド(is_staffis_superuser など)まで含まれてしまう危険があるためです。

逆に、特定のフィールドだけを除外したい場合は exclude を使います。

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        exclude = ['published_at', 'author']

ただし exclude も「将来モデルにフィールドを追加したとき、意図せずフォームに含まれる」リスクがあるため、fields で必要なものだけを列挙する方が安全です。

ビューでの使い方(新規作成)

ModelForm の最大の利点は .save() メソッドです。バリデーション通過後に呼び出すだけで、モデルインスタンスの作成とデータベースへの保存が一括で行われます。

from django.shortcuts import render, redirect
from .forms import ArticleForm

def article_create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('article_list')
    else:
        form = ArticleForm()
    return render(request, 'articles/create.html', {'form': form})

フォームに含まれていないフィールド(例えば author)を保存時にセットしたい場合は、commit=False を使います。

def article_create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            return redirect('article_list')
    else:
        form = ArticleForm()
    return render(request, 'articles/create.html', {'form': form})

commit=False はデータベースに保存せずにモデルインスタンスだけを返します。フィールドを手動で追加した後に article.save() を呼び出すことで、不足していた値を補完してから保存できます。

ビューでの使い方(編集)

既存レコードの編集では、instance パラメータに対象オブジェクトを渡します。

from django.shortcuts import render, redirect, get_object_or_404
from .forms import ArticleForm
from .models import Article

def article_edit(request, pk):
    article = get_object_or_404(Article, pk=pk)
    if request.method == 'POST':
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            form.save()
            return redirect('article_detail', pk=article.pk)
    else:
        form = ArticleForm(instance=article)
    return render(request, 'articles/edit.html', {'form': form})

instance=article を渡すことで、フォームは既存データで初期化されます。POST 時にも instance を渡せば、save() は新規作成ではなく更新として動作します。

GET: instance で既存データを初期化して表示

POST: instance と送信データでフォームを構築

save() で既存レコードを更新

widgets と labels のカスタマイズ

Meta クラスで widgetslabels を指定すると、フォームの見た目を調整できます。

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'body', 'category']
        widgets = {
            'title': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': 'タイトルを入力'
            }),
            'body': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 10
            }),
        }
        labels = {
            'title': '記事タイトル',
            'body': '本文',
            'category': 'カテゴリ',
        }

attrs に辞書を渡すと HTML 属性を追加できます。CSS フレームワーク(Bootstrap など)のクラス名を付けたいときにこの方法がよく使われます。

ModelForm を使うべき場面

モデルのデータを作成・編集するフォームなら、ほぼ確実に ModelForm を使うべきです。フィールドの定義がモデルと同期されるため、モデルを変更したときの修正箇所が減り、.save() による保存処理も簡潔になります。

一方で、検索フォームやログインフォームのようにモデルと 1 対 1 で対応しないフォームには、通常の Form クラスを使います。用途に応じた使い分けが、Django のフォーム設計を見通しよくするポイントです。