Opened 98 minutes ago
#36779 new Bug
DeleteModel can lead to missing FK constraints
| Reported by: | Jamie Cockburn | Owned by: | |
|---|---|---|---|
| Component: | Migrations | Version: | 6.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
I have two models, Jane and Bob. Jane has ForeignKey relationship to Bob.
I generate a migration 0001_initial:
class Migration(migrations.Migration): operations = [ migrations.CreateModel( name='Bob', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ], ), migrations.CreateModel( name='Jane', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('bob', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='constraintbug.bob')), ], ), ]
Jane is rendered in postgres as:
% docker compose exec db psql -U constraintbug psql (18.0 (Debian 18.0-1.pgdg13+3)) Type "help" for help. constraintbug=# \d constraintbug_jane Table "public.constraintbug_jane" Column | Type | Collation | Nullable | Default --------+--------+-----------+----------+---------------------------------- id | bigint | | not null | generated by default as identity bob_id | bigint | | not null | Indexes: "constraintbug_jane_pkey" PRIMARY KEY, btree (id) "constraintbug_jane_bob_id_35b76f6b" btree (bob_id) Foreign-key constraints: "constraintbug_jane_bob_id_35b76f6b_fk_constraintbug_bob_id" FOREIGN KEY (bob_id) REFERENCES constraintbug_bob(id) DEFERRABLE INITIALLY DEFERRED
Note the FK constraint.
Now, I decide to manually write a migration, because I want to remove Bob and replace it with a new thing. In my case, I was introducing model inheritance, and didn't want to revise my code to remove everything, makemigrations, add everything back, makemigrations again, and I decided to just manually type migrations.DeleteModel().
class Migration(migrations.Migration): operations = [ migrations.DeleteModel( # this should fail? name='Bob', ), ]
Now things start to go wrong. I would expect at this point that the migration should fail, because I'm trying to delete a table that is referred to by the FK constraint. Instead, is just deletes the constraint:
constraintbug=# \d constraintbug_jane Table "public.constraintbug_jane" Column | Type | Collation | Nullable | Default --------+--------+-----------+----------+---------------------------------- id | bigint | | not null | generated by default as identity bob_id | bigint | | not null | Indexes: "constraintbug_jane_pkey" PRIMARY KEY, btree (id) "constraintbug_jane_bob_id_35b76f6b" btree (bob_id)
I suppose we could say that this is an "intermediate" state, but then, I go and add the model back:
class Migration(migrations.Migration): operations = [ migrations.CreateModel( # maybe this should re-create the FK constraint on jane? name='Bob', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ], ), ]
But Jane is still missing her constraint, and worse, I can do things like:
Jane.objects.create( bob_id=1234, # there is no such bob with pk 1234, this should fail!!! )
Here's a wee example repo showing the issue:
https://github.com/daggaz/django-deletemodel/tree/master
I think that either:
- the
DeleteModel()migration should fail to delete a model that is referenced - the 2nd
CreateModel()migration should recreate the FK constraint onJanethatDeleteModel()deleted - at the very least the docs should have very big warning