Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13348: Restored ability to load models from apps in eggs. Than…

…ks Ramiro and metzen for pointers on how to find out if a module loaded from an egg has a particular submodule.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12982 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 29341aaffc28866e15e11109b26c6446f39cb68d 1 parent 9ac4d2f
Karen Tracey authored
30  django/db/models/loading.py
@@ -4,6 +4,7 @@
4 4
 from django.core.exceptions import ImproperlyConfigured
5 5
 from django.utils.datastructures import SortedDict
6 6
 from django.utils.importlib import import_module
  7
+from django.utils.module_loading import module_has_submodule
7 8
 
8 9
 import imp
9 10
 import sys
@@ -74,24 +75,27 @@ def load_app(self, app_name, can_postpone=False):
74 75
         self.nesting_level += 1
75 76
         app_module = import_module(app_name)
76 77
         try:
77  
-            imp.find_module('models', app_module.__path__)
78  
-        except ImportError: 
79  
-            self.nesting_level -= 1
80  
-            # App has no models module, that's not a problem.
81  
-            return None
82  
-        try:
83 78
             models = import_module('.models', app_name)
84 79
         except ImportError:
85 80
             self.nesting_level -= 1
86  
-            if can_postpone:
87  
-                # Either the app has an error, or the package is still being
88  
-                # imported by Python and the model module isn't available yet.
89  
-                # We will check again once all the recursion has finished (in
90  
-                # populate).
91  
-                self.postponed.append(app_name)
  81
+            # If the app doesn't have a models module, we can just ignore the
  82
+            # ImportError and return no models for it.
  83
+            if not module_has_submodule(app_module, 'models'):
92 84
                 return None
  85
+            # But if the app does have a models module, we need to figure out
  86
+            # whether to suppress or propagate the error. If can_postpone is
  87
+            # True then it may be that the package is still being imported by
  88
+            # Python and the models module isn't available yet. So we add the
  89
+            # app to the postponed list and we'll try it again after all the
  90
+            # recursion has finished (in populate). If can_postpone is False
  91
+            # then it's time to raise the ImportError.
93 92
             else:
94  
-                raise
  93
+                if can_postpone:
  94
+                    self.postponed.append(app_name)
  95
+                    return None
  96
+                else:
  97
+                    raise
  98
+
95 99
         self.nesting_level -= 1
96 100
         if models not in self.app_store:
97 101
             self.app_store[models] = len(self.app_store)
27  django/utils/module_loading.py
... ...
@@ -0,0 +1,27 @@
  1
+import os
  2
+import imp
  3
+
  4
+def module_has_submodule(mod, submod_name):
  5
+    # If the module was loaded from an egg, __loader__ will be set and 
  6
+    # its find_module must be used to search for submodules.
  7
+    loader = getattr(mod, '__loader__', None)
  8
+    if loader:
  9
+        mod_path = "%s.%s" % (mod.__name__, submod_name)
  10
+        mod_path = mod_path[len(loader.prefix):]
  11
+        x = loader.find_module(mod_path)
  12
+        if x is None:
  13
+            # zipimport.zipimporter.find_module is documented to take
  14
+            # dotted paths but in fact through Pyton 2.7 is observed 
  15
+            # to require os.sep in place of dots...so try using os.sep
  16
+            # if the dotted path version failed to find the requested 
  17
+            # submodule.
  18
+            x = loader.find_module(mod_path.replace('.', os.sep))
  19
+        return x is not None
  20
+
  21
+    try:
  22
+        imp.find_module(submod_name, mod.__path__)
  23
+        return True
  24
+    except ImportError:
  25
+        return False
  26
+
  27
+
BIN  tests/regressiontests/app_loading/eggs/brokenapp.egg
Binary file not shown
BIN  tests/regressiontests/app_loading/eggs/modelapp.egg
Binary file not shown
BIN  tests/regressiontests/app_loading/eggs/nomodelapp.egg
Binary file not shown
BIN  tests/regressiontests/app_loading/eggs/omelet.egg
Binary file not shown
50  tests/regressiontests/app_loading/tests.py
... ...
@@ -1,8 +1,10 @@
1 1
 import os
2 2
 import sys
3 3
 import time
  4
+from unittest import TestCase
4 5
 
5 6
 from django.conf import Settings
  7
+from django.db.models.loading import load_app
6 8
 
7 9
 __test__ = {"API_TESTS": """
8 10
 Test the globbing of INSTALLED_APPS.
@@ -25,3 +27,51 @@
25 27
 
26 28
 """}
27 29
 
  30
+class EggLoadingTest(TestCase):
  31
+
  32
+    def setUp(self):
  33
+        self.old_path = sys.path
  34
+        self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
  35
+
  36
+    def tearDown(self):
  37
+        sys.path = self.old_path
  38
+
  39
+    def test_egg1(self):
  40
+        """Models module can be loaded from an app in an egg"""
  41
+        egg_name = '%s/modelapp.egg' % self.egg_dir
  42
+        sys.path.append(egg_name)
  43
+        models = load_app('app_with_models')
  44
+        self.failIf(models is None)
  45
+
  46
+    def test_egg2(self):
  47
+        """Loading an app from an egg that has no models returns no models (and no error)"""
  48
+        egg_name = '%s/nomodelapp.egg' % self.egg_dir
  49
+        sys.path.append(egg_name)
  50
+        models = load_app('app_no_models')
  51
+        self.failUnless(models is None)
  52
+
  53
+    def test_egg3(self):
  54
+        """Models module can be loaded from an app located under an egg's top-level package"""
  55
+        egg_name = '%s/omelet.egg' % self.egg_dir
  56
+        sys.path.append(egg_name)
  57
+        models = load_app('omelet.app_with_models')
  58
+        self.failIf(models is None)
  59
+
  60
+    def test_egg4(self):
  61
+        """Loading an app with no models from under the top-level egg package generates no error"""
  62
+        egg_name = '%s/omelet.egg' % self.egg_dir
  63
+        sys.path.append(egg_name)
  64
+        models = load_app('omelet.app_no_models')
  65
+        self.failUnless(models is None)
  66
+ 
  67
+    def test_egg5(self):
  68
+        """Loading an app from an egg that has an import error in its models module raises that error"""
  69
+        egg_name = '%s/brokenapp.egg' % self.egg_dir
  70
+        sys.path.append(egg_name)
  71
+        self.assertRaises(ImportError, load_app, 'broken_app')
  72
+        try:
  73
+            load_app('broken_app')
  74
+        except ImportError, e:
  75
+            # Make sure the message is indicating the actual
  76
+            # problem in the broken app.
  77
+            self.failUnless("modelz" in e.args[0])

0 notes on commit 29341aa

Please sign in to comment.
Something went wrong with that request. Please try again.