Loading django/db/migrations/autodetector.py +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading django/db/migrations/graph.py +5 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): """ Loading django/db/migrations/loader.py +35 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading tests/migrations/test_loader.py +28 −1 Original line number Diff line number Diff line Loading @@ -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")) Loading @@ -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" Loading tests/migrations/test_migrations/0002_second.py +3 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,9 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [("migrations", "0001_initial")] dependencies = [ ("migrations", "0001_initial"), ] operations = [ Loading Loading
django/db/migrations/autodetector.py +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
django/db/migrations/graph.py +5 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): """ Loading
django/db/migrations/loader.py +35 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading
tests/migrations/test_loader.py +28 −1 Original line number Diff line number Diff line Loading @@ -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")) Loading @@ -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" Loading
tests/migrations/test_migrations/0002_second.py +3 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,9 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [("migrations", "0001_initial")] dependencies = [ ("migrations", "0001_initial"), ] operations = [ Loading