From 29341aaffc28866e15e11109b26c6446f39cb68d Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Thu, 15 Apr 2010 18:44:51 +0000 Subject: [PATCH] Fixed #13348: Restored ability to load models from apps in eggs. Thanks 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 --- django/db/models/loading.py | 30 ++++++----- django/utils/module_loading.py | 27 ++++++++++ .../app_loading/eggs/brokenapp.egg | Bin 0 -> 1605 bytes .../app_loading/eggs/modelapp.egg | Bin 0 -> 3347 bytes .../app_loading/eggs/nomodelapp.egg | Bin 0 -> 1211 bytes .../app_loading/eggs/omelet.egg | Bin 0 -> 9020 bytes tests/regressiontests/app_loading/tests.py | 50 ++++++++++++++++++ 7 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 django/utils/module_loading.py create mode 100755 tests/regressiontests/app_loading/eggs/brokenapp.egg create mode 100755 tests/regressiontests/app_loading/eggs/modelapp.egg create mode 100755 tests/regressiontests/app_loading/eggs/nomodelapp.egg create mode 100755 tests/regressiontests/app_loading/eggs/omelet.egg diff --git a/django/db/models/loading.py b/django/db/models/loading.py index d9c338f15d..620cebc25c 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -4,6 +4,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.datastructures import SortedDict from django.utils.importlib import import_module +from django.utils.module_loading import module_has_submodule import imp import sys @@ -73,25 +74,28 @@ class AppCache(object): self.handled[app_name] = None self.nesting_level += 1 app_module = import_module(app_name) - try: - imp.find_module('models', app_module.__path__) - except ImportError: - self.nesting_level -= 1 - # App has no models module, that's not a problem. - return None try: models = import_module('.models', app_name) except ImportError: self.nesting_level -= 1 - if can_postpone: - # Either the app has an error, or the package is still being - # imported by Python and the model module isn't available yet. - # We will check again once all the recursion has finished (in - # populate). - self.postponed.append(app_name) + # If the app doesn't have a models module, we can just ignore the + # ImportError and return no models for it. + if not module_has_submodule(app_module, 'models'): return None + # But if the app does have a models module, we need to figure out + # whether to suppress or propagate the error. If can_postpone is + # True then it may be that the package is still being imported by + # Python and the models module isn't available yet. So we add the + # app to the postponed list and we'll try it again after all the + # recursion has finished (in populate). If can_postpone is False + # then it's time to raise the ImportError. else: - raise + if can_postpone: + self.postponed.append(app_name) + return None + else: + raise + self.nesting_level -= 1 if models not in self.app_store: self.app_store[models] = len(self.app_store) diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py new file mode 100644 index 0000000000..b23336e9df --- /dev/null +++ b/django/utils/module_loading.py @@ -0,0 +1,27 @@ +import os +import imp + +def module_has_submodule(mod, submod_name): + # If the module was loaded from an egg, __loader__ will be set and + # its find_module must be used to search for submodules. + loader = getattr(mod, '__loader__', None) + if loader: + mod_path = "%s.%s" % (mod.__name__, submod_name) + mod_path = mod_path[len(loader.prefix):] + x = loader.find_module(mod_path) + if x is None: + # zipimport.zipimporter.find_module is documented to take + # dotted paths but in fact through Pyton 2.7 is observed + # to require os.sep in place of dots...so try using os.sep + # if the dotted path version failed to find the requested + # submodule. + x = loader.find_module(mod_path.replace('.', os.sep)) + return x is not None + + try: + imp.find_module(submod_name, mod.__path__) + return True + except ImportError: + return False + + diff --git a/tests/regressiontests/app_loading/eggs/brokenapp.egg b/tests/regressiontests/app_loading/eggs/brokenapp.egg new file mode 100755 index 0000000000000000000000000000000000000000..8aca67159811faefec727659fb75633b817a3cd7 GIT binary patch literal 1605 zcmWIWW@Zs#U|`^2h%W52*_Zxwkphq>2gD%Jq@w)n)V%n_f&%^A{FKz3V!eV&Z|zg( zLp@G`9402; z{?j?#yZ`HqawBFD1Llp)0^;2aogxZJDJRkrP9&yuB&8(yBqniOV_13O4?l}kk3$%z z^aO~FpDrgbaTYY0q^Ukz`uz1$71qWxR~%Hkwl=r~Wv2Og`2=~T6?Vo|rp?=&i#svF08|G>1)QyKck`bPyvD%L1{hySuKZpPRpa zN@_uBUP@|Sa%Fr@W?pu&UP(pCWAw<}eSZ6ga-gpLKrD!?JHQ)a%x7O+PajVm&nvv% zx?1PXoZlQ|aK-q+(@)-Ke76R9Xq`Imch*}+seF28&-&`D+7iU^_4BvS{NEox z{dD5I?^Rt7AAeV&VjbO+C-e^qT<|^Xd*+Oz(6^I5erL7auDzTTH790DWB}7PK|QGn zlOrZgi=Wc1!h+^U_Arag44~_F05K>L!~GcS9~$KB8VvH9wx6!1?gi}?PrSg6>h{;s zxX|B!{>*8e&VF~{bD+em>Bhly^b0VoON~EY`t+4~&)rhnzjyv@JjwdIoy~GL+v}+z zQxYcodFpzc_4YjLy{_l{$up<*jxAlMvR}bYeXEWs`w|8;m$k5DUE>A1h#QDOi3#qq zlKg`BoYb<^9FW_*;ZAyh9s$VFhY|r*nFYGViD{|eC}U(20ec*Ko(9?r2HP4z6jFMI z=kNe;gbCR4FhoBC!?wmC93~_q8-XnwA&d|LCPgGiAZ03KGq7bUgc%+<%|LMo$PnZZ z1Z4mi*w*+EXb3z)uw@5y_n_wngi-Rq5(91&EN`G|M^B~*?MIQdBPCjN6VQ_r!i0EW s9)~*sC2^sffgB#76a@p@8W$j&0ZU&2-mGjOC7eJQ3v}isP+`IV0GA5caR2}S literal 0 HcmV?d00001 diff --git a/tests/regressiontests/app_loading/eggs/modelapp.egg b/tests/regressiontests/app_loading/eggs/modelapp.egg new file mode 100755 index 0000000000000000000000000000000000000000..c2370b5b4cf7aacc9918cc800aaca3c7282e6e91 GIT binary patch literal 3347 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