I have a query concerning the lack of a ON DELETE CASCADE clause to the foreign key constraints generated for PostgreSQL (using Django 1.8.11) on the default through tables generated for ManyToManyField.
I was unable to find any related issues (”ManyToMany cascade” Search Results – Django) so I thought I’d ask before I open a ticket.
I create an app called constraints in which I define two models:
from django.db import models
class A(models.Model):
name = models.CharField(max_length=200)
class B(models.Model):
name = models.CharField(max_length=200)
a_set = models.ManyToManyField(A, db_constraint=True)
$ ./manage.py sqlall constraintsBEGIN;CREATE TABLE "constraints_a" ("id" serial NOT NULL PRIMARY KEY,"name" varchar(200) NOT NULL);CREATE TABLE "constraints_b_a_set" ("id" serial NOT NULL PRIMARY KEY,"b_id" integer NOT NULL,"a_id" integer NOT NULL REFERENCES "constraints_a" ("id") DEFERRABLE INITIALLY DEFERRED,UNIQUE ("b_id", "a_id"));CREATE TABLE "constraints_b" ("id" serial NOT NULL PRIMARY KEY,"name" varchar(200) NOT NULL);ALTER TABLE "constraints_b_a_set" ADD CONSTRAINT "b_id_refs_id_4308660a" FOREIGN KEY ("b_id") REFERENCES "constraints_b" ("id") DEFERRABLE INITIALLY DEFERRED;CREATE INDEX "constraints_b_a_set_b_id" ON "constraints_b_a_set" ("b_id");CREATE INDEX "constraints_b_a_set_a_id" ON "constraints_b_a_set" ("a_id");COMMIT;
For the table constraints_b_a_set I am expecting the two foreign key constraints to include an ON DELETE CASCADE:
"a_id" integer NOT NULL REFERENCES "constraints_a" ("id") ON DELETE CASCADE ...ALTER TABLE "constraints_b_a_set" ADD CONSTRAINT "b_id_refs_id_4308660a" FOREIGN KEY ("b_id") REFERENCES "constraints_b" ("id") ON DELETE CASCADE ...I came across this running DELETE FROM ... SQL statements against a Django generated database.
Using the example models if I run DELETE FROM "constraints_a";` (with related objects in place) I get an error:
ERROR: update or delete on table "constraints_a" violates foreign key constraint "<long_indentifier>" on table "constraints_b_a_set"
DETAIL: Key (id)=(1) is still referenced from table "constraints_b_a_set".
This is because the lack of the ON DELETE CASCADE means the through table record is not removed when the referenced row is deleted.
For me this looks like a bug. First question then is, is it a bug? Or is it by design, and something that just needs working around? Does it need documenting?
Second (related) question is, would anything break if I just added the constraint in SQL myself?
I can work around this by an additional query to DELETE FROM the through table, and — whilst there’s a lot of logic there that made my head hurt — it looks as if Django does this in django/django/db/models/deletion.py — but it would be nice to let the database handle the constraint if possible.
Would Django’s own logic break if we pushed it to the database level — or, conversely, is there room to do that?
I’m happy to spend time on it.
(No doubt, having raised it, the answer will be, “No, not bug; not open to change, and documented HERE” 😀)
Thanks for the input/guidance.
Kind Regards,
Carlton