Python | Web フレームワーク:Django のフォーム

Python
スポンサーリンク

概要(Django のフォーム=「入力フォームとバリデーションをまとめて面倒見てくれる仕組み」)

Django のフォームは、

「HTML フォームの入力項目」と
「その値のチェック(バリデーション)」と
「エラー表示」

を、ひとまとめに扱うための仕組みです。

生の HTML と手書きバリデーションでやろうとすると、
すぐにコードがぐちゃぐちゃになりますが、
Django のフォームを使うと、

どんな項目があるか
どんな型か
必須かどうか
どんなエラーを出すか

を Python のクラスとして定義できて、
テンプレート側では「フォームを描画する」ことに集中できます。

ここから、
フォームの基本構造
ビューとの連携
テンプレートでの表示
ModelForm との違い
設計のポイント

を順番にかみ砕いていきます。


なぜフォームクラスが必要なのか(生の HTML+手書きバリデーションのつらさ)

HTML だけでフォームを作ると何が起きるか

例えば、「名前とメールアドレスを入力してもらうフォーム」を考えます。

テンプレートにこう書くとします。

<form method="post">
  {% csrf_token %}
  <label>名前: <input type="text" name="name"></label>
  <label>メール: <input type="email" name="email"></label>
  <button type="submit">送信</button>
</form>

ビュー側では、POST された値をこう扱います。

from django.shortcuts import render
from django.http import HttpResponse

def contact(request):
    if request.method == "POST":
        name = request.POST.get("name")
        email = request.POST.get("email")

        # ここでバリデーションを自分で書く
        errors = []
        if not name:
            errors.append("名前は必須です。")
        if not email:
            errors.append("メールは必須です。")
        # メール形式チェックも自分で書く?

        if errors:
            return render(request, "contact.html", {"errors": errors})
        return HttpResponse("OK")
    return render(request, "contact.html")
Python

最初はこれでも動きますが、
項目が増えたり、チェックが複雑になると、

必須チェック
型チェック(整数かどうか、メール形式かどうか)
エラーメッセージの管理
入力値の再表示

などを全部自分で書くことになり、
ビューが一気に肥大化します。

ここで「フォームクラス」にまとめると、
これらを Django に任せられるようになります。


Django フォームの基本構造(forms.Form)

フォームクラスを定義する

まずは、モデルと関係ない「純粋なフォーム」から始めます。

forms.py をアプリ内に作り、こう書きます。

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(label="名前", max_length=100)
    email = forms.EmailField(label="メールアドレス")
    message = forms.CharField(label="メッセージ", widget=forms.Textarea)
Python

ここでやっていることは、

name は文字列で、最大 100 文字
email はメール形式の文字列
message はテキストエリア

という「入力項目の定義」です。

重要なのは、
この時点で「バリデーションのルール」もほぼ決まっていることです。

CharField は「空はダメ(デフォルト必須)」
EmailField は「メール形式でなければエラー」
max_length を超えたらエラー

といったチェックを、Django が自動でやってくれます。

ビューでフォームを使う流れ

次に、このフォームをビューで使います。

from django.shortcuts import render
from django.http import HttpResponse
from .forms import ContactForm

def contact(request):
    if request.method == "POST":
        form = ContactForm(request.POST)
        if form.is_valid():
            name = form.cleaned_data["name"]
            email = form.cleaned_data["email"]
            message = form.cleaned_data["message"]
            # ここでメール送信などの処理をする
            return HttpResponse("送信しました")
    else:
        form = ContactForm()

    return render(request, "contact.html", {"form": form})
Python

ここでの重要ポイントを整理します。

POST のときは ContactForm(request.POST) として「送信されたデータでフォームを作る」
form.is_valid() を呼ぶと、全フィールドのバリデーションが実行される
バリデーションが通れば form.cleaned_data に「型が整えられた値」が入る
GET のときは ContactForm() で空のフォームを作る

ビュー側は、

「フォームを作る」
「バリデーションを走らせる」
「OK なら処理する、ダメなら同じテンプレートを再表示する」

という流れだけを意識すればよくなります。


テンプレートでフォームを表示する(form.as_p から始める)

最も簡単な描画方法:{{ form.as_p }}

contact.html をこう書いてみます。

<h1>お問い合わせ</h1>

<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">送信</button>
</form>

{{ form.as_p }} は、
フォームの各フィールドを <p> タグで囲んだ HTML に変換してくれる簡易メソッドです。

これだけで、

ラベル
入力欄
エラーメッセージ

を含んだフォームが自動で描画されます。

最初はこれで十分です。
「フォームクラスを定義して、ビューで渡して、テンプレートで {{ form.as_p }}
という流れを体で覚えましょう。

各フィールドを個別に描画する

少し慣れてきたら、
フィールドごとに細かく制御したくなります。

例えば、こう書けます。

<form method="post">
  {% csrf_token %}

  <div>
    {{ form.name.label_tag }}
    {{ form.name }}
    {{ form.name.errors }}
  </div>

  <div>
    {{ form.email.label_tag }}
    {{ form.email }}
    {{ form.email.errors }}
  </div>

  <div>
    {{ form.message.label_tag }}
    {{ form.message }}
    {{ form.message.errors }}
  </div>

  <button type="submit">送信</button>
</form>

ここでのポイントは、

