#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 on Jane that DeleteModel() deleted
  • at the very least the docs should have very big warning

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top