From 6436f1fad9ce51f18735106ac75aeea3d6d1f310 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Wed, 5 Mar 2014 03:17:07 +0700 Subject: [PATCH] Fixed #21893 -- ModelState didn't account for MTI parents inherited from abstract models. --- django/db/migrations/state.py | 22 +++++++++++++++++++--- tests/migrations/test_state.py | 15 +++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 76fc42d368..90226221be 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -151,6 +151,23 @@ class ModelState(object): options[name] = set(normalize_together(it)) else: options[name] = model._meta.original_attrs[name] + + def flatten_bases(model): + bases = [] + for base in model.__bases__: + if hasattr(base, "_meta") and base._meta.abstract: + bases.extend(flatten_bases(base)) + else: + bases.append(base) + return bases + + # We can't rely on __mro__ directly because we only want to flatten + # abstract models and not the whole tree. However by recursing on + # __bases__ we may end up with duplicates and ordering issues, we + # therefore discard any duplicates and reorder the bases according + # to their index in the MRO. + flattened_bases = sorted(set(flatten_bases(model)), key=lambda x:model.__mro__.index(x)) + # Make our record bases = tuple( ( @@ -158,12 +175,11 @@ class ModelState(object): if hasattr(base, "_meta") else base ) - for base in model.__bases__ - if (not hasattr(base, "_meta") or not base._meta.abstract) + for base in flattened_bases ) # Ensure at least one base inherits from models.Model if not any((isinstance(base, six.string_types) or issubclass(base, models.Model)) for base in bases): - bases = (models.Model, ) + bases = (models.Model,) return cls( model._meta.app_label, model._meta.object_name, diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index 187b06b94a..10f5e7d9ab 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -166,6 +166,16 @@ class StateTests(TestCase): app_label = "migrations" apps = Apps() + class AbstractSubFooBar(FooBar): + class Meta: + abstract = True + apps = Apps() + + class SubFooBar(AbstractSubFooBar): + class Meta: + app_label = "migrations" + apps = Apps() + apps = Apps(["migrations"]) # We shouldn't be able to render yet @@ -175,8 +185,13 @@ class StateTests(TestCase): # Once the parent models are in the app registry, it should be fine ModelState.from_model(Foo).render(apps) + self.assertSequenceEqual(ModelState.from_model(Foo).bases, [models.Model]) ModelState.from_model(Bar).render(apps) + self.assertSequenceEqual(ModelState.from_model(Bar).bases, [models.Model]) ModelState.from_model(FooBar).render(apps) + self.assertSequenceEqual(ModelState.from_model(FooBar).bases, ['migrations.foo', 'migrations.bar']) + ModelState.from_model(SubFooBar).render(apps) + self.assertSequenceEqual(ModelState.from_model(SubFooBar).bases, ['migrations.foobar']) def test_render_project_dependencies(self): """