Fixed #22204: Bad circular-dep-breaking if more than one per run

This commit is contained in:
Andrew Godwin 2014-03-06 13:22:42 -08:00
parent 8ce3ea687c
commit e46e15e5a1
2 changed files with 15 additions and 4 deletions

View File

@ -94,9 +94,14 @@ class MigrationAutodetector(object):
# Phase 2 is progressively adding pending models, splitting up into two # Phase 2 is progressively adding pending models, splitting up into two
# migrations if required. # migrations if required.
pending_new_fks = [] pending_new_fks = []
added_phase_2 = set()
while pending_add: while pending_add:
# Is there one we can add that has all dependencies satisfied? # Is there one we can add that has all dependencies satisfied?
satisfied = [(m, rf) for m, rf in pending_add.items() if all((al, mn) not in pending_add for f, al, mn in rf)] satisfied = [
(m, rf)
for m, rf in pending_add.items()
if all((al, mn) not in pending_add for f, al, mn in rf)
]
if satisfied: if satisfied:
(app_label, model_name), related_fields = sorted(satisfied)[0] (app_label, model_name), related_fields = sorted(satisfied)[0]
model_state = self.to_state.models[app_label, model_name] model_state = self.to_state.models[app_label, model_name]
@ -107,7 +112,10 @@ class MigrationAutodetector(object):
fields=model_state.fields, fields=model_state.fields,
options=model_state.options, options=model_state.options,
bases=model_state.bases, bases=model_state.bases,
) ),
# If it's already been added in phase 2 put it in a new
# migration for safety.
new=any((al, mn) in added_phase_2 for f, al, mn in related_fields),
) )
for field_name, other_app_label, other_model_name in related_fields: for field_name, other_app_label, other_model_name in related_fields:
# If it depends on a swappable something, add a dynamic depend'cy # If it depends on a swappable something, add a dynamic depend'cy
@ -117,6 +125,7 @@ class MigrationAutodetector(object):
elif app_label != other_app_label: elif app_label != other_app_label:
self.add_dependency(app_label, other_app_label) self.add_dependency(app_label, other_app_label)
del pending_add[app_label, model_name] del pending_add[app_label, model_name]
added_phase_2.add((app_label, model_name))
# Ah well, we'll need to split one. Pick deterministically. # Ah well, we'll need to split one. Pick deterministically.
else: else:
(app_label, model_name), related_fields = sorted(pending_add.items())[0] (app_label, model_name), related_fields = sorted(pending_add.items())[0]
@ -342,6 +351,7 @@ class MigrationAutodetector(object):
else: else:
new_name = "%04i_%s" % (next_number, self.suggest_name(migration.operations)) new_name = "%04i_%s" % (next_number, self.suggest_name(migration.operations))
name_map[(app_label, migration.name)] = (app_label, new_name) name_map[(app_label, migration.name)] = (app_label, new_name)
next_number += 1
migration.name = new_name migration.name = new_name
# Now fix dependencies # Now fix dependencies
for app_label, migrations in changes.items(): for app_label, migrations in changes.items():

View File

@ -24,6 +24,7 @@ class AutodetectorTests(TestCase):
author_proxy_notproxy = ModelState("testapp", "AuthorProxy", [], {}, ("testapp.author", )) author_proxy_notproxy = ModelState("testapp", "AuthorProxy", [], {}, ("testapp.author", ))
publisher = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=100))]) publisher = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=100))])
publisher_with_author = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("name", models.CharField(max_length=100))]) publisher_with_author = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("name", models.CharField(max_length=100))])
publisher_with_book = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("otherapp.Book")), ("name", models.CharField(max_length=100))])
other_pony = ModelState("otherapp", "Pony", [("id", models.AutoField(primary_key=True))]) other_pony = ModelState("otherapp", "Pony", [("id", models.AutoField(primary_key=True))])
other_stable = ModelState("otherapp", "Stable", [("id", models.AutoField(primary_key=True))]) other_stable = ModelState("otherapp", "Stable", [("id", models.AutoField(primary_key=True))])
third_thing = ModelState("thirdapp", "Thing", [("id", models.AutoField(primary_key=True))]) third_thing = ModelState("thirdapp", "Thing", [("id", models.AutoField(primary_key=True))])
@ -243,7 +244,7 @@ class AutodetectorTests(TestCase):
""" """
# Make state # Make state
before = self.make_project_state([]) before = self.make_project_state([])
after = self.make_project_state([self.author_with_book, self.book]) after = self.make_project_state([self.author_with_book, self.book, self.publisher_with_book])
autodetector = MigrationAutodetector(before, after) autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes() changes = autodetector._detect_changes()
# Right number of migrations? # Right number of migrations?
@ -251,7 +252,7 @@ class AutodetectorTests(TestCase):
self.assertEqual(len(changes['otherapp']), 2) self.assertEqual(len(changes['otherapp']), 2)
# Right number of actions? # Right number of actions?
migration1 = changes['testapp'][0] migration1 = changes['testapp'][0]
self.assertEqual(len(migration1.operations), 1) self.assertEqual(len(migration1.operations), 2)
migration2 = changes['otherapp'][0] migration2 = changes['otherapp'][0]
self.assertEqual(len(migration2.operations), 1) self.assertEqual(len(migration2.operations), 1)
migration3 = changes['otherapp'][1] migration3 = changes['otherapp'][1]