DjangoのModelのFieldのオプション null と blank の違いについて

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ではPostgreSQLMySQLで試していたので、今回は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コマンドを用意します。

# management/commands/null_blank.py

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()

 

SQLiteCLIでデータを確認

SQLiteCLIを使い、実際のデータベースの中身を見てみます。
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