Fixed #22861: Internal migrations done first so __first__ works

Thanks to Chris Beaven.
This commit is contained in:
Andrew Godwin 2014-06-17 23:27:03 -07:00
parent 09b63a7cce
commit 2cee1d4642
10 changed files with 145 additions and 6 deletions

View File

@ -74,7 +74,7 @@ class MigrationGraph(object):
for node in self.nodes: for node in self.nodes:
if not any(key[0] == node[0] for key in self.dependencies.get(node, set())) and (not app or app == node[0]): 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) roots.add(node)
return roots return sorted(roots)
def leaf_nodes(self, app=None): def leaf_nodes(self, app=None):
""" """
@ -88,7 +88,7 @@ class MigrationGraph(object):
for node in self.nodes: for node in self.nodes:
if not any(key[0] == node[0] for key in self.dependents.get(node, set())) and (not app or app == node[0]): if not any(key[0] == node[0] for key in self.dependents.get(node, set())) and (not app or app == node[0]):
leaves.add(node) leaves.add(node)
return leaves return sorted(leaves)
def dfs(self, start, get_children): def dfs(self, start, get_children):
""" """

View File

@ -223,8 +223,19 @@ class MigrationLoader(object):
self.graph = MigrationGraph() self.graph = MigrationGraph()
for key, migration in normal.items(): for key, migration in normal.items():
self.graph.add_node(key, migration) self.graph.add_node(key, migration)
# Add all internal dependencies first to ensure __first__ dependencies
# find the correct root node.
for key, migration in normal.items(): for key, migration in normal.items():
for parent in migration.dependencies: for parent in migration.dependencies:
if parent[0] != key[0] or parent[1] == '__first__':
# Ignore __first__ references to the same app (#22325)
continue
self.graph.add_dependency(key, parent)
for key, migration in normal.items():
for parent in migration.dependencies:
if parent[0] == key[0]:
# Internal dependencies already added.
continue
parent = self.check_key(parent, key[0]) parent = self.check_key(parent, key[0])
if parent is not None: if parent is not None:
self.graph.add_dependency(key, parent) self.graph.add_dependency(key, parent)

View File

@ -51,11 +51,11 @@ class GraphTests(TestCase):
# Test roots and leaves # Test roots and leaves
self.assertEqual( self.assertEqual(
graph.root_nodes(), graph.root_nodes(),
set([('app_a', '0001'), ('app_b', '0001')]), [('app_a', '0001'), ('app_b', '0001')],
) )
self.assertEqual( self.assertEqual(
graph.leaf_nodes(), graph.leaf_nodes(),
set([('app_a', '0004'), ('app_b', '0002')]), [('app_a', '0004'), ('app_b', '0002')],
) )
def test_complex_graph(self): def test_complex_graph(self):
@ -105,11 +105,11 @@ class GraphTests(TestCase):
# Test roots and leaves # Test roots and leaves
self.assertEqual( self.assertEqual(
graph.root_nodes(), graph.root_nodes(),
set([('app_a', '0001'), ('app_b', '0001'), ('app_c', '0001')]), [('app_a', '0001'), ('app_b', '0001'), ('app_c', '0001')],
) )
self.assertEqual( self.assertEqual(
graph.leaf_nodes(), graph.leaf_nodes(),
set([('app_a', '0004'), ('app_b', '0002'), ('app_c', '0002')]), [('app_a', '0004'), ('app_b', '0002'), ('app_c', '0002')],
) )
def test_circular_graph(self): def test_circular_graph(self):

View File

@ -142,6 +142,26 @@ class LoaderTests(TestCase):
], ],
) )
@override_settings(MIGRATION_MODULES={
"migrations": "migrations.test_migrations_first",
"migrations2": "migrations2.test_migrations_2_first",
})
@modify_settings(INSTALLED_APPS={'append': 'migrations2'})
def test_first(self):
"""
Makes sure the '__first__' migrations build correctly.
"""
migration_loader = MigrationLoader(connection)
self.assertEqual(
migration_loader.graph.forwards_plan(("migrations", "second")),
[
("migrations", "thefirst"),
("migrations2", "0001_initial"),
("migrations2", "0002_second"),
("migrations", "second"),
],
)
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
def test_name_match(self): def test_name_match(self):
"Tests prefix name matching" "Tests prefix name matching"

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("migrations", "thefirst"),
("migrations2", "0002_second"),
]
operations = [
migrations.DeleteModel("Tribble"),
migrations.RemoveField("Author", "silly_field"),
migrations.AddField("Author", "rating", models.IntegerField(default=0)),
migrations.CreateModel(
"Book",
[
("id", models.AutoField(primary_key=True)),
("author", models.ForeignKey("migrations.Author", null=True)),
],
)
]

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
"Author",
[
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=255)),
("slug", models.SlugField(null=True)),
("age", models.IntegerField(default=0)),
("silly_field", models.BooleanField(default=False)),
],
),
migrations.CreateModel(
"Tribble",
[
("id", models.AutoField(primary_key=True)),
("fluffy", models.BooleanField(default=True)),
],
)
]

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("migrations", "__first__"),
]
operations = [
migrations.CreateModel(
"OtherAuthor",
[
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=255)),
("slug", models.SlugField(null=True)),
("age", models.IntegerField(default=0)),
("silly_field", models.BooleanField(default=False)),
],
),
]

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("migrations2", "0001_initial")]
operations = [
migrations.CreateModel(
"Bookstore",
[
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=255)),
("slug", models.SlugField(null=True)),
],
),
]