Commit 64887c64 authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Fixed #21142: Dependency failures on unmigrated apps.

parent 0423e079
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -299,6 +299,7 @@ class MigrationAutodetector(object):
                for migration in migrations:
                    name_map[(app_label, migration.name)] = (app_label, "__first__")
                del changes[app_label]
                continue
            # Work out the next number in the sequence
            if app_leaf is None:
                next_number = 1
+5 −2
Original line number Diff line number Diff line
@@ -63,14 +63,14 @@ class MigrationGraph(object):
            raise ValueError("Node %r not a valid node" % (node, ))
        return self.dfs(node, lambda x: self.dependents.get(x, set()))

    def root_nodes(self):
    def root_nodes(self, app=None):
        """
        Returns all root nodes - that is, nodes with no dependencies inside
        their app. These are the starting point for an app.
        """
        roots = set()
        for node in self.nodes:
            if not any(key[0] == node[0] for key in self.dependencies.get(node, set())):
            if not any(key[0] == node[0] for key in self.dependencies.get(node, set())) and (not app or app == node[0]):
                roots.add(node)
        return roots

@@ -145,6 +145,9 @@ class MigrationGraph(object):
            project_state = self.nodes[node].mutate_state(project_state)
        return project_state

    def __contains__(self, node):
        return node in self.nodes


class CircularDependencyError(Exception):
    """
+35 −0
Original line number Diff line number Diff line
@@ -5,6 +5,9 @@ import sys
from django.apps import apps
from django.db.migrations.recorder import MigrationRecorder
from django.db.migrations.graph import MigrationGraph
from django.db.migrations.migration import Migration
from django.db.migrations.state import ModelState
from django.db.migrations import operations
from django.utils import six
from django.conf import settings

@@ -191,6 +194,38 @@ class MigrationLoader(object):
            self.graph.add_node(key, migration)
        for key, migration in normal.items():
            for parent in migration.dependencies:
                # Special-case __first__, which means "the first migration" for
                # migrated apps, and is ignored for unmigrated apps. It allows
                # makemigrations to declare dependencies on apps before they
                # even have migrations.
                if parent[1] == "__first__" and parent not in self.graph:
                    if parent[0] in self.unmigrated_apps:
                        # This app isn't migrated, but something depends on it.
                        # We'll add a fake initial migration for it into the
                        # graph.
                        app_config = apps.get_app_config(parent[0])
                        ops = []
                        for model in app_config.get_models():
                            model_state = ModelState.from_model(model)
                            ops.append(
                                operations.CreateModel(
                                    name=model_state.name,
                                    fields=model_state.fields,
                                    options=model_state.options,
                                    bases=model_state.bases,
                                )
                            )
                        new_migration = type(
                            "FakeInitialMigration",
                            (Migration, ),
                            {"operations": ops},
                        )(parent[1], parent[0])
                        self.graph.add_node(parent, new_migration)
                        self.applied_migrations.add(parent)
                    elif parent[0] in self.migrated_apps:
                        parent = (parent[0], list(self.graph.root_nodes(parent[0]))[0])
                    else:
                        raise ValueError("Dependency on unknown app %s" % parent[0])
                self.graph.add_dependency(key, parent)

    def detect_conflicts(self):
+28 −1
Original line number Diff line number Diff line
@@ -49,7 +49,10 @@ class LoaderTests(TestCase):
        migration_loader = MigrationLoader(connection)
        self.assertEqual(
            migration_loader.graph.forwards_plan(("migrations", "0002_second")),
            [("migrations", "0001_initial"), ("migrations", "0002_second")],
            [
                ("migrations", "0001_initial"),
                ("migrations", "0002_second"),
            ],
        )
        # Now render it out!
        project_state = migration_loader.graph.project_state(("migrations", "0002_second"))
@@ -67,6 +70,30 @@ class LoaderTests(TestCase):
            ["id", "author"]
        )

    @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_unmigdep"})
    def test_load_unmigrated_dependency(self):
        """
        Makes sure the loader can load migrations with a dependency on an unmigrated app.
        """
        # Load and test the plan
        migration_loader = MigrationLoader(connection)
        self.assertEqual(
            migration_loader.graph.forwards_plan(("migrations", "0001_initial")),
            [
                ("auth", "__first__"),
                ("migrations", "0001_initial"),
            ],
        )
        # Now render it out!
        project_state = migration_loader.graph.project_state(("migrations", "0001_initial"))
        self.assertEqual(len(project_state.models), 4)

        book_state = project_state.models["migrations", "book"]
        self.assertEqual(
            [x for x, y in book_state.fields],
            ["id", "user"]
        )

    @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
    def test_name_match(self):
        "Tests prefix name matching"
+3 −1
Original line number Diff line number Diff line
@@ -3,7 +3,9 @@ from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [("migrations", "0001_initial")]
    dependencies = [
        ("migrations", "0001_initial"),
    ]

    operations = [

Loading