ご覧いただきありがとうございます!
領護(りょうご)です。
フォームのサブミットボタンを押しても反応なし...みたいなことないですか?
今回は、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」モデルを作成します。
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」フォームを作成します。
from django import forms
from .models import Article
# ブログ記事投稿フォーム
class ArticleForm(forms.ModelForm):
# フォーム表示設定
class Meta:
# 使用するモデルを指定
model = Article
# 使用するフィールドを指定
fields = ("title", "content")
記事タイトルと記事本文を入力するフィールドを指定
ビューの作成
投稿した記事を一覧表示する「ブログ記事一覧ページ」とブログ記事を投稿する「ブログ記事投稿ページ」の2ページを作成します。
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を作成します。
<!-- ブログ記事一覧ページ用 -->
{% 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」を作成
<!-- ブログ記事投稿ページ用 -->
{% 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を設定します。
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を設定
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」が読み込めるように追加
動作チェック
ブログ記事投稿ページから記事タイトルと記事本文を入れて「投稿する」をクリックします。
ブログ記事一覧ページに投稿内容が表示されます。
エラーを起こしてみる
ブログ記事投稿ページから記事本文を入力するフォームをコメントアウトします。
<!-- 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」フィールドをコメントアウトすることで、フォームエラーが発生してサブミットができなくなります。
「投稿する」をクリックしてもブログ記事一覧ページには遷移されず、ブログ記事投稿ページに戻ってきてしまいます。
原因は、「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」に追加することで、問題のフィールドを表示してくれます。
{% 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」の最後にフォームエラーを表示するコードを追加
先ほどと同じように「投稿する」をクリックすると、上記のように問題のフィールドが表示されます!
問題のフィールドが表示されることで、エラーの原因が特定しやすくなります。
{% 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のフォームエラーでサブミットができない時に問題のフィールドを簡単に発見する方法を解説しました。
フォームのフィールド数が増えてくると打ち間違いが起きやすく、原因特定が難しくなるので、そんな時に活用してみてください!
フォームエラーの原因解決が速くなりますよ!