From 114da2d0455594bae0bbda8f2b1d46c5ec854956 Mon Sep 17 00:00:00 2001 From: Nan Liu <45443761+nanlliu@users.noreply.github.com> Date: Sun, 24 May 2020 08:59:46 +0800 Subject: [PATCH] [3.1.x] Fixed #31416 -- Made autodetector find dependencies for MTI model creation on base fields removal. Removing a base field must take place before adding a new inherited model that has a field with the same name. Backport of 33c365781abbcc1b21a31b31d95d344a174df0d5 from master --- django/db/migrations/autodetector.py | 10 ++++++++++ tests/migrations/test_autodetector.py | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 30616c0172b..85c30138975 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -563,6 +563,16 @@ 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)) + # Depend on the removal of base fields if the new model has + # a field with the same name. + old_base_model_state = self.from_state.models.get((base_app_label, base_name)) + new_base_model_state = self.to_state.models.get((base_app_label, base_name)) + if old_base_model_state and new_base_model_state: + removed_base_fields = set(old_base_model_state.fields).difference( + new_base_model_state.fields, + ).intersection(model_state.fields) + for removed_base_field in removed_base_fields: + dependencies.append((base_app_label, base_name, removed_base_field, False)) # Depend on the other end of the primary key if it's a relation if primary_key_rel: dependencies.append(( diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index daff57fc2cf..10af72561b4 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -2454,3 +2454,28 @@ class AutodetectorTests(TestCase): self.assertNumberMigrations(changes, 'app', 1) self.assertOperationTypes(changes, 'app', 0, ['DeleteModel']) self.assertOperationAttributes(changes, 'app', 0, 0, name='Dog') + + def test_add_model_with_field_removed_from_base_model(self): + """ + Removing a base field takes place before adding a new inherited model + that has a field with the same name. + """ + before = [ + ModelState('app', 'readable', [ + ('id', models.AutoField(primary_key=True)), + ('title', models.CharField(max_length=200)), + ]), + ] + after = [ + ModelState('app', 'readable', [ + ('id', models.AutoField(primary_key=True)), + ]), + ModelState('app', 'book', [ + ('title', models.CharField(max_length=200)), + ], bases=('app.readable',)), + ] + changes = self.get_changes(before, after) + self.assertNumberMigrations(changes, 'app', 1) + self.assertOperationTypes(changes, 'app', 0, ['RemoveField', 'CreateModel']) + self.assertOperationAttributes(changes, 'app', 0, 0, name='title', model_name='readable') + self.assertOperationAttributes(changes, 'app', 0, 1, name='book')