summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2013-05-10 16:09:57 +0100
committerAndrew Godwin <andrew@aeracode.org>2013-05-10 16:09:57 +0100
commit8a1f0177778275d8ee4707ef0dca79553b03a035 (patch)
tree45d6622e5a607d09851cd568aba6fa10932987a8
parent9ce83546720b9536c02817e802c9376eb74f811d (diff)
Add root_node and leaf_node functions to MigrationGraph
-rw-r--r--django/db/migrations/graph.py30
-rw-r--r--tests/migrations/tests.py18
2 files changed, 46 insertions, 2 deletions
diff --git a/django/db/migrations/graph.py b/django/db/migrations/graph.py
index 08481869f4..8d23b36cb7 100644
--- a/django/db/migrations/graph.py
+++ b/django/db/migrations/graph.py
@@ -19,8 +19,9 @@ class MigrationGraph(object):
replacing migration, and repoint any dependencies that pointed to the
replaced migrations to point to the replacing one.
- A node should be a tuple: (app_path, migration_name) - but the code
- here doesn't really care.
+ A node should be a tuple: (app_path, migration_name). The tree special-cases
+ things within an app - namely, root nodes and leaf nodes ignore dependencies
+ to other apps.
"""
def __init__(self):
@@ -59,6 +60,31 @@ 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):
+ """
+ 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 filter(lambda key: key[0] == node[0], self.dependencies.get(node, set())):
+ roots.add(node)
+ return roots
+
+ def leaf_nodes(self):
+ """
+ Returns all leaf nodes - that is, nodes with no dependents in their app.
+ These are the "most current" version of an app's schema.
+ Having more than one per app is technically an error, but one that
+ gets handled further up, in the interactive command - it's usually the
+ result of a VCS merge and needs some user input.
+ """
+ leaves = set()
+ for node in self.nodes:
+ if not filter(lambda key: key[0] == node[0], self.dependents.get(node, set())):
+ leaves.add(node)
+ return leaves
+
def dfs(self, start, get_children):
"""
Dynamic programming based depth first search, for finding dependencies.
diff --git a/tests/migrations/tests.py b/tests/migrations/tests.py
index 56f4c9fdb9..338e28aa56 100644
--- a/tests/migrations/tests.py
+++ b/tests/migrations/tests.py
@@ -44,6 +44,15 @@ class GraphTests(TransactionTestCase):
graph.backwards_plan(("app_b", "0002")),
[('app_a', '0004'), ('app_a', '0003'), ('app_b', '0002')],
)
+ # Test roots and leaves
+ self.assertEqual(
+ graph.root_nodes(),
+ set([('app_a', '0001'), ('app_b', '0001')]),
+ )
+ self.assertEqual(
+ graph.leaf_nodes(),
+ set([('app_a', '0004'), ('app_b', '0002')]),
+ )
def test_complex_graph(self):
"""
@@ -81,6 +90,15 @@ class GraphTests(TransactionTestCase):
graph.backwards_plan(("app_b", "0001")),
[('app_a', '0004'), ('app_c', '0002'), ('app_c', '0001'), ('app_a', '0003'), ('app_b', '0002'), ('app_b', '0001')],
)
+ # Test roots and leaves
+ self.assertEqual(
+ graph.root_nodes(),
+ set([('app_a', '0001'), ('app_b', '0001'), ('app_c', '0001')]),
+ )
+ self.assertEqual(
+ graph.leaf_nodes(),
+ set([('app_a', '0004'), ('app_b', '0002'), ('app_c', '0002')]),
+ )
def test_circular_graph(self):
"""