Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixed #21893 -- ModelState didn't account for MTI parents inherited from abstract models. #2396

Merged
merged 2 commits into from

2 participants

@loic
Collaborator

No description provided.

@andrewgodwin andrewgodwin merged commit 8fcc014 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 4, 2014
  1. @loic

    Added tests for MTI in RunPython.

    loic authored loic committed
  2. @loic
This page is out of date. Refresh to see the latest.
View
22 django/db/migrations/state.py
@@ -151,6 +151,23 @@ def from_model(cls, model):
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 @@ def from_model(cls, model):
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,
View
53 tests/migrations/test_operations.py
@@ -14,7 +14,7 @@ class OperationTests(MigrationTestBase):
both forwards and backwards.
"""
- def set_up_test_model(self, app_label, second_model=False, related_model=False):
+ def set_up_test_model(self, app_label, second_model=False, related_model=False, mti_model=False):
"""
Creates a test model state and database table.
"""
@@ -38,7 +38,12 @@ def set_up_test_model(self, app_label, second_model=False, related_model=False):
],
)]
if second_model:
- operations.append(migrations.CreateModel("Stable", [("id", models.AutoField(primary_key=True))]))
+ operations.append(migrations.CreateModel(
+ "Stable",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ]
+ ))
if related_model:
operations.append(migrations.CreateModel(
"Rider",
@@ -47,6 +52,21 @@ def set_up_test_model(self, app_label, second_model=False, related_model=False):
("pony", models.ForeignKey("Pony")),
],
))
+ if mti_model:
+ operations.append(migrations.CreateModel(
+ "ShetlandPony",
+ fields=[
+ ('pony_ptr', models.OneToOneField(
+ auto_created=True,
+ primary_key=True,
+ to_field='id',
+ serialize=False,
+ to='Pony',
+ )),
+ ("cuteness", models.IntegerField(default=1)),
+ ],
+ bases=['%s.Pony' % app_label],
+ ))
project_state = ProjectState()
for operation in operations:
operation.state_forwards(app_label, project_state)
@@ -495,7 +515,7 @@ def test_run_python(self):
Tests the RunPython operation
"""
- project_state = self.set_up_test_model("test_runpython")
+ project_state = self.set_up_test_model("test_runpython", mti_model=True)
# Create the operation
def inner_method(models, schema_editor):
@@ -533,7 +553,34 @@ def inner_method_reverse(models, schema_editor):
no_reverse_operation.database_forwards("test_runpython", editor, project_state, new_state)
with self.assertRaises(NotImplementedError):
no_reverse_operation.database_backwards("test_runpython", editor, new_state, project_state)
+ self.assertEqual(project_state.render().get_model("test_runpython", "Pony").objects.count(), 2)
+ def create_ponies(models, schema_editor):
+ Pony = models.get_model("test_runpython", "Pony")
+ pony1 = Pony.objects.create(pink=1, weight=3.55)
+ self.assertIsNot(pony1.pk, None)
+ pony2 = Pony.objects.create(weight=5)
+ self.assertIsNot(pony2.pk, None)
+ self.assertNotEqual(pony1.pk, pony2.pk)
+
+ operation = migrations.RunPython(create_ponies)
+ with connection.schema_editor() as editor:
+ operation.database_forwards("test_runpython", editor, project_state, new_state)
+ self.assertEqual(project_state.render().get_model("test_runpython", "Pony").objects.count(), 4)
+
+ def create_shetlandponies(models, schema_editor):
+ ShetlandPony = models.get_model("test_runpython", "ShetlandPony")
+ pony1 = ShetlandPony.objects.create(weight=4.0)
+ self.assertIsNot(pony1.pk, None)
+ pony2 = ShetlandPony.objects.create(weight=5.0)
+ self.assertIsNot(pony2.pk, None)
+ self.assertNotEqual(pony1.pk, pony2.pk)
+
+ operation = migrations.RunPython(create_shetlandponies)
+ with connection.schema_editor() as editor:
+ operation.database_forwards("test_runpython", editor, project_state, new_state)
+ self.assertEqual(project_state.render().get_model("test_runpython", "Pony").objects.count(), 6)
+ self.assertEqual(project_state.render().get_model("test_runpython", "ShetlandPony").objects.count(), 2)
class MigrateNothingRouter(object):
"""
View
15 tests/migrations/test_state.py
@@ -166,6 +166,16 @@ class Meta:
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 Meta:
# 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):
"""
Something went wrong with that request. Please try again.