Django и пустые значения

В последние недели жизнь тесно познакомила меня с самым популярным фреймворком для быстрой разработки веб-приложений на Питоне — Django. В нём много любопытного, чем я время от времени намерен делиться ツ

Допустим, у вас в приложении есть сущность «Клиент», а у клиента поле «Имя». Клиенты указывают имя при регистрации, но поскольку это не обязательно — многие предпочитают оставлять поле пустым. Как в таких случаях должно имя храниться в базе данных?

Я всегда полагал, что на этот вопрос может быть единственный ответ — использовать специальное значение NULL. Оно ведь ровно для этого и предназначено: показать, что поле не заполнено, его значение неизвестно.

NULL vs пустая строка

Создатели «Джанги», однако, пошли против вековой мудрости разработчиков, и норовят сохранять отсутствующее значение как пустую строку. За это отвечает специальная настройка (null=False). Менять её не рекомендуют — мол, путаница от этого возникнет:

If True, Django will store empty values as NULL in the database. Default is False.

Avoid using null on string-based fields because empty string values will always be stored as empty strings, not as NULL.

If a string-based field has null=True, that means it has two possible values for "no data": NULL, and the empty string. In most cases, it’s redundant to have two possible values for "no data"; the Django convention is to use the empty string, not NULL.

Этот кусок документации заслуживает особой похвалы. Так всё запутать, ничего толком не объяснив — это ещё надо суметь. Следите за руками:

  • с одной стороны, «if True, Django will store empty values as NULL»,
  • одновременно с этим «empty string values will always be stored as empty strings, not as NULL».

Так вы храните пустые значения как NULL при включённой настройке? Или всегда-всегда как пустую строку (и на кой чёрт тогда настройка)? Определитесь уже!

На самом деле, авторы имели в виду следующее:

  • Если null=False, и вы не присвоите полю значение, «Джанга» молча присвоит ему "" и сохранит в базе как пустую строку.
  • Если null=True, и вы не присвоите полю значение, «Джанга» молча присвоит ему None и сохранит в базе как NULL.
  • Вне зависимости от настройки, если вы явно присвоите полю значение "", «Джанга» сохранит его в базе как пустую строку.
  • Вне зависимости от настройки, если вы явно присвоите полю значение None, «Джанга» сохранит его в базе как NULL.

Ох.

NOT NULL на таблице

Но это полбеды. О чём авторы совсем забыли упомянуть в документации — о том, что null=False генерирует такой SQL-код:

ALTER TABLE "tablename"
ADD COLUMN "columnname" varchar (50) DEFAULT '' NOT NULL;

То есть не просто «мы будем записывать неизвестное значение в базу как пустую строку», а «мы вообще запретим этому полю иметь значение NULL».

Это очень, очень творческое дизайнерское решение. Дело в том, что если у вас таблица на 100000 строк, и вы добавляете в неё NOT NULL столбец, то это, мягко говоря, небыстро. И полностью блокирует таблицу на всё время операции.

А если приложение развивается, новые столбцы добавляют регулярно. И с NOT NULL каждый раз при миграции таблица блокируется и полностью перезаписывается. Как вообще можно было додуматься до такого подхода?

Чтобы решить проблему, достаточно игнорировать рекомендации «Джанги» и ставить текстовым полям null=True.

P.S. В PostgreSQL 11 добавили специальную оптимизацию, благодаря которой добавление NOT NULL столбца работает моментально, если у него есть значение по умолчанию. Так что в новом «Постгресе» рекомендуемые настройки «Джанги» наконец-то перестали стрелять в ногу разработчикам.