【Django】 .db.utils.IntegrityError: (1364, "Field...)の内容・解決・注意事項


投稿日 2020年8月13日 >> 更新日 2023年3月2日

今回は、「django.db.utils.IntegrityError: (1364, "Field 'フィールド名' doesn't have a default value")」のエラーについての説明していきたいと思います。

私の場合は、ブログ記事などを投稿しようとした時に上記のエラーに遭遇しました。

しかも、開発環境では起こらなかったのに本番環境でこのようなエラーが起こることで、最初は検討がつかなかったのですが、冷静にデプロイした際の事を思い返すと問題が1つあったので、そのような事も含めエラーとなった理由から、解決・注意事項までをご紹介していこうと思います。

そしてデータベースの操作では、SQLite・MySQLでの異なる処理を実装していくので、参考にして頂けたら幸いです。

実行環境&使用パッケージ

実行環境(開発)
Window Subsystem for Linux
Python 3.6
pip 9.0.1
実行環境(本番)
CentOS7
Python3.6
使用パッケージ ライセンス
Django==2.2.5 BSD

内容・解決方法

この「django.db.utils.IntegrityError: (1364, "Field 'フィールド名' doesn't have a default value"」をGoogle翻訳に充てると、「フィールドの’フィールド名’にはデフォルト値がありません」となります。

’フィールド名’の部分に入るところが問題の部分です。

データベース(SQLiteやMySQLなど)内のフィールドと、Djangoで設定されているモデルのフィールドが一致していないという事になります。

私の例で見てみますと、ブログの投稿時に「django.db.utils.IntegrityError: (1364, "Field 'views' doesn't have a default value"」のようなエラーが起こったとします。

「フィールド’views’にはデフォルト値がありません」ということなので、ブログアプリのモデルで定義されているフィールドを確認してみることにします。

# models.py

from django.db import models


class Blog(models.Model):
    title = ...
    text = ...
    created_at = ...

    ....

3つのフィールドがDjangoDBのデフォルト値として設定されています。

「views」というフィールドは過去に設定していましたが、削除したはずです。

ブログアプリ内にあるマイグレーションファイルを確認したら、初期化するために移行ファイルを削除していました(諸事情で)。

なのでmigrateコマンドを打っても「No migrations to apply」となってしまいます。

では実際に保存されているデータベースのBlogテーブルではどのようなフィールドとなっているか見てみます。

SQLite3の場合

# 実行

$ python3 manage.py dbshell
# テーブルの確認
sqlite> .table
auth_group                    blog_blog
auth_group_permissions        django_admin_log
auth_permission               django_content_type
auth_user                     django_migrations
auth_user_groups              django_session
auth_user_user_permissions    
sqlite>
# フィールド(カラム)の確認
sqlite> .schema blog_blog
CREATE TABLE IF NOT EXISTS "blog_blog" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(50) NOT NULL, "text" text NOT NULL, "created_at" date NOT NULL, "views" integer NOT NULL);

MySQLの場合

# 実行

$ python3 manage.py dbshell
# テーブルの確認
mysql> show tables;
+----------------------------+
| Tables_in_DB名              |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| blog_blog                  |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
mysql>
# フィールド(カラム)の確認
mysql> show columns from blog_blog;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| title       | varchar(150) | NO   |     | NULL    |                |
| text        | longtext     | NO   |     | NULL    |                |
| created_at  | date         | NO   |     | NULL    |                |
| views       | int()        | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

データベース内には削除したと思われる「views」フィールド(カラム)がまだ存在していました。

やはりマイグレーションファイルをやたら無暗に削除してしまうと、このようにデータベースではしっかり変更が行われないことが分かります。

Djangoで設定したBlogモデル内と、データベースのテーブル内を一致させるために、データベースの「views」フィールド(カラム)を削除していきたいと思います。

なお、MySQLでは簡単にカラムの削除を行えますが、SQLiteではひと手間かかるので、先にMySQLでの処理から行っていきます。

MySQL文でフィールド(カラム)の削除

ではdbshellコマンドでデータベースにアクセスします。

# 実行

$ python3 manage.py dbshell
# フィールドの確認
mysql> show columns from blog_blog;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| title       | varchar(150) | NO   |     | NULL    |                |
| text        | longtext     | NO   |     | NULL    |                |
| created_at  | date         | NO   |     | NULL    |                |
| views       | int()        | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

中身が確認できたら、問題のblog_blogテーブル内にあるviewsを削除します。

# 問題の根幹を削除
mysql> alter table blogs_blog drop views;
......(アラート)
mysql>
# もう1度問題のテーブル内を確認
mysql> show columns from blog_blog;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| title       | varchar(150) | NO   |     | NULL    |                |
| text        | longtext     | NO   |     | NULL    |                |
| created_at  | date         | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+
# 削除されていたら終了
mysql> exit

これで問題は解決されました。

SQLite文でフィールド(カラム)の削除

SQLite文では、テーブルは削除できてもフィールド(カラム)の削除には対応していないとのことで、ひと手間加えた処理を行います。

流れを簡単に説明すると、新しくテーブルを作成し、viewsフィールドを外したそれ以外のフィールドを定義します。そして既存テーブルから新テーブルへとデータを追加し確認して終了です。

今回例に挙げているブログアプリにおいて、Djangoではデータベースと紐づける際の名前空間が「blog_blog」と定まっているため、新しく作成するテーブル名は「blog_blog」とする必要があります。

ではさっそく実装していきましょう。

# 実行

$ python3 manage.py dbshell
# テーブル内を確認
sqlite>
sqlite> .schema blog_blog
CREATE TABLE IF NOT EXISTS "blog_blog" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(50) NOT NULL, "text" text NOT NULL, "created_at" date NOT NULL, "views" integer NOT NULL);

問題のviewsフィールド(カラム)があることを確認しました。

新しく作成するテーブル名は「blog_blog」である必要があるので、既存の「blog_blog」テーブルの名前を変更します。

# 既存のテーブル名を変更
sqlite> alter table blog_blog rename to blog_remove;
sqlite>
# 確認
sqlite> .tables
auth_group                    blog_remove
auth_group_permissions        django_admin_log
auth_permission               django_content_type
auth_user                     django_migrations
auth_user_groups              django_session
auth_user_user_permissions

削除用のテーブル名として変更しました。

これで「blog_blog」というDjangoが名前空間を持つテーブル名を使えるようになったので、新しくテーブルを作成していきます。

viewsフィールド(カラム)以外のフィールドも記述していくので、schemaで「blog_remove」のフィールドを見ながら記述していきます。

# フィールドの確認
sqlite> .schema blog_remove
CREATE TABLE IF NOT EXISTS "blog_remove" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(50) NOT NULL, "text" text NOT NULL, "created_at" date NOT NULL, "views" integer NOT NULL);
sqlite>
# 新しくテーブルの作成
sqlite> create table blog_blog("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(50) NOT NULL, "text" text NOT NULL, "created_at" date NOT NULL);

では確認してみます。

# テーブルの確認
sqlite> .tables
auth_group                    blog_remove
auth_group_permissions        django_admin_log
auth_permission               django_content_type
auth_user                     django_migrations
auth_user_groups              django_session
auth_user_user_permissions
blog_blog
sqlite>
# フィールドの確認
sqlite> .schema blog_blog
CREATE TABLE blog_blog("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(50) NOT NULL, "text" text NOT NULL, "created_at" date NOT NULL);

問題のviewsフィールドが抜けた以外全く等しいフィールド内が作成されています。

最後に、元のテーブル(blog_remove)内にあるデータを、新しく作成した「blog_blog」テーブルに追加していきます。

# 既存のデータを追加
sqlite> insert into blog_blog("id", "title", "text", "created_at") select id, title, text, created_at from blog_remove;
sqlite>

しっかりとデータが追加されているか確認します。

sqlite> select id, title from blog_blog;
1|〇〇×△×△
2|......
3|......
sqlite>
# コンソール終了
sqlite> .exit

データが追加されていると確認できたら、元のテーブルは削除して大丈夫ですが、不安であれば動作確認も行った後に削除するのが良いと思います。

余分にテーブルが存在しますが、Djangoはしっかり動きます。

データベース関連のエラーが出たら、もう一度記述漏れやミスが無いか確認してみてください。

全ての確認が終わったらテーブルを削除します。

# 削除
sqlite> drop table blog_remove;
sqlite>
# テーブルの確認
sqlite> .tables
auth_group                    blog_blog
auth_group_permissions        django_admin_log
auth_permission               django_content_type
auth_user                     django_migrations
auth_user_groups              django_session
auth_user_user_permissions
sqlite>
sqlite> .exit

失敗しない為の注意事項

今回このようなエラーとなってしまった理由は、マイグレーションファイルを闇雲に削除してしまったことです。

初心者にありがちなミスだと思いますが、Djangoはマイグレーションファイルを元にデータベーススキーマを構築していくので、途中経過が抜けていると何らかのエラーとなりますし、失敗してマイグレーションファイルを初期化してもどこかでデータベース側とのミスマッチが起きてしまいます(今回のように)。

特にGitHub経由でのデプロイでは、マイグレーションファイルを除外するなどの処置を取らないと、本番環境下に移行済み(開発環境でのマイグレート)ファイルを結合してきてしまうため、うっかり本番環境下でマイグレートを実行してしまってミスが起こります。

そして焦りから、サーバーエラーから逃れたいがための裏ワザ(初期化)の結果でした。

しかしこの経験のおかげでDjangoでは中々触らないSQL文に少しは慣れる事ができたので、結果オーライでした。

それでは以上となります。

最後までご覧いただきありがとうございました。