Commit 3f91238a authored by Andrew Godwin's avatar Andrew Godwin
Browse files

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

parent 6e5651e5
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -94,7 +94,11 @@ class Command(BaseCommand):
            return

        # 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.
        if not changes and self.verbosity >= 1:
+16 −7
Original line number Diff line number Diff line
@@ -28,13 +28,13 @@ class MigrationAutodetector(object):
        self.to_state = to_state
        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.
        Takes a graph to base names on and an optional set of apps
        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)
        if trim_to_apps:
            changes = self._trim_to_apps(changes, trim_to_apps)
@@ -77,7 +77,7 @@ class MigrationAutodetector(object):
            fields_def.append(deconstruction)
        return fields_def

    def _detect_changes(self):
    def _detect_changes(self, convert_apps=None):
        """
        Returns a dict of migration plans which will achieve the
        change from from_state to to_state. The dict has app labels
@@ -105,7 +105,10 @@ class MigrationAutodetector(object):
        self.new_model_keys = []
        for al, mn in sorted(self.to_state.models.keys()):
            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))

        # Renames have to come first
@@ -376,6 +379,14 @@ class MigrationAutodetector(object):
                else:
                    dep_app_label = field.rel.to._meta.app_label
                    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
                self.add_operation(
                    app_label,
@@ -384,9 +395,7 @@ class MigrationAutodetector(object):
                        name=name,
                        field=field,
                    ),
                    dependencies=[
                        (dep_app_label, dep_object_name, None, True),
                    ]
                    dependencies=list(set(dependencies)),
                )
            # Generate other opns
            if unique_together:
+15 −1
Original line number Diff line number Diff line
@@ -79,7 +79,7 @@ class AutodetectorTests(TestCase):
    def assertNumberMigrations(self, changes, app_label, number):
        if len(changes.get(app_label, [])) != number:
            self.fail("Incorrect number of migrations (%s) for %s (expected %s)\n%s" % (
                len(changes[app_label]),
                len(changes.get(app_label, [])),
                app_label,
                number,
                self.repr_changes(changes),
@@ -706,6 +706,20 @@ class AutodetectorTests(TestCase):
        self.assertEqual(action.__class__.__name__, "AddField")
        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):
        """
        Removing a ManyToManyField and the "through" model in the same change must remove