[Django] #31343: Foreign key constraint to a multi-table inheritance child model is lost if the child model's parent is changed

8 views
Skip to first unread message

Django

unread,
Mar 5, 2020, 9:34:46 AM3/5/20
to django-...@googlegroups.com
#31343: Foreign key constraint to a multi-table inheritance child model is lost if
the child model's parent is changed
------------------------------------------------+------------------------
Reporter: Antonis Christofides | Owner: nobody
Type: Bug | Status: new
Component: Migrations | Version: master
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
------------------------------------------------+------------------------
= 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)
}}}

2. Run `./manage.py makemigrations`. It will create `0001_initial.py`.

3. 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.

4. 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.

5. 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.**

--
Ticket URL: <https://code.djangoproject.com/ticket/31343>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Mar 6, 2020, 5:40:28 AM3/6/20
to django-...@googlegroups.com
#31343: Foreign key constraint to MTI child model is lost if the child model's
parent was changed.
-------------------------------------+-------------------------------------
Reporter: Antonis | Owner: nobody
Christofides |
Type: Bug | Status: closed
Component: Migrations | Version: master
Severity: Normal | Resolution: duplicate

Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by felixxm):

* status: new => closed
* resolution: => duplicate


Comment:

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'),
),
]
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/31343#comment:1>

Reply all
Reply to author
Forward
0 new messages