通常の Form クラスではフィールドを 1 つずつ手書きしますが、ModelForm を使えばモデルの定義からフォームを自動生成できます。モデルのフィールドとフォームのフィールドが二重定義にならず、保存処理も .save() 一発で完了するのが大きな利点です。
ModelForm の基本
ModelForm は forms.ModelForm を継承し、内部クラス Meta で対象モデルとフィールドを指定します。
from django import forms from .models import Article class ArticleForm(forms.ModelForm): class Meta: model = Article fields = ['title', 'body', 'category']
Article モデルの title・body・category フィールドに対応するフォームフィールドが自動的に作られます。CharField は forms.CharField に、ForeignKey は forms.ModelChoiceField に——というように、モデルのフィールド型から適切なフォームフィールドへの変換が行われます。
フィールドを手動で定義する。モデルと無関係なフォーム(検索フォームなど)に向く
モデルからフィールドを自動生成する。CRUD 操作で威力を発揮する
fields と exclude
Meta クラスではフォームに含めるフィールドを明示的に指定します。
class ArticleForm(forms.ModelForm): class Meta: model = Article fields = ['title', 'body', 'category']
fields = '__all__' と書くと全フィールドがフォームに含まれますが、セキュリティ上の理由から推奨されません。ユーザーに編集させたくないフィールド(is_staff や is_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 クラスで widgets や labels を指定すると、フォームの見た目を調整できます。
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 のフォーム設計を見通しよくするポイントです。