疑問 : 「on_delete=models.CASCADE」を設定したのに「RESTRICT」になる
- 環境
- Python : 3.12.11
- Django : 4.2.11
- MySQL : 8.0.32
Djangoで「on_delete=models.CASCADE」を設定した子レコードのある親レコードをデータベースで直接SQLを使って削除しようとしたらエラーになった。
「on_delete=models.CASCADE」を設定したらデータベースでも「CASCADE」になると思っていた。
class AnimalArea(models.Model):
animal_area_id = models.AutoField(primary_key=True)
animal = models.ForeignKey(Animal, on_delete=models.CASCADE, verbose_name='CASCADEで親レコードが削除されると子レコードも削除されるようにする')
area = models.ForeignKey(Area, on_delete=models.PROTECT, verbose_name='PROTECTで親レコードを削除しようとした際に、子レコードが存在すると拒否されるようにする')
しかし、実際は「on_delete=models.PROTECT」と同じ「RESTRICT」だった。
select TABLE_NAME,UPDATE_RULE,DELETE_RULE,REFERENCED_TABLE_NAME from information_schema.REFERENTIAL_CONSTRAINTS where table_name like 'animal_area';
TABLE_NAME | UPDATE_RULE | DELETE_RULE | REFERENCED_TABLE_NAME |
---|---|---|---|
animal_area | RESTRICT | RESTRICT | area |
animal_area | RESTRICT | RESTRICT | animal |
なぜだろう?
答え : Djangoはデフォルトで、「CASCADE」を削除しているため
CASCADE
カスケード削除。Django は SQL の制約 ON DELETE CASCADE の動作をエミュレートし、 ForeignKey を含むオブジェクトも削除します。
引用元 : モデルフィールドリファレンス | Django documentation | Django
ん?親レコードを削除したら子レコードを削除できるのか?
RESTRICT
RestrictedError (django.db.IntegrityError のサブクラス) を発生させて、参照オブジェクトの削除を防ぎます。 PROTECT とは異なり、同じ操作で削除されるオブジェクトが CASCADE リレーションシップで参照されている場合、参照オブジェクトの削除は許可されます。
引用元 : モデルフィールドリファレンス | Django documentation | Django
「on_delete=models.CASCADE」の親レコードを削除する流れは、以下になるので「RESTRICT」で問題ないそうだ。
- 親レコード(参照される側)の.delete()メソッドを実行
- 親レコードを参照しているすべての子レコード(参照している側)を検索
- 取得した子レコードを先に削除
- 最後に親レコードを削除
「CASCADE」を削除してもpre_delete と post_deleteは使えるから安全
Djangoでは、モデルが削除される前後に特定の処理を挟むためのpre_deleteやpost_deleteといったシグナルがあります。
モデルのシグナル
django.db.models.signals モジュールは、モデルシステムによって送信される一連のシグナルを定義しています。
(省略)
django.db.models.signals.pre_delete
モデルの delete() メソッドとクエリセットの delete() メソッドの開始時に送信されます。
(省略)
django.db.models.signals.post_delete
pre_delete のようですが、モデルの delete() メソッドとクエリセットの delete() メソッドの終わりに送信されます。
データベースレベルで「CASCADE」で削除されると子レコードの削除がORマッパーを使わずに行われてしまうため、シグナルが子オブジェクトに対して発火しない。
なので「CASCADE」を削除することで、子レコードの削除がORマッパーを使って行うようにしてシグナルが発火するようにしている。
CASCADE
(省略)
Model.delete() はリレーション先モデルでは呼び出されませんが、 pre_delete と post_delete シグナルは削除された全てのオブジェクトに対して送られます。
引用元 : モデルフィールドリファレンス | Django documentation | Django
どうしてもデータベースで「CASCADE」にしたい場合は、マイグレーションファイルをいじったりして「CASCADE」を強制設定する必要があるよう。
ただ、直接データベースで操作を行う前提でなければ強制する必要はないように思う。