自動化無しに生活無し

WEBとかAIとかLinux関係をひたすら書く備忘録系ブログ

DjangoでYou are Trying to add a non-nullable fieldと表示されたときの対策【makemigrations】

thumbnail

Djangoのモデルにフィールドを追加して、さあマイグレーションしようとすると、こんな表示がされることがある。

NULL禁止フィールド追加が原因のエラー

これはなんなのか、対策も兼ねて解説する。

この警告文の解説

要するに、既にレコードが存在する状態で、NULL禁止かつデフォルト値指定なしのフィールドを追加するとこうなる。

NULL禁止かつデフォルト無しだと既存レコードの扱いはどうするか聞かれる

デフォルト指定していないので、既存のレコードにはNULL禁止であるにも関わらず、NULLが入ってしまう。そこで既存のレコードはどうするか聞いている。与えられた選択肢は2つ。

1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
    1. 1度限りのデフォルト値を入れる
    1. 一旦makemigrationsを中止して、models.pyに追加したフィールドにフィールドオプションのdefaultを指定する

どういう時にこの警告文が出るのか。

下記条件を全て満たすとこの警告文が出てくる

  • そのアプリにおける、2回目以降のmakemigrationsコマンド実行である
  • 既存のモデルに対して、null禁止かつdefault無しのフィールドを追加した

つまり、フィールドを追加した時、defaultが無いので、null禁止であるにもかかわらず、どうしてもnullになってしまう。

この矛盾をどうするべきか聞いているのがこの警告文。

対策

【対策1】警告文に応じる(1度限りのdefaultを指定する)

エラーが起こる前のmodels.pyがこんな感じだったとする。

from django.db import models

class Topic(models.Model):

    comment = models.CharField(verbose_name="コメント",max_length=2000)

    def __str__(self):
        return self.comment

DBにデータが格納されている状態で、モデルフィールドを追加する。NULL禁止、デフォルト値指定なしの投稿日時を指定するフィールドである。

from django.db import models

class Topic(models.Model):

    comment = models.CharField(verbose_name="コメント",max_length=2000)
    dt      = models.DateTimeField(verbose_name="投稿日時")

    def __str__(self):
        return self.comment

ここでmakemigrationsすると先の文言が端末に出力される。

You are trying to add a non-nullable field 'dt' to topic without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option:

今回は日時型なので1を選択する。続いて、シェルにtimezone.now()と入力する。

現在の日時を指定してマイグレーション

これでマイグレーションファイルが作られた。後はマイグレーションするだけ。既存のデータにはマイグレーション時の日時が一時的に追加される。

現在の日時が指定された

【対策2】フィールドオプションのdefaultを追加する

一方で、常にdefault値を指定する場合は、2を選んで終了する。

その後、models.pyの新しく追加したdtにフィールドオプションのdefaultを指定する。DatetimeFieldの場合、下記のようにdefaultを指定する。

from django.db import models
from django.utils import timezone

class Topic(models.Model):

    comment = models.CharField(verbose_name="コメント",max_length=2000)
    dt      = models.DateTimeField(verbose_name="投稿日時",default=timezone.now)

    def __str__(self):
        return self.comment

実行するときに値が変わる、メソッドのtimezone.now()ではなく、その関数そのものを意味する属性値のtimezone.nowを指定する。timezonedjango.utilsの中に含まれているので、冒頭でインポートさせる。

結論

Djangoのモデルフィールドでは何も指定していないとNULL禁止になるので、安易にフィールドを追加して機能拡張しようと考えているとハマる。

とは言え、プロジェクト内のdb.sqlite3を削除すれば普通にマイグレーションできる問題でもあるので対処法を知らなくても問題はない(既存データは消えるが)。

モデルのフィールド追加・削除に関しての詳細は下記を確認。

【Django】models.pyにフィールドを追加・削除する【マイグレーションできないときの原因と対策も】

スポンサーリンク

シェアボタン

Twitter LINEで送る Facebook はてなブログ