DjangoでYou are Trying to add a non-nullable fieldと表示されたときの対策【makemigrations】
Djangoのモデルにフィールドを追加して、さあマイグレーションしようとすると、こんな表示がされることがある。
これはなんなのか、対策も兼ねて解説する。
この警告文の解説
要するに、既にレコードが存在する状態で、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度限りのデフォルト値を入れる
-
- 一旦
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
を指定する。timezone
はdjango.utils
の中に含まれているので、冒頭でインポートさせる。
結論
Djangoのモデルフィールドでは何も指定していないとNULL禁止になるので、安易にフィールドを追加して機能拡張しようと考えているとハマる。
とは言え、プロジェクト内のdb.sqlite3
を削除すれば普通にマイグレーションできる問題でもあるので対処法を知らなくても問題はない(既存データは消えるが)。
モデルのフィールド追加・削除に関しての詳細は下記を確認。