on_deleteはデータベース制約を作成しない

DjangoのModel定義のon_deleteについて改めて分かったことを簡単にまとめます。

以下のような要点を改めて知る機会がありました。

  • マイグレーション実行の有無は関係ない
  • Modelに記述したかどうかで処理が決まる
  • データベースにはon_deleteの内容は反映されない

どういうこと?

まず、on_deleteについてリファレンスに以下の説明があります。

When an object referenced by a ForeignKey is deleted, Django will emulate the behavior of the SQL constraint specified by the on_delete argument. For example, if you have a nullable ForeignKey and you want it to be set null when the referenced object is deleted:

https://docs.djangoproject.com/en/5.0/ref/models/fields/#django.db.models.ForeignKey.on_delete

ここではSET_NULLの例を記述していますが、on_deleteの内容によってSQL制約の振る舞いをエミュレートする、とあります。

また、以下のような記述もあり、データベースにSQL制約を作成しないとあります。

on_delete doesn’t create an SQL constraint in the database.

https://docs.djangoproject.com/en/5.0/ref/models/fields/#django.db.models.ForeignKey.on_delete

あくまでも、データベースではなくアプリケーション側(Django側)であるという説明をしてくれていると理解しています。

こういう事があるよ

データベースに対して制約をかけていない、という点を理解していないとロールバックが必要な際に取るべき手順を誤ってしまう可能性があります。

例えば以下の条件が存在していると仮定します。

  • taskというアプリケーション
  • ForeignKeyon_delete=PROTECTを設定していたモデルが存在する

そこから、on_delete=CASCADEに変更する場合、以下の条件で実施します。

  1. on_delete=CASCADEにモデルを変更する
  2. python manage.py makemigrationsマイグレーションファイルを作成する
  3. python manage.py migrateマイグレーションを実行する

2.の手順を踏んだ際にはcascadeの変更が記述されたマイグレーションファイルが作成され、以下のようになったと仮定します。

  • 0001_initial.py
  • 0002_change_cascade.py # このファイルが生成されたイメージ

そしてロールバックを実行する際はカラム追加のようなケースであれば python manage.py task migrate 0001と実行しますが、 データベースレベルでは何も制約がないため、on_deleteの変更の場合、実行した所で何も意味がない状態になってしまいます。

つまり、今回のケースで言うとPROTECTに戻ったように見えて実際はCASCADEのまま、のような状態です。

この場合、ファイルをrevertすることで期待するロールバックを実行する事が可能になります。

実は失敗談

私が勘違いしていた内容でした。 自社の書籍かつ愛読書である自走プログラマーではデータマイグレーションの例ですが、ロールバックができることを確認しようという節がありました。

59:データマイグレーションはロールバックも実装する

今回のようなケースでも念の為、ロールバックを出来ることを確認しておこう、と思ってロールバック後に動作確認して意図する挙動にならなかったので「なぜ?」ということになった感じでした。

そして、挙動については公式リファレンスにも載っているので、ちゃんと読まないといけないことを改めて実感・反省しました。

ちなみに、自走プログラマーにはそういう節もあります。 33:公式ドキュメントを読もう

とはいえ、この記事をどこかで見た誰かがそうだったのかーと思えると良いなと思っています。

この記事を書くにあたって調べてみたところ、Modelで作成されたテーブルの内容と、実際にSQLを書いて作成したテーブルの内容では差異もあったのでそうしたところもちゃんと調べてしていけると良いな、と思っています。