Fixed #22777: Add dependency on through for autodetected M2M adds

This commit is contained in:
Andrew Godwin 2014-06-08 17:12:27 -07:00
parent 6e5651e514
commit 3f91238adf
3 changed files with 36 additions and 9 deletions

View File

@ -94,7 +94,11 @@ class Command(BaseCommand):
return return
# Detect changes # Detect changes
changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None) changes = autodetector.changes(
graph=loader.graph,
trim_to_apps=app_labels or None,
convert_apps=app_labels or None,
)
# No changes? Tell them. # No changes? Tell them.
if not changes and self.verbosity >= 1: if not changes and self.verbosity >= 1:

View File

@ -28,13 +28,13 @@ class MigrationAutodetector(object):
self.to_state = to_state self.to_state = to_state
self.questioner = questioner or MigrationQuestioner() self.questioner = questioner or MigrationQuestioner()
def changes(self, graph, trim_to_apps=None): def changes(self, graph, trim_to_apps=None, convert_apps=None):
""" """
Main entry point to produce a list of appliable changes. Main entry point to produce a list of appliable changes.
Takes a graph to base names on and an optional set of apps Takes a graph to base names on and an optional set of apps
to try and restrict to (restriction is not guaranteed) to try and restrict to (restriction is not guaranteed)
""" """
changes = self._detect_changes() changes = self._detect_changes(convert_apps)
changes = self.arrange_for_graph(changes, graph) changes = self.arrange_for_graph(changes, graph)
if trim_to_apps: if trim_to_apps:
changes = self._trim_to_apps(changes, trim_to_apps) changes = self._trim_to_apps(changes, trim_to_apps)
@ -77,7 +77,7 @@ class MigrationAutodetector(object):
fields_def.append(deconstruction) fields_def.append(deconstruction)
return fields_def return fields_def
def _detect_changes(self): def _detect_changes(self, convert_apps=None):
""" """
Returns a dict of migration plans which will achieve the Returns a dict of migration plans which will achieve the
change from from_state to to_state. The dict has app labels change from from_state to to_state. The dict has app labels
@ -105,7 +105,10 @@ class MigrationAutodetector(object):
self.new_model_keys = [] self.new_model_keys = []
for al, mn in sorted(self.to_state.models.keys()): for al, mn in sorted(self.to_state.models.keys()):
model = self.new_apps.get_model(al, mn) model = self.new_apps.get_model(al, mn)
if not model._meta.proxy and model._meta.managed and al not in self.from_state.real_apps: if not model._meta.proxy and model._meta.managed and (
al not in self.from_state.real_apps or
(convert_apps and al in convert_apps)
):
self.new_model_keys.append((al, mn)) self.new_model_keys.append((al, mn))
# Renames have to come first # Renames have to come first
@ -376,6 +379,14 @@ class MigrationAutodetector(object):
else: else:
dep_app_label = field.rel.to._meta.app_label dep_app_label = field.rel.to._meta.app_label
dep_object_name = field.rel.to._meta.object_name dep_object_name = field.rel.to._meta.object_name
dependencies = [(dep_app_label, dep_object_name, None, True)]
if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created:
dependencies.append((
field.rel.through._meta.app_label,
field.rel.through._meta.object_name,
None,
True
))
# Make operation # Make operation
self.add_operation( self.add_operation(
app_label, app_label,
@ -384,9 +395,7 @@ class MigrationAutodetector(object):
name=name, name=name,
field=field, field=field,
), ),
dependencies=[ dependencies=list(set(dependencies)),
(dep_app_label, dep_object_name, None, True),
]
) )
# Generate other opns # Generate other opns
if unique_together: if unique_together:

View File

@ -79,7 +79,7 @@ class AutodetectorTests(TestCase):
def assertNumberMigrations(self, changes, app_label, number): def assertNumberMigrations(self, changes, app_label, number):
if len(changes.get(app_label, [])) != number: if len(changes.get(app_label, [])) != number:
self.fail("Incorrect number of migrations (%s) for %s (expected %s)\n%s" % ( self.fail("Incorrect number of migrations (%s) for %s (expected %s)\n%s" % (
len(changes[app_label]), len(changes.get(app_label, [])),
app_label, app_label,
number, number,
self.repr_changes(changes), self.repr_changes(changes),
@ -706,6 +706,20 @@ class AutodetectorTests(TestCase):
self.assertEqual(action.__class__.__name__, "AddField") self.assertEqual(action.__class__.__name__, "AddField")
self.assertEqual(action.name, "publishers") self.assertEqual(action.name, "publishers")
def test_create_with_through_model(self):
"""
Adding a m2m with a through model and the models that use it should
be ordered correctly.
"""
before = self.make_project_state([])
after = self.make_project_state([self.author_with_m2m_through, self.publisher, self.contract])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number of migrations?
self.assertNumberMigrations(changes, "testapp", 1)
# Right actions in right order?
self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel", "CreateModel", "AddField", "AddField"])
def test_many_to_many_removed_before_through_model(self): def test_many_to_many_removed_before_through_model(self):
""" """
Removing a ManyToManyField and the "through" model in the same change must remove Removing a ManyToManyField and the "through" model in the same change must remove