Fixed #21060 -- Refactored admin's autodiscover method to make it reusable.

We want to be able to use it for instance for discovering `tasks.py` modules
inside the INSTALLED_APPS.

This commit therefore moves the logic to `autodiscover_modules` method in
django.utils.module_loading.
This commit is contained in:
Juan Catalano 2013-09-06 20:23:25 -03:00 committed by Tim Graham
parent 39b49fd339
commit 6feb75129f
6 changed files with 82 additions and 30 deletions

View File

@ -7,35 +7,8 @@ from django.contrib.admin.sites import AdminSite, site
from django.contrib.admin.filters import (ListFilter, SimpleListFilter, from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter, FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter) ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
from django.utils.module_loading import autodiscover_modules
def autodiscover(): def autodiscover():
""" autodiscover_modules('admin', register_to=site)
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
"""
import copy
from importlib import import_module
from django.conf import settings
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
before_import_registry = copy.copy(site._registry)
import_module('%s.admin' % app)
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
# (see #8245).
site._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'admin'):
raise

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import # Avoid importing `importlib` from this package. from __future__ import absolute_import # Avoid importing `importlib` from this package.
import copy
import imp import imp
from importlib import import_module from importlib import import_module
import os import os
@ -34,6 +35,43 @@ def import_by_path(dotted_path, error_prefix=''):
return attr return attr
def autodiscover_modules(*args, **kwargs):
"""
Auto-discover INSTALLED_APPS modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
You may provide a register_to keyword parameter as a way to access a
registry. This register_to object must have a _registry instance variable
to access it.
"""
from django.conf import settings
register_to = kwargs.get('register_to')
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's module.
try:
if register_to:
before_import_registry = copy.copy(register_to._registry)
for module_to_search in args:
import_module('%s.%s' % (app, module_to_search))
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
# (see #8245).
if register_to:
register_to._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, module_to_search):
raise
def module_has_submodule(package, module_name): def module_has_submodule(package, module_name):
"""See if 'module' is in 'package'.""" """See if 'module' is in 'package'."""
name = ".".join([package.__name__, module_name]) name = ".".join([package.__name__, module_name])

View File

@ -0,0 +1,3 @@
class SiteMock(object):
_registry = {}
site = SiteMock()

View File

@ -0,0 +1,8 @@
from . import site
content = 'Another Bad Module'
site._registry.update({
'foo': 'bar',
})
raise Exception('Some random exception.')

View File

@ -0,0 +1 @@
content = 'Another Good Module'

View File

@ -6,7 +6,10 @@ import unittest
from zipimport import zipimporter from zipimport import zipimporter
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import import_by_path, module_has_submodule from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.utils import six
from django.utils.module_loading import autodiscover_modules, import_by_path, module_has_submodule
from django.utils._os import upath from django.utils._os import upath
@ -130,6 +133,32 @@ class ModuleImportTestCase(unittest.TestCase):
self.assertIsNotNone(traceback.tb_next.tb_next, self.assertIsNotNone(traceback.tb_next.tb_next,
'Should have more than the calling frame in the traceback.') 'Should have more than the calling frame in the traceback.')
@override_settings(INSTALLED_APPS=('utils_tests.test_module',))
class AutodiscoverModulesTestCase(SimpleTestCase):
def test_autodiscover_modules_found(self):
autodiscover_modules('good_module')
def test_autodiscover_modules_not_found(self):
autodiscover_modules('missing_module')
def test_autodiscover_modules_found_but_bad_module(self):
with six.assertRaisesRegex(self, ImportError, "No module named '?a_package_name_that_does_not_exist'?"):
autodiscover_modules('bad_module')
def test_autodiscover_modules_several_one_bad_module(self):
with six.assertRaisesRegex(self, ImportError, "No module named '?a_package_name_that_does_not_exist'?"):
autodiscover_modules('good_module', 'bad_module')
def test_autodiscover_modules_several_found(self):
autodiscover_modules('good_module', 'another_good_module')
def test_validate_registry_keeps_intact(self):
from .test_module import site
with six.assertRaisesRegex(self, Exception, "Some random exception."):
autodiscover_modules('another_bad_module', register_to=site)
self.assertEqual(site._registry, {})
class ProxyFinder(object): class ProxyFinder(object):
def __init__(self): def __init__(self):