form.name は「name フィールドの input 要素」
form.name.label_tag は「ラベル(<label>)」
form.name.errors は「そのフィールドのエラーメッセージ」

という対応です。

この書き方をすると、
CSS クラスを付けたり、
レイアウトを細かく調整したりしやすくなります。


バリデーションの仕組みをもう少し深掘りする

is_valid() と cleaned_data の関係

form.is_valid() を呼ぶと、
Django は次のことをします。

各フィールドの型チェック(EmailField ならメール形式かどうかなど)
必須チェック(required=True のフィールドが空でないか)
max_length などの制約チェック
フォーム全体のカスタムバリデーション(後述)

すべてのチェックが通ると True を返し、
そのときだけ form.cleaned_data に「きれいな値」が入ります。

例えば、IntegerField なら文字列から整数に変換された値、
EmailField なら前後の空白が削られたメールアドレス、
といった具合です。

逆に、エラーがある場合は False を返し、
form.errors にエラーメッセージが入ります。
テンプレートで {{ form.errors }}{{ form.field.errors }} を表示できます。

フィールドごとのカスタムバリデーション(clean_フィールド名)

例えば、「名前は ‘test’ を禁止したい」というような
独自ルールを追加したいとします。

フォームクラスにこう書けます。

class ContactForm(forms.Form):
    name = forms.CharField(label="名前", max_length=100)
    email = forms.EmailField(label="メールアドレス")
    message = forms.CharField(label="メッセージ", widget=forms.Textarea)

    def clean_name(self):
        value = self.cleaned_data["name"]
        if value.lower() == "test":
            raise forms.ValidationError("この名前は使用できません。")
        return value
Python

clean_フィールド名 というメソッドを定義すると、
そのフィールドのバリデーションの最後に呼ばれます。

ここでエラーを投げると、そのフィールドにエラーメッセージが付きます。

重要なのは、

フォームクラスの中に「バリデーションロジック」を閉じ込められる
ビュー側は is_valid() を呼ぶだけでよい

という構造です。


ModelForm(モデルとフォームを一気に結びつける)

モデルとフォームが対応している場合は ModelForm が便利

例えば、Book モデルがあるとします。

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    price = models.IntegerField()
Python

この Book を作成・編集するフォームを作りたいとき、
わざわざ forms.Form で同じフィールドを二重に書くのは面倒です。

そこで使うのが forms.ModelForm です。

from django import forms
from .models import Book

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ["title", "price"]
Python

これだけで、

title は CharField
price は IntegerField

というフォームが自動で生成されます。

モデルのフィールド定義を元に、
フォームのフィールドとバリデーションルールが作られるイメージです。

ModelForm を使ったビューの流れ

Book を新規作成するビューはこう書けます。

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

def create_book(request):
    if request.method == "POST":
        form = BookForm(request.POST)
        if form.is_valid():
            form.save()  # Book インスタンスを DB に保存
            return redirect("book_list")
    else:
        form = BookForm()
    return render(request, "book_form.html", {"form": form})
Python

ここでのポイントは、

form.save() が「モデルインスタンスを作って保存する」までやってくれる
フォームのバリデーションとモデルの保存が一体化している

ということです。

ModelForm を使うと、

フォーム定義
バリデーション
モデル保存

をかなり少ないコードでまとめられます。


設計のポイント(フォームに何を任せて、ビューに何を残すか)

フォームは「入力項目とバリデーションの責任者」

フォームクラスに任せるべきことは、

どんな項目があるか
型や制約(必須、最大長、形式)
フィールドごとのバリデーション
フォーム全体の整合性チェック

です。

ビューは、

どのフォームを使うか決める
GET なら空フォーム、POST なら送信データ付きフォームを作る
is_valid() を呼んで、OK ならビジネスロジックを実行する
テンプレートにフォームを渡して描画させる

という「流れの制御」に集中させると、
役割分担がきれいになります。

「フォームを通さない入力」は基本的に危険だと意識する

Django では、

ユーザーからの入力(POST データなど)は、
基本的に「フォームを通して検証する」

という前提で設計されています。

フォームを使わずに request.POST を直接いじりまくると、

必須チェック漏れ
型変換ミス
エラーメッセージの表示忘れ

などが起きやすくなります。

「ユーザー入力があるなら、まずフォームを考える」
という癖をつけておくと、安全で読みやすいコードになりやすいです。


まとめ(Django のフォームは「入力とバリデーションの全部を引き受ける窓口」)

Django のフォームを初心者向けに整理すると、こうなります。

フォームクラス(forms.Form / forms.ModelForm)は、「どんな入力項目があり、どんな型・制約・バリデーションが必要か」を Python のクラスとして表現する仕組み。
ビューでは、GET で空フォームを作り、POST で送信データ付きフォームを作り、is_valid() でバリデーションを走らせ、OK なら cleaned_dataform.save() を使って処理する、という流れになる。
テンプレートでは {{ form.as_p }} でざっくり描画するところから始め、慣れてきたら {{ form.field }}{{ form.field.errors }} を使って細かくレイアウトを制御できる。
ModelForm を使うと、モデルのフィールド定義からフォームが自動生成され、バリデーションと保存処理をかなり少ないコードで書ける。
設計の肝は、「ユーザー入力はフォームに通す」「フォームは入力とバリデーションの責任者、ビューは流れの責任者」という役割分担を意識すること。

タイトルとURLをコピーしました