mirror of https://github.com/django/django.git
Fixed #22777: Add dependency on through for autodetected M2M adds
This commit is contained in:
parent
6e5651e514
commit
3f91238adf
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue