ブログBLOG

\ Web・ゲーム開発に関する情報を発信中 /

【Django】フォームエラーでサブミットができない時に問題のフィールドを簡単に発見する方法

Django
django , python
20230330_django-form-verification-tmb

ご覧いただきありがとうございます!
領護(りょうご)です。

フォームのサブミットボタンを押しても反応なし...みたいなことないですか?
今回は、Djangoのフォームエラーでサブミットができない時に問題のフィールドを簡単に発見する方法を解説します。

目次

実行環境

OS・・・Windows10
Docker Desktop・・・4.17.1
Python・・・3.10.9
Django・・・4.1.7

ディレクトリ構成

blog_web/
├── app/
│   ├── static/
│   │   └── css/
│   │       └── base.css
│   ├── templates/
│   │   ├── blog/
│   │   │   ├── blog_list_page.html
│   │   │   └── blog_post_page.html
│   │   └── base.html
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── blog/
│   ├── migrations/
│   │   ├── __init__.py
│   │   └── 0001_initial.py
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── db.sqlite3
├── docker-compose.yml
├── Dockerfile
├── manage.py
└── requirements.txt

上記のディレクトリ構成で、ブログ記事を投稿して、投稿一覧ページに表示する機能を作成します。
プロジェクト名やフォルダ名は、ご自身のプロジェクトに読み替えてください。

モデルの作成

ブログ記事を投稿する「Article」モデルを作成します。

blog/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

# ブログ記事投稿モデル
class Article(models.Model):

    # 記事タイトル(文字制限 64文字)
    title = models.CharField(verbose_name=_("記事タイトル"), max_length=64)

    # 記事本文
    content = models.TextField(verbose_name=_("記事本文"))

    # 投稿日時(投稿時に自動で当日の日時を保存)
    posted_at = models.DateTimeField(verbose_name=_("投稿日時"), auto_now_add=True)

記事タイトル、記事本文、投稿日時の3つのフィールドを作成

python manage.py makemigrations
python manage.py migrate

マイグレーションファイルを作成してデータベースに反映

フォームの作成

ブログ記事を投稿する「ArticleForm」フォームを作成します。

blog/forms.py
from django import forms
from .models import Article

# ブログ記事投稿フォーム
class ArticleForm(forms.ModelForm):

    # フォーム表示設定
    class Meta:

        # 使用するモデルを指定
        model = Article

        # 使用するフィールドを指定
        fields = ("title", "content")

記事タイトルと記事本文を入力するフィールドを指定

ビューの作成

投稿した記事を一覧表示する「ブログ記事一覧ページ」とブログ記事を投稿する「ブログ記事投稿ページ」の2ページを作成します。

blog/views.py
from django.urls import reverse_lazy
from django.views.generic import ListView, CreateView
from .models import Article
from .forms import ArticleForm

# ブログ記事一覧ページ
class BlogListPage(ListView):

    # 使用するテンプレート
    template_name = "blog/blog_list_page.html"

    # 一覧表示するモデル
    model = Article


# ブログ記事投稿ページ
class BlogPostPage(CreateView):

    # 使用するテンプレート
    template_name = "blog/blog_post_page.html"

    # 保存するモデル
    model = Article

    # 使用するフォーム
    form_class = ArticleForm

    # ブログ投稿成功時の遷移先URL
    success_url = reverse_lazy("blog_list")

ブログ記事が正しく投稿できれば、ブログ記事一覧ページに遷移するように遷移先を指定しています。

HTMLテンプレートの作成

「ブログ記事一覧ページ」「ブログ記事投稿ページ」に使用するHTMLを作成します。

blog_list_page.html
<!-- ブログ記事一覧ページ用 -->
{% extends "base.html" %}

{% block main %}
<h1>ブログ記事一覧ページ</h1>
<ul>
  {% for article in object_list %}
  <li>
    ブログタイトル:{{ article.title }}<br>
    ブログ本文:{{ article.content }}<br>
    投稿日時:{{ article.posted_at }}
  <li>
  {% endfor %}
</ul>

ブログ記事一覧ページに使用する「blog_list_page.html」を作成

blog_post_page.html
<!-- ブログ記事投稿ページ用 -->
{% extends "base.html" %}

{% block main %}
<h1>ブログ記事投稿ページ</h1>
<form action="" method="POST">
  {% csrf_token %}
  <div class="form-item">
    {{ form.title.label }}
    {{ form.title }}
  </div>
  <div class="form-item">
    {{ form.content.label }}
    {{ form.content }}
  </div>
  <button type="submit" class="btn btn-primary">投稿する</button>
</form>
{% endblock %}

ブログ記事投稿ページに使用する「blog_post_page.html」を作成

URLの作成

最後にページを表示するためのURLを設定します。

blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ブログ記事一覧ページ
    path(
        "list",
        views.BlogListPage.as_view(),
        name="blog_list",
    ),
    # ブログ記事投稿ページ
    path(
        "post",
        views.BlogPostPage.as_view(),
        name="blog_post",
    ),
]

「ブログ記事一覧ページ」「ブログ記事投稿ページ」にそれぞれURLを設定

app/urls.py
from django.contrib import admin
from django.urls import include, path
from blog import urls as blog_urls

urlpatterns = [
    # Django管理画面ログイン
    path("admin/", admin.site.urls),
    # ブログページ
    path("blog/", include(blog_urls)), #追加
]

appプロジェクトの「urls.py」にblogアプリの「urls.py」が読み込めるように追加

動作チェック

20230330_django-form-verification-01

ブログ記事投稿ページから記事タイトルと記事本文を入れて「投稿する」をクリックします。

20230330_django-form-verification-02

ブログ記事一覧ページに投稿内容が表示されます。

エラーを起こしてみる

ブログ記事投稿ページから記事本文を入力するフォームをコメントアウトします。

blog_post_page.html
<!-- contentフィールドをコメントアウト -->
{% extends "base.html" %}

{% block main %}
<h1>ブログ記事投稿ページ</h1>
<form action="" method="POST">
  {% csrf_token %}
  <div class="form-item">
    {{ form.title.label }}
    {{ form.title }}
  </div>
  <!-- コメントアウトしてエラーを起こす -->
  <!-- <div class="form-item">
    {{ form.content.label }}
    {{ form.content }}
  </div> -->
  <button type="submit" class="btn btn-primary">投稿する</button>
</form>
{% endblock %}

「blog/forms.py」で定義した「content」フィールドをコメントアウトすることで、フォームエラーが発生してサブミットができなくなります。

20230330_django-form-verification-03

「投稿する」をクリックしてもブログ記事一覧ページには遷移されず、ブログ記事投稿ページに戻ってきてしまいます。

原因は、「blog/forms.py」で定義さている「content」フィールドがHTML側でコメントアウトしているため、記事本文のデータが送れずにフォームエラーが発生しているからです。

System check identified no issues (0 silenced).
March 30, 2023 - 06:08:20
Django version 4.1.7, using settings 'app.settings'
Starting development server at http://127.0.0.1:80/
Quit the server with CONTROL-C.
[30/Mar/2023 06:08:24] "POST /blog/post HTTP/1.1" 200 1064

その時のターミナルを見てもフォームエラーの原因が分かりません。

フォームエラーを表示するコードを追加

ここでフォームエラーを表示するコードが活躍します。

<!-- フォームエラーを表示するコード -->
{% for error in form.errors %}
{{ error }}
{% endfor %}

上記のコードを「blog_post_page.html」に追加することで、問題のフィールドを表示してくれます。

blog_post_page.html
{% extends "base.html" %}

{% block main %}
<h1>ブログ記事投稿ページ</h1>
<form action="" method="POST">
  {% csrf_token %}
  <div class="form-item">
    {{ form.title.label }}
    {{ form.title }}
  </div>
  <!-- <div class="form-item">
    {{ form.content.label }}
    {{ form.content }}
  </div> -->
  <button type="submit" class="btn btn-primary">投稿する</button>
</form>
<!-- フォームエラーを表示するコードを追加 -->
{% for error in form.errors %}
{{ error }}
{% endfor %}
{% endblock %}

「blog_post_page.html」の最後にフォームエラーを表示するコードを追加

20230330_django-form-verification-04

先ほどと同じように「投稿する」をクリックすると、上記のように問題のフィールドが表示されます!
問題のフィールドが表示されることで、エラーの原因が特定しやすくなります。

blog_post_page.html
{% extends "base.html" %}

{% block main %}
<h1>ブログ記事投稿ページ</h1>
<form action="" method="POST">
  {% csrf_token %}
  <div class="form-item">
    {{ form.title.label }}
    {{ form.title }}
  </div>
  <!-- コメントアウトを外す -->
  <div class="form-item">
    {{ form.content.label }}
    {{ form.content }}
  </div>
  <button type="submit" class="btn btn-primary">投稿する</button>
</form>
{% endblock %}

表示された問題のフィールド「content」を中心にエラーの原因を探ります。
今回は、contentフィールドのコメントアウトを外す事で、エラーが解消して投稿が出来るようになります。

よくある原因

フォームエラーのよくある原因としては、下記の2点が特に多いです。

  • フィールド名の打ち間違い
  • forms.pyで定義したフィールド名がHTML側に書かれていない

特定した問題のフィールド名をよく確認してみてください。

最後に

今回は、Djangoのフォームエラーでサブミットができない時に問題のフィールドを簡単に発見する方法を解説しました。
フォームのフィールド数が増えてくると打ち間違いが起きやすく、原因特定が難しくなるので、そんな時に活用してみてください!
フォームエラーの原因解決が速くなりますよ!


\ よかったらシェアしてね /

関連記事