疑念は探究の動機であり、探究の唯一の目的は信念の確定である。

数学・論理学・哲学・語学のことを書きたいと思います。どんなことでも何かコメントいただけるとうれしいです。特に、勉学のことで間違いなどあったらご指摘いただけると幸いです。 よろしくお願いします。くりぃむのラジオを聴くこととパワポケ2と日向坂46が人生の唯一の楽しみです。

Django モデルのフィールドのnullとblankについて

概要

Djangoモデルのフィールドにはnullblankのオプションがある。nullはデータベースに関するオプションであり、blankはアプリケーションのバリデーションに関するオプションである。

オプションそれぞれの場合について、どのような動作をするのか確認する。また、Djano REST Framework(DRF)の場合、APIを実行するとどのようになるのかも確認する。

要約

  • 文字列のフィールド場合(CharField, TextField)、空文字可能にするためには原則blank=Trueのみでいい。
  • それ以外のフィールドの場合(e.g.BooleanField, DateTimeField)、空データを可能にするためにはblank=Truenull=Trueが必要となる。
  • フォームでのバリデーションは不要だが、デフォルトが決まっている場合、models.BooleanField(blank=True, default=False)とすればよい。
  • blank=Falseかつnull=Trueはほぼ使われない。
  • DRFにおいてblankまたはnullの少なくとも1つがTrueである場合、APIのリクエストボディはバリデーションされない。

前提知識

  • 必須: Pythonの使い方を知っている。
  • 必須: Djangoの使い方を知っている。
  • 任意: Django REST Frameworkを知っている。

nullとblankについて

Djangoのモデルのフィールドについて、 nullblankのオプションがあります。これらはどちらも空白に関するものです。しかしそれぞれに役割が異なります。nullはデータベースに関するものであり、たいしてblankはフォームのバリデーションに関するものです。

デフォルトでは両方ともFalseですが、Trueにした場合次のようになります。

  1. null=Trueとする場合、データベースにNULLが格納されることが可能となります。
  2. 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)については問題点がありますので、別途考えなければなりません。

文字型フィールドの問題点

文字型フィールドはCharFieldTextFieldがあります。もしこの文字型フィールドに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

isbnasinには何も設定されておりませんが、一方は何も表示がなく、他方ではハイフン(-)があります。これはそれぞれ空文字とNULLを表しています。

API

APIのリクエストボディに対しては、バリデーションはnullまたはblankの少なくとも1つがTrueである場合、実行されません。別の言い方をすればバリデーションがおこなわれるのはnull=Falseかつblank=Falseのデフォルトのみということです。

この例ではバリデーションはtitlepublished_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のみは使用しない。





nullblankの違いがようやく少しわかった気がします。



参考文献

僕から以上


  1. 原則的には文字型フィールドの場合blank=Trueで問題ありませんが、唯一uniqueを使う場合は例外となります。ですが、私はそのあたりはよくわかりません。
  2. しかし環境構築の方法などはまだまとまっておりませんので、実際に動かして試すことは難しいかもしれません。また今回では不要なソースがたくさんありますので、ご了承ください。