mirror of
https://github.com/django/django.git
synced 2025-11-18 19:01:40 +00:00
Fix migration order for fields moved from parent to child
Ensure RemoveField on parent model runs before child CreateModel to avoid FieldError when moving a field in model inheritance. Fixes #21890.
This commit is contained in:
parent
18759b2209
commit
fdb4410ce7
2 changed files with 49 additions and 0 deletions
|
|
@ -563,6 +563,24 @@ class MigrationAutodetector:
|
||||||
if isinstance(base, str) and "." in base:
|
if isinstance(base, str) and "." in base:
|
||||||
base_app_label, base_name = base.split(".", 1)
|
base_app_label, base_name = base.split(".", 1)
|
||||||
dependencies.append((base_app_label, base_name, None, True))
|
dependencies.append((base_app_label, base_name, None, True))
|
||||||
|
# Check if any fields in this model exist in the parent model
|
||||||
|
# in the old state and need to be removed first (to avoid
|
||||||
|
# FieldError when a field is moved from parent to child).
|
||||||
|
if (base_app_label, base_name.lower()) in self.kept_model_keys:
|
||||||
|
old_base_model_state = self.from_state.models.get(
|
||||||
|
(base_app_label, base_name.lower())
|
||||||
|
)
|
||||||
|
if old_base_model_state:
|
||||||
|
for field_name in model_state.fields:
|
||||||
|
if field_name in old_base_model_state.fields:
|
||||||
|
# This field exists in the parent in the old state
|
||||||
|
# Depend on its removal from the parent
|
||||||
|
dependencies.append((
|
||||||
|
base_app_label,
|
||||||
|
base_name.lower(),
|
||||||
|
field_name,
|
||||||
|
False
|
||||||
|
))
|
||||||
# Depend on the other end of the primary key if it's a relation
|
# Depend on the other end of the primary key if it's a relation
|
||||||
if primary_key_rel:
|
if primary_key_rel:
|
||||||
dependencies.append((
|
dependencies.append((
|
||||||
|
|
|
||||||
|
|
@ -2454,3 +2454,34 @@ class AutodetectorTests(TestCase):
|
||||||
self.assertNumberMigrations(changes, 'app', 1)
|
self.assertNumberMigrations(changes, 'app', 1)
|
||||||
self.assertOperationTypes(changes, 'app', 0, ['DeleteModel'])
|
self.assertOperationTypes(changes, 'app', 0, ['DeleteModel'])
|
||||||
self.assertOperationAttributes(changes, 'app', 0, 0, name='Dog')
|
self.assertOperationAttributes(changes, 'app', 0, 0, name='Dog')
|
||||||
|
|
||||||
|
def test_create_model_with_field_removed_from_base_model(self):
|
||||||
|
"""
|
||||||
|
Tests autodetection of field being moved from parent model to child
|
||||||
|
model. The field removal from the parent should be ordered before the
|
||||||
|
child model creation to avoid FieldError.
|
||||||
|
"""
|
||||||
|
# Before: Readable model with title field
|
||||||
|
readable_with_field = ModelState('app', 'Readable', [
|
||||||
|
("id", models.AutoField(primary_key=True)),
|
||||||
|
("title", models.CharField(max_length=200)),
|
||||||
|
])
|
||||||
|
# After: Readable without title, Book subclass with title
|
||||||
|
readable_without_field = ModelState('app', 'Readable', [
|
||||||
|
("id", models.AutoField(primary_key=True)),
|
||||||
|
])
|
||||||
|
book = ModelState('app', 'Book', [
|
||||||
|
("readable_ptr", models.OneToOneField(
|
||||||
|
"app.Readable",
|
||||||
|
models.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
auto_created=True,
|
||||||
|
)),
|
||||||
|
("title", models.CharField(max_length=200)),
|
||||||
|
], bases=('app.Readable',))
|
||||||
|
changes = self.get_changes([readable_with_field], [readable_without_field, book])
|
||||||
|
self.assertNumberMigrations(changes, 'app', 1)
|
||||||
|
self.assertOperationTypes(changes, 'app', 0, ['RemoveField', 'CreateModel'])
|
||||||
|
self.assertOperationAttributes(changes, 'app', 0, 0, model_name='readable', name='title')
|
||||||
|
self.assertOperationAttributes(changes, 'app', 0, 1, name='Book')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue