From 45fa5201874df2883be003b7ed29ac14751431b8 Mon Sep 17 00:00:00 2001
From: Chris Beaven <smileychris@gmail.com>
Date: Thu, 29 May 2014 22:30:47 +1200
Subject: [PATCH] Implement Migration.run_before

This attribute (used for reverse dependencies) was previously declared
and mentioned in the code, but never actually used.
---
 django/db/migrations/loader.py                | 41 +++++++++++--------
 tests/migrations/test_loader.py               | 16 ++++++++
 .../0001_initial.py                           | 20 +++++++++
 .../test_migrations_run_before/0002_second.py | 23 +++++++++++
 .../test_migrations_run_before/0003_third.py  | 32 +++++++++++++++
 .../test_migrations_run_before/__init__.py    |  0
 6 files changed, 115 insertions(+), 17 deletions(-)
 create mode 100644 tests/migrations/test_migrations_run_before/0001_initial.py
 create mode 100644 tests/migrations/test_migrations_run_before/0002_second.py
 create mode 100644 tests/migrations/test_migrations_run_before/0003_third.py
 create mode 100644 tests/migrations/test_migrations_run_before/__init__.py

diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index 661f74ff97..bb41c46be1 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -134,6 +134,25 @@ class MigrationLoader(object):
         else:
             return self.disk_migrations[results[0]]
 
+    def check_key(self, key, current_app):
+        if key[1] != "__first__" or key in self.graph:
+            return key
+        # 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 key[0] == current_app:
+            # Ignore __first__ references to the same app (#22325)
+            return
+        if key[0] in self.unmigrated_apps:
+            # This app isn't migrated, but something depends on it.
+            # The models will get auto-added into the state, though
+            # so we're fine.
+            return
+        if key[0] in self.migrated_apps:
+            return list(self.graph.root_nodes(key[0]))[0]
+        raise ValueError("Dependency on unknown app %s" % key[0])
+
     def build_graph(self):
         """
         Builds a migration dependency graph using both the disk and database.
@@ -196,25 +215,13 @@ 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] == key[0]:
-                        # Ignore __first__ references to the same app (#22325)
-                        continue
-                    elif parent[0] in self.unmigrated_apps:
-                        # This app isn't migrated, but something depends on it.
-                        # The models will get auto-added into the state, though
-                        # so we're fine.
-                        continue
-                    elif parent[0] in self.migrated_apps:
-                        parent = list(self.graph.root_nodes(parent[0]))[0]
-                    else:
-                        raise ValueError("Dependency on unknown app %s" % parent[0])
+                parent = self.check_key(parent, key[0])
                 if parent is not None:
                     self.graph.add_dependency(key, parent)
+            for child in migration.run_before:
+                child = self.check_key(child, key[0])
+                if child is not None:
+                    self.graph.add_dependency(child, key)
 
     def detect_conflicts(self):
         """
diff --git a/tests/migrations/test_loader.py b/tests/migrations/test_loader.py
index 34bc340495..5f307a1816 100644
--- a/tests/migrations/test_loader.py
+++ b/tests/migrations/test_loader.py
@@ -102,6 +102,22 @@ class LoaderTests(TestCase):
             ["id", "user"]
         )
 
+    @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_run_before"})
+    def test_run_before(self):
+        """
+        Makes sure the loader uses Migration.run_before.
+        """
+        # Load and test the plan
+        migration_loader = MigrationLoader(connection)
+        self.assertEqual(
+            migration_loader.graph.forwards_plan(("migrations", "0002_second")),
+            [
+                ("migrations", "0001_initial"),
+                ("migrations", "0003_third"),
+                ("migrations", "0002_second"),
+            ],
+        )
+
     @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
     def test_name_match(self):
         "Tests prefix name matching"
diff --git a/tests/migrations/test_migrations_run_before/0001_initial.py b/tests/migrations/test_migrations_run_before/0001_initial.py
new file mode 100644
index 0000000000..7d44a8d789
--- /dev/null
+++ b/tests/migrations/test_migrations_run_before/0001_initial.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    operations = [
+
+        migrations.CreateModel(
+            "Salamander",
+            [
+                ("id", models.AutoField(primary_key=True)),
+                ("size", models.IntegerField(default=0)),
+                ("silly_field", models.BooleanField(default=False)),
+            ],
+        ),
+
+    ]
diff --git a/tests/migrations/test_migrations_run_before/0002_second.py b/tests/migrations/test_migrations_run_before/0002_second.py
new file mode 100644
index 0000000000..c0e03b4620
--- /dev/null
+++ b/tests/migrations/test_migrations_run_before/0002_second.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("migrations", "0001_initial"),
+    ]
+
+    operations = [
+
+        migrations.CreateModel(
+            "Book",
+            [
+                ("id", models.AutoField(primary_key=True)),
+                ("author", models.ForeignKey("migrations.Author", null=True)),
+            ],
+        )
+
+    ]
diff --git a/tests/migrations/test_migrations_run_before/0003_third.py b/tests/migrations/test_migrations_run_before/0003_third.py
new file mode 100644
index 0000000000..d5c098c730
--- /dev/null
+++ b/tests/migrations/test_migrations_run_before/0003_third.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    """
+    This is a wee bit crazy, but it's just to show that run_before works.
+    """
+
+    dependencies = [
+        ("migrations", "0001_initial"),
+    ]
+
+    run_before = [
+        ("migrations", "0002_second"),
+    ]
+
+    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)),
+            ],
+        )
+
+    ]
diff --git a/tests/migrations/test_migrations_run_before/__init__.py b/tests/migrations/test_migrations_run_before/__init__.py
new file mode 100644
index 0000000000..e69de29bb2