Opened 4 years ago

Closed 4 years ago

#31343 closed Bug (duplicate)

Foreign key constraint to MTI child model is lost if the child model's parent was changed.

Reported by: Antonis Christofides Owned by: nobody
Component: Migrations Version: dev
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

Steps to reproduce

  1. In a project using PostgreSQL, create an app places with the following models:
    from django.db import models
    
    
    class Place(models.Model):
        name = models.CharField(max_length=50)
    
    
    class Restaurant(Place):
        serves_hot_dogs = models.BooleanField(default=False)
    
    
    class MenuItem(models.Model):
        restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
    
  1. Run ./manage.py makemigrations. It will create 0001_initial.py.
  1. Change the models to this:
    from django.db import models
    
    
    class Place(models.Model):
        name = models.CharField(max_length=50)
    
    
    class CommercialPlace(Place):
        business_name = models.CharField(max_length=50)
    
    
    class Restaurant(CommercialPlace):
        serves_hot_dogs = models.BooleanField(default=False)
    
    
    class MenuItem(models.Model):
        restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
    

The difference is that Restaurant now inherits the new CommercialPlace, whereas before it was directly inheriting Place. The MenuItem model is untouched.

  1. Execute /.manage.py makemigrations.

At this stage it will not know what to do ("You are trying to add a non-nullable field 'commercialplace_ptr' to restaurant without a default"). Select option 1 (provide a one-off default) and enter 0 as the default value.

(This doesn't make sense, but it doesn't matter. What should happen here is for commercialplace_ptr to get the value place_ptr previously had, but Django can't figure this out on its own, so the developer will need to write it in the migration. This has nothing to do with the bug, so in this example we can ignore this problem.)

Migration 0002_auto_something.py will be created as a result of this step.

  1. Execute ./manage.py sqlmigrate places 0002.

Result

The sqlmigrate output contains, among other things, this:

ALTER TABLE "places_restaurant" DROP COLUMN "place_ptr_id" CASCADE;

As a result of the CASCADE, the foreign key constraint from MenuItem.restaurant will be dropped. No constraint is created in its place.

Change History (1)

comment:1 by Mariusz Felisiak, 4 years ago

Resolution: duplicate
Status: newclosed
Summary: Foreign key constraint to a multi-table inheritance child model is lost if the child model's parent is changedForeign key constraint to MTI child model is lost if the child model's parent was changed.

Thanks for this ticket. I think it's another use case for AlterModelBases, see #23521. I was able to migrate this properly with the following operations:

    operations = [
        migrations.CreateModel(
            name='CommercialPlace',
            fields=[
                ('place_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='places.Place')),
                ('business_name', models.CharField(max_length=50)),
            ],
            bases=('places.place',),
        ),
        migrations.AlterModelBases('restaurant', ('places.commercialplace',)),
        migrations.RenameField('restaurant', 'place_ptr', 'commercialplace_ptr'),
        migrations.AlterField(
            model_name='restaurant',
            name='commercialplace_ptr',
            field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='places.CommercialPlace'),
        ),
    ]
Note: See TracTickets for help on using tickets.
Back to Top