Commit 2da20379 authored by Markus Holtermann's avatar Markus Holtermann Committed by Tim Graham
Browse files

[1.7.x] Fixed #23322 -- Use resolved swappable model for dependency resolution...

[1.7.x] Fixed #23322 -- Use resolved swappable model for dependency resolution during makemigrations

Backport of 144cff3f from master
parent 2b31342c
Loading
Loading
Loading
Loading
+13 −3
Original line number Diff line number Diff line
@@ -237,9 +237,17 @@ class MigrationAutodetector(object):
                    deps_satisfied = True
                    operation_dependencies = set()
                    for dep in operation._auto_deps:
                        is_swappable_dep = False
                        if dep[0] == "__setting__":
                            operation_dependencies.add((dep[0], dep[1]))
                        elif dep[0] != app_label:
                            # We need to temporarily resolve the swappable dependency to prevent
                            # circular references. While keeping the dependency checks on the
                            # resolved model we still add the swappable dependencies.
                            # See #23322
                            resolved_app_label, resolved_object_name = getattr(settings, dep[1]).split('.')
                            original_dep = dep
                            dep = (resolved_app_label, resolved_object_name.lower(), dep[2], dep[3])
                            is_swappable_dep = True
                        if dep[0] != app_label and dep[0] != "__setting__":
                            # External app dependency. See if it's not yet
                            # satisfied.
                            for other_operation in self.generated_operations.get(dep[0], []):
@@ -249,7 +257,9 @@ class MigrationAutodetector(object):
                            if not deps_satisfied:
                                break
                            else:
                                if self.migrations.get(dep[0], None):
                                if is_swappable_dep:
                                    operation_dependencies.add((original_dep[0], original_dep[1]))
                                elif dep[0] in self.migrations:
                                    operation_dependencies.add((dep[0], self.migrations[dep[0]][-1].name))
                                else:
                                    # If we can't find the other app, we add a first/last dependency,
+82 −0
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-
from django.conf import settings
from django.test import TestCase, override_settings
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.questioner import MigrationQuestioner
@@ -1091,3 +1092,84 @@ class AutodetectorTests(TestCase):
        self.assertOperationTypes(changes, 'a', 0, ["CreateModel", "CreateModel"])
        self.assertOperationTypes(changes, 'a', 1, ["AddField"])
        self.assertOperationTypes(changes, 'b', 0, ["CreateModel", "CreateModel"])

    @override_settings(AUTH_USER_MODEL="a.Tenant")
    def test_circular_dependency_swappable(self):
        """
        Tests that the dependency resolver knows to explicitly resolve
        swappable models (#23322)
        """
        tenant = ModelState("a", "Tenant", [
            ("id", models.AutoField(primary_key=True)),
            ("primary_address", models.ForeignKey("b.Address"))],
            bases=(AbstractBaseUser, )
        )
        address = ModelState("b", "Address", [
            ("id", models.AutoField(primary_key=True)),
            ("tenant", models.ForeignKey(settings.AUTH_USER_MODEL)),
        ])
        # Make state
        before = self.make_project_state([])
        after = self.make_project_state([address, tenant])
        autodetector = MigrationAutodetector(before, after)
        changes = autodetector._detect_changes()
        # Right number of migrations?
        self.assertNumberMigrations(changes, 'a', 2)
        self.assertNumberMigrations(changes, 'b', 1)
        self.assertOperationTypes(changes, 'a', 0, ["CreateModel"])
        self.assertOperationTypes(changes, 'a', 1, ["AddField"])
        self.assertOperationTypes(changes, 'b', 0, ["CreateModel"])
        self.assertEqual(changes['a'][0].dependencies, [])
        self.assertEqual(set(changes['a'][1].dependencies), set([('a', 'auto_1'), ('b', 'auto_1')]))
        self.assertEqual(changes['b'][0].dependencies, [('__setting__', 'AUTH_USER_MODEL')])

    @override_settings(AUTH_USER_MODEL="b.Tenant")
    def test_circular_dependency_swappable2(self):
        """
        Tests that the dependency resolver knows to explicitly resolve
        swappable models but with the swappable not being the first migrated
        model (#23322)
        """
        address = ModelState("a", "Address", [
            ("id", models.AutoField(primary_key=True)),
            ("tenant", models.ForeignKey(settings.AUTH_USER_MODEL)),
        ])
        tenant = ModelState("b", "Tenant", [
            ("id", models.AutoField(primary_key=True)),
            ("primary_address", models.ForeignKey("a.Address"))],
            bases=(AbstractBaseUser, )
        )
        # Make state
        before = self.make_project_state([])
        after = self.make_project_state([address, tenant])
        autodetector = MigrationAutodetector(before, after)
        changes = autodetector._detect_changes()
        # Right number of migrations?
        self.assertNumberMigrations(changes, 'a', 2)
        self.assertNumberMigrations(changes, 'b', 1)
        self.assertOperationTypes(changes, 'a', 0, ["CreateModel"])
        self.assertOperationTypes(changes, 'a', 1, ["AddField"])
        self.assertOperationTypes(changes, 'b', 0, ["CreateModel"])
        self.assertEqual(changes['a'][0].dependencies, [])
        self.assertEqual(set(changes['a'][1].dependencies), set([('__setting__', 'AUTH_USER_MODEL'), ('a', 'auto_1')]))
        self.assertEqual(changes['b'][0].dependencies, [('a', 'auto_1')])

    @override_settings(AUTH_USER_MODEL="a.Person")
    def test_circular_dependency_swappable_self(self):
        """
        Tests that the dependency resolver knows to explicitly resolve
        swappable models (#23322)
        """
        person = ModelState("a", "Person", [
            ("id", models.AutoField(primary_key=True)),
            ("parent1", models.ForeignKey(settings.AUTH_USER_MODEL, related_name='children'))
        ])
        # Make state
        before = self.make_project_state([])
        after = self.make_project_state([person])
        autodetector = MigrationAutodetector(before, after)
        changes = autodetector._detect_changes()
        # Right number of migrations?
        self.assertNumberMigrations(changes, 'a', 1)
        self.assertOperationTypes(changes, 'a', 0, ["CreateModel"])
        self.assertEqual(changes['a'][0].dependencies, [])