From 584110417f6a3c16cc0c6723825a21940a9a2bc4 Mon Sep 17 00:00:00 2001
From: Loic Bistuer <loic.bistuer@sixmedia.com>
Date: Sat, 19 Oct 2013 07:24:38 +0700
Subject: [PATCH] Fixed #21283 -- Added support for migrations if models is a
 package.

Thanks Markus Holtermann for the report.
---
 django/db/migrations/loader.py                |  4 +--
 django/db/migrations/writer.py                | 18 ++++++-----
 django/db/models/loading.py                   |  7 +++++
 .../migrations_test_apps/__init__.py          |  0
 .../migrations_test_apps/normal/__init__.py   |  0
 .../migrations_test_apps/normal/models.py     |  0
 .../with_package_model/__init__.py            |  0
 .../with_package_model/models/__init__.py     |  0
 tests/migrations/test_writer.py               | 30 +++++++++++++++++--
 9 files changed, 46 insertions(+), 13 deletions(-)
 create mode 100644 tests/migrations/migrations_test_apps/__init__.py
 create mode 100644 tests/migrations/migrations_test_apps/normal/__init__.py
 create mode 100644 tests/migrations/migrations_test_apps/normal/models.py
 create mode 100644 tests/migrations/migrations_test_apps/with_package_model/__init__.py
 create mode 100644 tests/migrations/migrations_test_apps/with_package_model/models/__init__.py

diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index b40584981d..c9be3841b9 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -41,8 +41,8 @@ class MigrationLoader(object):
     def migrations_module(cls, app_label):
         if app_label in settings.MIGRATION_MODULES:
             return settings.MIGRATION_MODULES[app_label]
-        app = cache.get_app(app_label)
-        return ".".join(app.__name__.split(".")[:-1] + ["migrations"])
+        else:
+            return '%s.migrations' % cache.get_app_package(app_label)
 
     def load_disk(self):
         """
diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py
index beb1b30aba..333f39d6e0 100644
--- a/django/db/migrations/writer.py
+++ b/django/db/migrations/writer.py
@@ -61,20 +61,22 @@ class MigrationWriter(object):
 
     @property
     def path(self):
-        migrations_module_name = MigrationLoader.migrations_module(self.migration.app_label)
-        app_module = cache.get_app(self.migration.app_label)
+        migrations_package_name = MigrationLoader.migrations_module(self.migration.app_label)
         # See if we can import the migrations module directly
         try:
-            migrations_module = import_module(migrations_module_name)
+            migrations_module = import_module(migrations_package_name)
             basedir = os.path.dirname(migrations_module.__file__)
         except ImportError:
+            app = cache.get_app(self.migration.app_label)
+            app_path = cache._get_app_path(app)
+            app_package_name = cache._get_app_package(app)
+            migrations_package_basename = migrations_package_name.split(".")[-1]
+
             # Alright, see if it's a direct submodule of the app
-            oneup = ".".join(migrations_module_name.split(".")[:-1])
-            app_oneup = ".".join(app_module.__name__.split(".")[:-1])
-            if oneup == app_oneup:
-                basedir = os.path.join(os.path.dirname(app_module.__file__), migrations_module_name.split(".")[-1])
+            if '%s.%s' % (app_package_name, migrations_package_basename) == migrations_package_name:
+                basedir = os.path.join(app_path, migrations_package_basename)
             else:
-                raise ImportError("Cannot open migrations module %s for app %s" % (migrations_module_name, self.migration.app_label))
+                raise ImportError("Cannot open migrations module %s for app %s" % (migrations_package_name, self.migration.app_label))
         return os.path.join(basedir, self.filename)
 
     @classmethod
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index 2858b8b699..162f334846 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -185,6 +185,12 @@ class BaseAppCache(object):
 
         return [elt[0] for elt in apps]
 
+    def _get_app_package(self, app):
+        return '.'.join(app.__name__.split('.')[:-1])
+
+    def get_app_package(self, app_label):
+        return self._get_app_package(self.get_app(app_label))
+
     def _get_app_path(self, app):
         if hasattr(app, '__path__'):        # models/__init__.py package
             app_path = app.__path__[0]
@@ -380,6 +386,7 @@ cache = AppCache()
 # These methods were always module level, so are kept that way for backwards
 # compatibility.
 get_apps = cache.get_apps
+get_app_package = cache.get_app_package
 get_app_path = cache.get_app_path
 get_app_paths = cache.get_app_paths
 get_app = cache.get_app
diff --git a/tests/migrations/migrations_test_apps/__init__.py b/tests/migrations/migrations_test_apps/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/migrations/migrations_test_apps/normal/__init__.py b/tests/migrations/migrations_test_apps/normal/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/migrations/migrations_test_apps/normal/models.py b/tests/migrations/migrations_test_apps/normal/models.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/migrations/migrations_test_apps/with_package_model/__init__.py b/tests/migrations/migrations_test_apps/with_package_model/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/migrations/migrations_test_apps/with_package_model/models/__init__.py b/tests/migrations/migrations_test_apps/with_package_model/models/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py
index bf9f55aff0..dbee0d1130 100644
--- a/tests/migrations/test_writer.py
+++ b/tests/migrations/test_writer.py
@@ -2,12 +2,15 @@
 
 from __future__ import unicode_literals
 
+import copy
 import datetime
+import os
 
-from django.utils import six
-from django.test import TestCase
-from django.db.migrations.writer import MigrationWriter
 from django.db import models, migrations
+from django.db.migrations.writer import MigrationWriter
+from django.db.models.loading import cache
+from django.test import TestCase, override_settings
+from django.utils import six
 from django.utils.translation import ugettext_lazy as _
 
 
@@ -95,3 +98,24 @@ class WriterTests(TestCase):
         # Just make sure it runs for now, and that things look alright.
         result = self.safe_exec(output)
         self.assertIn("Migration", result)
+
+    def test_migration_path(self):
+        _old_app_store = copy.deepcopy(cache.app_store)
+
+        test_apps = [
+            'migrations.migrations_test_apps.normal',
+            'migrations.migrations_test_apps.with_package_model',
+        ]
+
+        base_dir = os.path.dirname(os.path.dirname(__file__))
+
+        try:
+            with override_settings(INSTALLED_APPS=test_apps):
+                for app in test_apps:
+                    cache.load_app(app)
+                    migration = migrations.Migration('0001_initial', app.split('.')[-1])
+                    expected_path = os.path.join(base_dir, *(app.split('.') + ['migrations', '0001_initial.py']))
+                    writer = MigrationWriter(migration)
+                    self.assertEqual(writer.path, expected_path)
+        finally:
+            cache.app_store = _old_app_store