DjangoにてDBのNOT NULL 制約を外したい場合、nullとblankのどちらを使えばよいのか忘れることがあるため、メモしておきます。
目次
環境
違い
stackoverflowにありました。
python - differentiate null=True, blank=True in django - Stack Overflow
公式ドキュメントの場合はこのあたり。
https://docs.djangoproject.com/en/2.0/ref/models/fields/#null
null
オプションは、NOT NULL 制約を外す
- 基本、何も値を与えなければNULLをセット
- ただし、CharField型などの文字型については、NULLではなく空文字をセット
- このため、
blank=True
かつ unique=True
の場合は、 null=True
にしないと空文字が複数回登場することになり、エラーとなる
- BooleanFieldでNULLを扱いたい場合は、
NullBooleanField
にする
blank
オプションは、保存時に何もデータを渡さなくても、バリデーションでOKとする (値の任意入力化)
でした。
確認
stackoverflowではPostgreSQLとMySQLで試していたので、今回はSQLiteで試してみます。
Model
今回は
- CharField
- IntegerField
- DateTimeField
- BooleanField
- NullBooleanField
なModelを用意します。
class NullBlank(models.Model):
charNull = models.CharField(max_length=10, null=True)
charBlank = models.CharField(max_length=10, blank=True)
charNullBlank = models.CharField(max_length=10, null=True, blank=True)
charBlankUnique = models.CharField(
max_length=10, blank=True, unique=True,
)
charNullBlankUnique = models.CharField(
max_length=10, null=True, blank=True, unique=True,
)
intNull = models.IntegerField(null=True)
intBlank = models.IntegerField(blank=True)
intNullBlank = models.IntegerField(null=True, blank=True)
dateNull = models.DateTimeField(null=True)
dateBlank = models.DateTimeField(blank=True)
dateNullBlank = models.DateTimeField(null=True, blank=True)
boolNull = models.BooleanField(null=True)
boolNullBlank = models.BooleanField(null=True, blank=True)
boolBlank = models.BooleanField(blank=True)
nullboolNull = models.NullBooleanField(null=True)
nullboolBlank = models.NullBooleanField(blank=True)
nullboolNullBlank = models.NullBooleanField(null=True, blank=True)
BooleanFieldがNULLを許可しないため、マイグレーションファイル作成時にエラーとなりました。
$ python manage.py makemigrations nullblank
nullblank.NullBlank.boolNull: (fields.E110) BooleanFields do not accept null values.
HINT: Use a NullBooleanField instead.
nullblank.NullBlank.boolNullBlank: (fields.E110) BooleanFields do not accept null values.
HINT: Use a NullBooleanField instead.
それらのFieldをコメントアウトして、再度実行すると、マイグレーションファイルができました。
$ python manage.py makemigrations nullblank
Migrations for 'nullblank':
nullblank/migrations/0001_initial.py
- Create model NullBlank
sqlmigrateで、発行されるSQLを確認
CREATE TABLEのところを整形して見やすくしてみます。
$ python manage.py sqlmigrate nullblank 0001
BEGIN;
--
-- Create model NullBlank
--
CREATE TABLE "nullblank_nullblank" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"charNull" varchar(10) NULL,
"charBlank" varchar(10) NOT NULL,
"charNullBlank" varchar(10) NULL,
"charBlankUnique" varchar(10) NOT NULL UNIQUE,
"charNullBlankUnique" varchar(10) NULL UNIQUE,
"intNull" integer NULL,
"intBlank" integer NOT NULL,
"intNullBlank" integer NULL,
"dateNull" datetime NULL,
"dateBlank" datetime NOT NULL,
"dateNullBlank" datetime NULL,
"boolBlank" bool NOT NULL,
"nullboolNull" bool NULL,
"nullboolBlank" bool NULL,
"nullboolNullBlank" bool NULL
);
COMMIT;
いい感じです。
$ python manage.py migrate nullblank
Operations to perform:
Apply all migrations: nullblank
Running migrations:
Applying nullblank.0001_initial... OK
データの投入
実際にデータを投入してみます。
以下のようなDjangoコマンドを用意します。
from django.core.management import BaseCommand
from django.utils import timezone
from nullblank.models import NullBlank
class Command(BaseCommand):
def handle(self, *args, **options):
NullBlank().save()
投入してみます。
$ python manage.py null_blank
sqlite3.IntegrityError:
NOT NULL constraint failed: nullblank_nullblank.intBlank
NOT NULL constraint failed: nullblank_nullblank.dateBlank
NOT NULL constraint failed: nullblank_nullblank.boolBlank
blank=True
なカラムがエラーとなりました。
そのため、
NullBlank(
intBlank=0,
dateBlank=timezone.now(),
boolBlank=True,
).save()
とすると、登録できました。
しかし、もう一度実行すると、
$ python manage.py null_blank
sqlite3.IntegrityError:
UNIQUE constraint failed: nullblank_nullblank.charBlankUnique
と出ました。
UNIQUE制約のある列で、空文字が重複したようです。
そのため、最終的には以下となります。
NullBlank(
intBlank=0,
dateBlank=timezone.now(),
boolBlank=True,
charBlankUnique=timezone.now().strftime('%Y/%m/%d %H:%M:%S')
).save()
SQLiteのCLIを使い、実際のデータベースの中身を見てみます。
https://www.sqlite.org/cli.html
手元のMacではsqlite3コマンドがインストール済なので、それを利用します。
# SQLite3ファイルがある場所へと移動
$ ls -a db.sqlite3
db.sqlite3
# データベースファイルに対し、CLIを起動
$ sqlite3 db.sqlite3
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
# ヘッダを付けて表示
sqlite> .header on
# カラムモードで表示
sqlite> .mode column
# NULLの場合は、"NULL" という文字を出力
# こうしないと、空文字とNULLの違いが分からないため
sqlite> .nullvalue NULL
# CharField関係
sqlite> select id, charNull, charBlank, charNullBlank, charBlankUnique, charNullBlankUnique from nullblank_nullblank;
id charNull charBlank charNullBlank charBlankUnique charNullBlankUnique
---------- ---------- ---------- ------------- --------------- -------------------
1 NULL NULL NULL
2 NULL NULL 2018/06/20 08:4 NULL
# IntegerField関係
sqlite> select intNull, intBlank, intNullBlank from nullblank_nullblank;
intNull intBlank intNullBlank
---------- ---------- ------------
NULL 0 NULL
NULL 0 NULL
# DatetimeField関係
sqlite> select dateNull, dateBlank, dateNullBlank from nullblank_nullblank;
dateNull dateBlank dateNullBlank
---------- -------------------------- -------------
NULL 2018-06-20 08:39:24.738832 NULL
NULL 2018-06-20 08:48:50.256008 NULL
# BooleanField関係
sqlite> select boolBlank, nullboolNull, nullboolBlank, nullboolNullBlank from nullblank_nullblank;
boolBlank nullboolNull nullboolBlank nullboolNullBlank
---------- ------------ ------------- -----------------
1 NULL NULL NULL
1 NULL NULL NULL
GitHubに上げました。 nullblank
アプリケーション以下が、今回のファイルになります。
https://github.com/thinkAmi-sandbox/Django20-sample