mirror of
https://github.com/django/django.git
synced 2025-11-17 02:24:22 +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:
|
||||
base_app_label, base_name = base.split(".", 1)
|
||||
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
|
||||
if primary_key_rel:
|
||||
dependencies.append((
|
||||
|
|
|
|||
|
|
@ -2454,3 +2454,34 @@ class AutodetectorTests(TestCase):
|
|||
self.assertNumberMigrations(changes, 'app', 1)
|
||||
self.assertOperationTypes(changes, 'app', 0, ['DeleteModel'])
|
||||
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