概要
Djangoモデルのフィールドにはnull
とblank
のオプションがある。null
はデータベースに関するオプションであり、blank
はアプリケーションのバリデーションに関するオプションである。
オプションそれぞれの場合について、どのような動作をするのか確認する。また、Djano REST Framework(DRF)の場合、APIを実行するとどのようになるのかも確認する。
要約
- 文字列のフィールド場合(
CharField
,TextField
)、空文字可能にするためには原則blank=True
のみでいい。 - それ以外のフィールドの場合(e.g.
BooleanField
,DateTimeField
)、空データを可能にするためにはblank=True
とnull=True
が必要となる。 - フォームでのバリデーションは不要だが、デフォルトが決まっている場合、
models.BooleanField(blank=True, default=False)
とすればよい。 blank=False
かつnull=True
はほぼ使われない。- DRFにおいて
blank
またはnull
の少なくとも1つがTrue
である場合、APIのリクエストボディはバリデーションされない。
前提知識
nullとblankについて
Djangoのモデルのフィールドについて、 null
とblank
のオプションがあります。これらはどちらも空白に関するものです。しかしそれぞれに役割が異なります。null
はデータベースに関するものであり、たいしてblank
はフォームのバリデーションに関するものです。
デフォルトでは両方ともFalse
ですが、True
にした場合次のようになります。
null=True
とする場合、データベースにNULL
が格納されることが可能となります。blank=True
とする場合、フォームにそのフィールドが設定されていなくてもよくなります。
そこで次の4通りをそれぞれ考えてみます。
1. null=Falseかつblank=False(デフォルト)の場合
null=False
かつblank=False
の場合、これはデフォルトの場合です。DateField()
のように特に何も設定していなければこの場合になります。
この意味はこのフィールドのデータはNULL
は設定不可であり、かつこのフィールドはフォームのとき必須項目であるということです。
2. null=Trueかつblank=Falseの場合
この意味はこのフィールドのデータはNULL
は設定可能であり、かつこのフィールドはフォームのとき必須項目であるということです。
しかしこれは正直あまり意味がありません。というのもフォームでバリデーションが動いているため、そもそもデータベースにNULL
を設定できないからです。
「OK。データベースはNULL許容か。でも、どうやってフォームからNULLを設定するんだ? だって、そのフィールドは必須項目であるが、NULLなんか設定できないぞ?」
3. null=Falseかつblank=Trueの場合
この意味はこのフィールドのデータはNULL
は設定不可であり、かつこのフィールドはフォームのとき任意である(必須項目でない)ということです。
これはしばしば別のオプションdefault
と併用されると思います。例えばあるフィールドがBooleanField
であり、特にフォームではバリデーションをおこなわなず、何も設定されていなければデフォルトとしてFalse
を設定するということができます。
4. null=Trueかつblank=Trueの場合
この意味はこのフィールドのデータはNULL
は設定可能であり、かつこのフィールドはフォームのとき任意である(必須項目でない)ということです。
基本的な考えはこれで問題ありませんが、文字型フィールド(CharField
かつTextField
)については問題点がありますので、別途考えなければなりません。
文字型フィールドの問題点
文字型フィールドはCharField
とTextField
があります。もしこの文字型フィールドにnull=True
を入れると、このフィールドはNULL許容となります。するとNO DATAを表すために2種類のデータが存在することとなります。つまりNULL
と空文字(' '
)です。これは冗長です。そのためDjangoでは慣習として空文字が採用されています。文字型フィールドで空文字可能にするためにはblank=True
とすれば可能となります。そのフィールドが何も記入されていなければ(かつそのときに限り)、自動的に空文字(' '
)がデータベースに格納されます。1
次に上記でまとめた内容を例で示します。ソースコードはこちらにあります。2
例
ディレクトリ構成
バックエンド側(Django)のディレクトリ構成は下記の通りです。
. ├── README.md ├── backend │ ├── backend │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── books │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── fixture │ │ │ │ ├── master.json │ │ │ │ └── user.json │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_author_book_bookid_review_category.py │ │ │ │ ├── __init__.py │ │ │ ├── models │ │ │ │ ├── book.py │ │ │ │ ├── category.py │ │ │ │ └── master.py │ │ │ ├── serializer.py │ │ │ ├── tests.py │ │ │ ├── urls.py │ │ │ └── views.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── db.sqlite3 │ ├── manage.py │ └── requirements.txt └── env
ソースコード
以下、関係するソースコードを記載します。ただし必要なインポートなどを適宜省略しています。
モデル
backend/backend/books/models/book.py
class Book(models.Model): """Book model""" title = models.CharField(max_length=100) author = models.CharField(max_length=50, null=True) isbn = models.CharField(max_length=17, blank=True) asin = models.CharField(max_length=10, blank=True, null=True) published_at = models.DateField() purchase_date = models.DateField(blank=True, null=True) finished_reading = models.BooleanField(blank=True, default=False) review = models.TextField(blank=True)
それぞれのフィールドの意味は(実験の意味も込めて)次の通りです。
title
: 本のタイトル。null=False
かつblank=False
: タイトルが空白はあり得ない。author
: 著者。null=True
: 実験的に追加。isbn
: ISBN(本を識別するコードのこと)。blank=True
: ISBNがわからない場合もあるだろうし、ISBNに紐づけられていない刊行物(雑誌)もあるので、空文字可能にする。asin
: ASIN(Amazonの品物コードのこと)。null=True
かつblank=True
: 実験的に追加。published_at
: 出版日。null=False
かつblank=False
: 出版日はわかるということで。purchase_date
: 本の購入日。null=True
かつblank=True
: まだ買っていない本も登録したいのでNULL許容にする。finished_reading
: 了読フラグ。blank=True
かつdefault=False
: フォームではバリデーションしないが、データベースにはNULLを入れたくないので、フォームに何も設定されていなければ、デフォルトではFalse
が入るようにする。review
: 本の感想。blank=True
: 本の感想をまだ書けない場合があるので、から文字可能にする。ただし、blank=True
のみだと次のようなパターンを判別できない。- まだ読んでいないため、レビューを書いていない場合
- 読んでレビューを書いたけれども、途中までの文章を全部消した場合
- したがってこの場合では
null=True
も使えるかも。
- したがってこの場合では
シリアライザー
backend/backend/books/serializer.py
class BookSerializer(serializers.ModelSerializer): """Book serializer""" class Meta: """Meta""" model = Book fields = '__all__'
ビュー
backend/backend/books/views.py
class BookCreate(APIView): """Book view""" def post(self, request, format=None): """Create a new book""" serializer = BookSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
URL
backend/backend/urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('backend.books.urls')), ]
backend/backend/books/urls.py
urlpatterns = [
path('books/', BookCreate.as_view()),
管理者画面
backend/backend/books/admin.py
class BookAdmin(admin.ModelAdmin): """Display Book model""" model = Book list_display = ('id', 'title', 'isbn', 'asin') admin.site.register(Book, BookAdmin)
実行結果
次の実行結果はそれぞれ管理者画面でのものとAPIでのものです。
管理者画面
各フィールドのオプションとバリデーションの関係をまとめると次のようになります。
null | blank | validation | |
---|---|---|---|
title | F | F | T |
author | T | F | T |
isbn | F | T | F |
asin | T | T | F |
published_at | F | F | T |
purchase_date | T | T | F |
finished_reading | F | T | F |
review | F | T | T |
isbn
とasin
には何も設定されておりませんが、一方は何も表示がなく、他方ではハイフン(-
)があります。これはそれぞれ空文字とNULLを表しています。
API
APIのリクエストボディに対しては、バリデーションはnull
またはblank
の少なくとも1つがTrue
である場合、実行されません。別の言い方をすればバリデーションがおこなわれるのはnull=False
かつblank=False
のデフォルトのみということです。
この例ではバリデーションはtitle
とpublished_at
のみがされて、管理者画面ではバリデートされていたauhtor
が判定されていません。
null | blank | validation | |
---|---|---|---|
title | F | F | T |
author | T | F | F |
isbn | F | T | F |
asin | T | T | F |
published_at | F | F | T |
purchase_date | T | T | F |
finished_reading | F | T | F |
review | F | T | T |
管理者画面の場合と同様にnull=True
と設定したフィールドでは、リクエストボディに何も設定していなければ、NULLが設定されています。
まとめ
文字型フィールドで空文字可能にする場合、原則
blank=True
のみでいい。理由はもしnull=True
を設定すると、NO DATAを表すデータが2種類になるため。それ以外のフィールドで空(カラ)を許容する場合、基本的には
null=True
かつblank=True
にすること。理由はnull=True
のみだとフォームでNULLを設定できないから。ただしDRFではレスポンスボディに何も設定せずにAPIを実行するとNULLが設定される。しかしAPIではなく管理者画面でデータを操作したい場合もあるので、そのときだけNULL設定できないというのは不便であるので原則null=True
のみは使用しない。
null
とblank
の違いがようやく少しわかった気がします。
参考文献
僕から以上