Code

Ticket #17061: 17061-1.diff

File 17061-1.diff, 36.4 KB (added by claudep, 18 months ago)
Line 
1diff --git a/django/conf/__init__.py b/django/conf/__init__.py
2index 4e6f0f9..01624c9 100644
3--- a/django/conf/__init__.py
4+++ b/django/conf/__init__.py
5@@ -14,6 +14,7 @@ from django.conf import global_settings
6 from django.core.exceptions import ImproperlyConfigured
7 from django.utils.functional import LazyObject, empty
8 from django.utils import importlib
9+from django.utils.module_loading import import_module_from_path
10 from django.utils import six
11 
12 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
13@@ -58,9 +59,7 @@ class LazySettings(LazyObject):
14         if self.LOGGING_CONFIG:
15             from django.utils.log import DEFAULT_LOGGING
16             # First find the logging configuration function ...
17-            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
18-            logging_config_module = importlib.import_module(logging_config_path)
19-            logging_config_func = getattr(logging_config_module, logging_config_func_name)
20+            logging_config_func = import_module_from_path(self.LOGGING_CONFIG)
21 
22             logging_config_func(DEFAULT_LOGGING)
23 
24diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py
25index 7c62c1a..9f20e59 100644
26--- a/django/contrib/admin/tests.py
27+++ b/django/contrib/admin/tests.py
28@@ -1,5 +1,5 @@
29 from django.test import LiveServerTestCase
30-from django.utils.importlib import import_module
31+from django.utils.module_loading import import_module_from_path
32 from django.utils.unittest import SkipTest
33 from django.utils.translation import ugettext as _
34 
35@@ -9,11 +9,7 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
36     @classmethod
37     def setUpClass(cls):
38         try:
39-            # Import and start the WebDriver class.
40-            module, attr = cls.webdriver_class.rsplit('.', 1)
41-            mod = import_module(module)
42-            WebDriver = getattr(mod, attr)
43-            cls.selenium = WebDriver()
44+            cls.selenium = import_module_from_path(cls.webdriver_class)()
45         except Exception as e:
46             raise SkipTest('Selenium webdriver "%s" not installed or not '
47                            'operational: %s' % (cls.webdriver_class, str(e)))
48diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
49index dd4a848..e6ae03d 100644
50--- a/django/contrib/auth/__init__.py
51+++ b/django/contrib/auth/__init__.py
52@@ -1,8 +1,8 @@
53 import re
54 
55-from django.core.exceptions import ImproperlyConfigured
56-from django.utils.importlib import import_module
57 from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
58+from django.core.exceptions import ImproperlyConfigured
59+from django.utils.module_loading import import_module_from_path
60 
61 SESSION_KEY = '_auth_user_id'
62 BACKEND_SESSION_KEY = '_auth_user_backend'
63@@ -10,19 +10,7 @@ REDIRECT_FIELD_NAME = 'next'
64 
65 
66 def load_backend(path):
67-    i = path.rfind('.')
68-    module, attr = path[:i], path[i + 1:]
69-    try:
70-        mod = import_module(module)
71-    except ImportError as e:
72-        raise ImproperlyConfigured('Error importing authentication backend %s: "%s"' % (path, e))
73-    except ValueError:
74-        raise ImproperlyConfigured('Error importing authentication backends. Is AUTHENTICATION_BACKENDS a correctly defined list or tuple?')
75-    try:
76-        cls = getattr(mod, attr)
77-    except AttributeError:
78-        raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr))
79-    return cls()
80+    return import_module_from_path(path)()
81 
82 
83 def get_backends():
84diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
85index c628059..980cc72 100644
86--- a/django/contrib/auth/hashers.py
87+++ b/django/contrib/auth/hashers.py
88@@ -12,6 +12,7 @@ from django.utils.encoding import force_bytes
89 from django.core.exceptions import ImproperlyConfigured
90 from django.utils.crypto import (
91     pbkdf2, constant_time_compare, get_random_string)
92+from django.utils.module_loading import import_module_from_path
93 from django.utils.translation import ugettext_noop as _
94 
95 
96@@ -84,13 +85,7 @@ def load_hashers(password_hashers=None):
97     if not password_hashers:
98         password_hashers = settings.PASSWORD_HASHERS
99     for backend in password_hashers:
100-        try:
101-            mod_path, cls_name = backend.rsplit('.', 1)
102-            mod = importlib.import_module(mod_path)
103-            hasher_cls = getattr(mod, cls_name)
104-        except (AttributeError, ImportError, ValueError):
105-            raise ImproperlyConfigured("hasher not found: %s" % backend)
106-        hasher = hasher_cls()
107+        hasher = import_module_from_path(backend)()
108         if not getattr(hasher, 'algorithm'):
109             raise ImproperlyConfigured("hasher doesn't specify an "
110                                        "algorithm name: %s" % backend)
111diff --git a/django/contrib/formtools/tests/wizard/loadstorage.py b/django/contrib/formtools/tests/wizard/loadstorage.py
112index 267dee0..bb0b06e 100644
113--- a/django/contrib/formtools/tests/wizard/loadstorage.py
114+++ b/django/contrib/formtools/tests/wizard/loadstorage.py
115@@ -1,8 +1,6 @@
116 from django.test import TestCase
117 
118-from django.contrib.formtools.wizard.storage import (get_storage,
119-                                                     MissingStorageModule,
120-                                                     MissingStorageClass)
121+from django.contrib.formtools.wizard.storage import get_storage, MissingStorage
122 from django.contrib.formtools.wizard.storage.base import BaseStorage
123 
124 
125@@ -12,11 +10,9 @@ class TestLoadStorage(TestCase):
126             type(get_storage('django.contrib.formtools.wizard.storage.base.BaseStorage', 'wizard1')),
127             BaseStorage)
128 
129-    def test_missing_module(self):
130-        self.assertRaises(MissingStorageModule, get_storage,
131+    def test_missing_storage(self):
132+        self.assertRaises(MissingStorage, get_storage,
133             'django.contrib.formtools.wizard.storage.idontexist.IDontExistStorage', 'wizard1')
134-
135-    def test_missing_class(self):
136-        self.assertRaises(MissingStorageClass, get_storage,
137+        self.assertRaises(MissingStorage, get_storage,
138             'django.contrib.formtools.wizard.storage.base.IDontExistStorage', 'wizard1')
139 
140diff --git a/django/contrib/formtools/wizard/storage/__init__.py b/django/contrib/formtools/wizard/storage/__init__.py
141index f2293c9..b30d1dc 100644
142--- a/django/contrib/formtools/wizard/storage/__init__.py
143+++ b/django/contrib/formtools/wizard/storage/__init__.py
144@@ -1,22 +1,14 @@
145-from django.utils.importlib import import_module
146+from django.core.exceptions import ImproperlyConfigured
147+from django.utils.module_loading import import_module_from_path
148 
149 from django.contrib.formtools.wizard.storage.base import BaseStorage
150 from django.contrib.formtools.wizard.storage.exceptions import (
151-    MissingStorageModule, MissingStorageClass, NoFileStorageConfigured)
152+    MissingStorage, NoFileStorageConfigured)
153 
154 
155 def get_storage(path, *args, **kwargs):
156-    i = path.rfind('.')
157-    module, attr = path[:i], path[i+1:]
158     try:
159-        mod = import_module(module)
160-    except ImportError as e:
161-        raise MissingStorageModule(
162-            'Error loading storage %s: "%s"' % (module, e))
163-    try:
164-        storage_class = getattr(mod, attr)
165-    except AttributeError:
166-        raise MissingStorageClass(
167-            'Module "%s" does not define a storage named "%s"' % (module, attr))
168+        storage_class = import_module_from_path(path)
169+    except ImproperlyConfigured as e:
170+        raise MissingStorage('Error loading storage: %s' % e)
171     return storage_class(*args, **kwargs)
172-
173diff --git a/django/contrib/formtools/wizard/storage/exceptions.py b/django/contrib/formtools/wizard/storage/exceptions.py
174index eab9030..e273a86 100644
175--- a/django/contrib/formtools/wizard/storage/exceptions.py
176+++ b/django/contrib/formtools/wizard/storage/exceptions.py
177@@ -1,9 +1,6 @@
178 from django.core.exceptions import ImproperlyConfigured
179 
180-class MissingStorageModule(ImproperlyConfigured):
181-    pass
182-
183-class MissingStorageClass(ImproperlyConfigured):
184+class MissingStorage(ImproperlyConfigured):
185     pass
186 
187 class NoFileStorageConfigured(ImproperlyConfigured):
188diff --git a/django/contrib/messages/storage/__init__.py b/django/contrib/messages/storage/__init__.py
189index a584acc..8f3142b 100644
190--- a/django/contrib/messages/storage/__init__.py
191+++ b/django/contrib/messages/storage/__init__.py
192@@ -1,28 +1,5 @@
193 from django.conf import settings
194-from django.core.exceptions import ImproperlyConfigured
195-from django.utils.importlib import import_module
196-
197-
198-def get_storage(import_path):
199-    """
200-    Imports the message storage class described by import_path, where
201-    import_path is the full Python path to the class.
202-    """
203-    try:
204-        dot = import_path.rindex('.')
205-    except ValueError:
206-        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
207-    module, classname = import_path[:dot], import_path[dot + 1:]
208-    try:
209-        mod = import_module(module)
210-    except ImportError as e:
211-        raise ImproperlyConfigured('Error importing module %s: "%s"' %
212-                                   (module, e))
213-    try:
214-        return getattr(mod, classname)
215-    except AttributeError:
216-        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
217-                                   'class.' % (module, classname))
218+from django.utils.module_loading import import_module_from_path as get_storage
219 
220 
221 # Callable with the same interface as the storage classes i.e.  accepts a
222diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
223index 9b06c2c..685376e 100644
224--- a/django/contrib/staticfiles/finders.py
225+++ b/django/contrib/staticfiles/finders.py
226@@ -4,7 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
227 from django.core.files.storage import default_storage, Storage, FileSystemStorage
228 from django.utils.datastructures import SortedDict
229 from django.utils.functional import empty, memoize, LazyObject
230-from django.utils.importlib import import_module
231+from django.utils.module_loading import import_module_from_path
232 from django.utils._os import safe_join
233 from django.utils import six
234 
235@@ -258,17 +258,7 @@ def _get_finder(import_path):
236     Imports the staticfiles finder class described by import_path, where
237     import_path is the full Python path to the class.
238     """
239-    module, attr = import_path.rsplit('.', 1)
240-    try:
241-        mod = import_module(module)
242-    except ImportError as e:
243-        raise ImproperlyConfigured('Error importing module %s: "%s"' %
244-                                   (module, e))
245-    try:
246-        Finder = getattr(mod, attr)
247-    except AttributeError:
248-        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
249-                                   'class.' % (module, attr))
250+    Finder = import_module_from_path(import_path)
251     if not issubclass(Finder, BaseFinder):
252         raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
253                                    (Finder, BaseFinder))
254diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
255index d1a02a9..bf2be06 100644
256--- a/django/core/cache/__init__.py
257+++ b/django/core/cache/__init__.py
258@@ -25,6 +25,8 @@ from django.core.cache.backends.base import (
259     InvalidCacheBackendError, CacheKeyWarning, BaseCache)
260 from django.core.exceptions import ImproperlyConfigured
261 from django.utils import importlib
262+from django.utils.module_loading import import_module_from_path
263+
264 
265 __all__ = [
266     'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS'
267@@ -86,11 +88,10 @@ def parse_backend_conf(backend, **kwargs):
268     else:
269         try:
270             # Trying to import the given backend, in case it's a dotted path
271-            mod_path, cls_name = backend.rsplit('.', 1)
272-            mod = importlib.import_module(mod_path)
273-            backend_cls = getattr(mod, cls_name)
274-        except (AttributeError, ImportError, ValueError):
275-            raise InvalidCacheBackendError("Could not find backend '%s'" % backend)
276+            backend_cls = import_module_from_path(backend)
277+        except ImproperlyConfigured as e:
278+            raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
279+                backend, e))
280         location = kwargs.pop('LOCATION', '')
281         return backend, location, kwargs
282 
283@@ -126,10 +127,8 @@ def get_cache(backend, **kwargs):
284             backend_cls = mod.CacheClass
285         else:
286             backend, location, params = parse_backend_conf(backend, **kwargs)
287-            mod_path, cls_name = backend.rsplit('.', 1)
288-            mod = importlib.import_module(mod_path)
289-            backend_cls = getattr(mod, cls_name)
290-    except (AttributeError, ImportError) as e:
291+            backend_cls = import_module_from_path(backend)
292+    except (AttributeError, ImportError, ImproperlyConfigured) as e:
293         raise InvalidCacheBackendError(
294             "Could not find backend '%s': %s" % (backend, e))
295     cache = backend_cls(location, params)
296diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
297index 06e8952..25f3615 100644
298--- a/django/core/cache/backends/base.py
299+++ b/django/core/cache/backends/base.py
300@@ -4,7 +4,7 @@ from __future__ import unicode_literals
301 import warnings
302 
303 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
304-from django.utils.importlib import import_module
305+from django.utils.module_loading import import_module_from_path
306 
307 class InvalidCacheBackendError(ImproperlyConfigured):
308     pass
309@@ -35,9 +35,7 @@ def get_key_func(key_func):
310         if callable(key_func):
311             return key_func
312         else:
313-            key_func_module_path, key_func_name = key_func.rsplit('.', 1)
314-            key_func_module = import_module(key_func_module_path)
315-            return getattr(key_func_module, key_func_name)
316+            return import_module_from_path(key_func)
317     return default_key_func
318 
319 class BaseCache(object):
320diff --git a/django/core/files/storage.py b/django/core/files/storage.py
321index 650373f..2e3f809 100644
322--- a/django/core/files/storage.py
323+++ b/django/core/files/storage.py
324@@ -8,12 +8,12 @@ import itertools
325 from datetime import datetime
326 
327 from django.conf import settings
328-from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
329+from django.core.exceptions import SuspiciousOperation
330 from django.core.files import locks, File
331 from django.core.files.move import file_move_safe
332 from django.utils.encoding import force_text, filepath_to_uri
333 from django.utils.functional import LazyObject
334-from django.utils.importlib import import_module
335+from django.utils.module_loading import import_module_from_path
336 from django.utils.text import get_valid_filename
337 from django.utils._os import safe_join, abspathu
338 
339@@ -277,21 +277,7 @@ class FileSystemStorage(Storage):
340         return datetime.fromtimestamp(os.path.getmtime(self.path(name)))
341 
342 def get_storage_class(import_path=None):
343-    if import_path is None:
344-        import_path = settings.DEFAULT_FILE_STORAGE
345-    try:
346-        dot = import_path.rindex('.')
347-    except ValueError:
348-        raise ImproperlyConfigured("%s isn't a storage module." % import_path)
349-    module, classname = import_path[:dot], import_path[dot+1:]
350-    try:
351-        mod = import_module(module)
352-    except ImportError as e:
353-        raise ImproperlyConfigured('Error importing storage module %s: "%s"' % (module, e))
354-    try:
355-        return getattr(mod, classname)
356-    except AttributeError:
357-        raise ImproperlyConfigured('Storage module "%s" does not define a "%s" class.' % (module, classname))
358+    return import_module_from_path(import_path or settings.DEFAULT_FILE_STORAGE)
359 
360 class DefaultStorage(LazyObject):
361     def _setup(self):
362diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
363index c422945..c3b7ec9 100644
364--- a/django/core/files/uploadhandler.py
365+++ b/django/core/files/uploadhandler.py
366@@ -7,10 +7,9 @@ from __future__ import unicode_literals
367 from io import BytesIO
368 
369 from django.conf import settings
370-from django.core.exceptions import ImproperlyConfigured
371 from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
372-from django.utils import importlib
373 from django.utils.encoding import python_2_unicode_compatible
374+from django.utils.module_loading import import_module_from_path
375 
376 __all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler',
377            'TemporaryFileUploadHandler', 'MemoryFileUploadHandler',
378@@ -201,17 +200,4 @@ def load_handler(path, *args, **kwargs):
379         <TemporaryFileUploadHandler object at 0x...>
380 
381     """
382-    i = path.rfind('.')
383-    module, attr = path[:i], path[i+1:]
384-    try:
385-        mod = importlib.import_module(module)
386-    except ImportError as e:
387-        raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
388-    except ValueError:
389-        raise ImproperlyConfigured('Error importing upload handler module.'
390-            'Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
391-    try:
392-        cls = getattr(mod, attr)
393-    except AttributeError:
394-        raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
395-    return cls(*args, **kwargs)
396+    return import_module_from_path(path)(*args, **kwargs)
397diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
398index 2357246..da72258 100644
399--- a/django/core/handlers/base.py
400+++ b/django/core/handlers/base.py
401@@ -6,8 +6,9 @@ import types
402 
403 from django import http
404 from django.core import signals
405+from django.core.exceptions import MiddlewareNotUsed, PermissionDenied
406 from django.utils.encoding import force_text
407-from django.utils.importlib import import_module
408+from django.utils.module_loading import import_module_from_path
409 from django.utils import six
410 
411 logger = logging.getLogger('django.request')
412@@ -33,7 +34,6 @@ class BaseHandler(object):
413         Must be called after the environment is fixed (see __call__ in subclasses).
414         """
415         from django.conf import settings
416-        from django.core import exceptions
417         self._view_middleware = []
418         self._template_response_middleware = []
419         self._response_middleware = []
420@@ -41,21 +41,10 @@ class BaseHandler(object):
421 
422         request_middleware = []
423         for middleware_path in settings.MIDDLEWARE_CLASSES:
424-            try:
425-                mw_module, mw_classname = middleware_path.rsplit('.', 1)
426-            except ValueError:
427-                raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
428-            try:
429-                mod = import_module(mw_module)
430-            except ImportError as e:
431-                raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e))
432-            try:
433-                mw_class = getattr(mod, mw_classname)
434-            except AttributeError:
435-                raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
436+            mw_class = import_module_from_path(middleware_path)
437             try:
438                 mw_instance = mw_class()
439-            except exceptions.MiddlewareNotUsed:
440+            except MiddlewareNotUsed:
441                 continue
442 
443             if hasattr(mw_instance, 'process_request'):
444@@ -75,7 +64,7 @@ class BaseHandler(object):
445 
446     def get_response(self, request):
447         "Returns an HttpResponse object for the given HttpRequest"
448-        from django.core import exceptions, urlresolvers
449+        from django.core import urlresolvers
450         from django.conf import settings
451 
452         try:
453@@ -156,7 +145,7 @@ class BaseHandler(object):
454                     except:
455                         signals.got_request_exception.send(sender=self.__class__, request=request)
456                         response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
457-            except exceptions.PermissionDenied:
458+            except PermissionDenied:
459                 logger.warning(
460                     'Forbidden (Permission denied): %s', request.path,
461                     extra={
462diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py
463index 08f9702..51b9bf9 100644
464--- a/django/core/mail/__init__.py
465+++ b/django/core/mail/__init__.py
466@@ -4,8 +4,7 @@ Tools for sending email.
467 from __future__ import unicode_literals
468 
469 from django.conf import settings
470-from django.core.exceptions import ImproperlyConfigured
471-from django.utils.importlib import import_module
472+from django.utils.module_loading import import_module_from_path
473 
474 # Imported for backwards compatibility, and for the sake
475 # of a cleaner namespace. These symbols used to be in
476@@ -27,18 +26,7 @@ def get_connection(backend=None, fail_silently=False, **kwds):
477     Both fail_silently and other keyword arguments are used in the
478     constructor of the backend.
479     """
480-    path = backend or settings.EMAIL_BACKEND
481-    try:
482-        mod_name, klass_name = path.rsplit('.', 1)
483-        mod = import_module(mod_name)
484-    except ImportError as e:
485-        raise ImproperlyConfigured(('Error importing email backend module %s: "%s"'
486-                                    % (mod_name, e)))
487-    try:
488-        klass = getattr(mod, klass_name)
489-    except AttributeError:
490-        raise ImproperlyConfigured(('Module "%s" does not define a '
491-                                    '"%s" class' % (mod_name, klass_name)))
492+    klass = import_module_from_path(backend or settings.EMAIL_BACKEND)
493     return klass(fail_silently=fail_silently, **kwds)
494 
495 
496diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
497index 7387d13..5b1b6f5 100644
498--- a/django/core/servers/basehttp.py
499+++ b/django/core/servers/basehttp.py
500@@ -21,11 +21,9 @@ from django.utils.six.moves import socketserver
501 from wsgiref import simple_server
502 from wsgiref.util import FileWrapper   # for backwards compatibility
503 
504-import django
505-from django.core.exceptions import ImproperlyConfigured
506 from django.core.management.color import color_style
507 from django.core.wsgi import get_wsgi_application
508-from django.utils.importlib import import_module
509+from django.utils.module_loading import import_module_from_path
510 
511 __all__ = ['WSGIServer', 'WSGIRequestHandler']
512 
513@@ -49,22 +47,11 @@ def get_internal_wsgi_application():
514     app_path = getattr(settings, 'WSGI_APPLICATION')
515     if app_path is None:
516         return get_wsgi_application()
517-    module_name, attr = app_path.rsplit('.', 1)
518-    try:
519-        mod = import_module(module_name)
520-    except ImportError as e:
521-        raise ImproperlyConfigured(
522-            "WSGI application '%s' could not be loaded; "
523-            "could not import module '%s': %s" % (app_path, module_name, e))
524-    try:
525-        app = getattr(mod, attr)
526-    except AttributeError as e:
527-        raise ImproperlyConfigured(
528-            "WSGI application '%s' could not be loaded; "
529-            "can't find '%s' in module '%s': %s"
530-            % (app_path, attr, module_name, e))
531-
532-    return app
533+
534+    return import_module_from_path(
535+        app_path,
536+        error_prefix="WSGI application '%s' could not be loaded; " % app_path
537+    )
538 
539 
540 class WSGIServerException(Exception):
541diff --git a/django/core/signing.py b/django/core/signing.py
542index 92ab968..67c2770 100644
543--- a/django/core/signing.py
544+++ b/django/core/signing.py
545@@ -41,11 +41,10 @@ import time
546 import zlib
547 
548 from django.conf import settings
549-from django.core.exceptions import ImproperlyConfigured
550 from django.utils import baseconv
551 from django.utils.crypto import constant_time_compare, salted_hmac
552 from django.utils.encoding import force_bytes, force_str, force_text
553-from django.utils.importlib import import_module
554+from django.utils.module_loading import import_module_from_path
555 
556 
557 class BadSignature(Exception):
558@@ -76,18 +75,7 @@ def base64_hmac(salt, value, key):
559 
560 
561 def get_cookie_signer(salt='django.core.signing.get_cookie_signer'):
562-    modpath = settings.SIGNING_BACKEND
563-    module, attr = modpath.rsplit('.', 1)
564-    try:
565-        mod = import_module(module)
566-    except ImportError as e:
567-        raise ImproperlyConfigured(
568-            'Error importing cookie signer %s: "%s"' % (modpath, e))
569-    try:
570-        Signer = getattr(mod, attr)
571-    except AttributeError as e:
572-        raise ImproperlyConfigured(
573-            'Error importing cookie signer %s: "%s"' % (modpath, e))
574+    Signer = import_module_from_path(settings.SIGNING_BACKEND)
575     return Signer('django.http.cookies' + settings.SECRET_KEY, salt=salt)
576 
577 
578diff --git a/django/db/utils.py b/django/db/utils.py
579index a912986..49b871e 100644
580--- a/django/db/utils.py
581+++ b/django/db/utils.py
582@@ -5,6 +5,7 @@ from threading import local
583 from django.conf import settings
584 from django.core.exceptions import ImproperlyConfigured
585 from django.utils.importlib import import_module
586+from django.utils.module_loading import import_module_from_path
587 from django.utils import six
588 
589 
590@@ -109,17 +110,7 @@ class ConnectionRouter(object):
591         self.routers = []
592         for r in routers:
593             if isinstance(r, six.string_types):
594-                try:
595-                    module_name, klass_name = r.rsplit('.', 1)
596-                    module = import_module(module_name)
597-                except ImportError as e:
598-                    raise ImproperlyConfigured('Error importing database router %s: "%s"' % (klass_name, e))
599-                try:
600-                    router_class = getattr(module, klass_name)
601-                except AttributeError:
602-                    raise ImproperlyConfigured('Module "%s" does not define a database router name "%s"' % (module, klass_name))
603-                else:
604-                    router = router_class()
605+                router = import_module_from_path(r)()
606             else:
607                 router = r
608             self.routers.append(router)
609diff --git a/django/template/context.py b/django/template/context.py
610index 81aa194..9b7dc93 100644
611--- a/django/template/context.py
612+++ b/django/template/context.py
613@@ -1,6 +1,5 @@
614 from copy import copy
615-from django.core.exceptions import ImproperlyConfigured
616-from django.utils.importlib import import_module
617+from django.utils.module_loading import import_module_from_path
618 
619 # Cache of actual callables.
620 _standard_context_processors = None
621@@ -146,16 +145,7 @@ def get_standard_processors():
622         collect.extend(_builtin_context_processors)
623         collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS)
624         for path in collect:
625-            i = path.rfind('.')
626-            module, attr = path[:i], path[i+1:]
627-            try:
628-                mod = import_module(module)
629-            except ImportError as e:
630-                raise ImproperlyConfigured('Error importing request processor module %s: "%s"' % (module, e))
631-            try:
632-                func = getattr(mod, attr)
633-            except AttributeError:
634-                raise ImproperlyConfigured('Module "%s" does not define a "%s" callable request processor' % (module, attr))
635+            func = import_module_from_path(path)
636             processors.append(func)
637         _standard_context_processors = tuple(processors)
638     return _standard_context_processors
639diff --git a/django/template/loader.py b/django/template/loader.py
640index cfffb40..8678164 100644
641--- a/django/template/loader.py
642+++ b/django/template/loader.py
643@@ -27,8 +27,8 @@
644 
645 from django.core.exceptions import ImproperlyConfigured
646 from django.template.base import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
647-from django.utils.importlib import import_module
648 from django.conf import settings
649+from django.utils.module_loading import import_module_from_path
650 from django.utils import six
651 
652 template_source_loaders = None
653@@ -91,15 +91,7 @@ def find_template_loader(loader):
654     else:
655         args = []
656     if isinstance(loader, six.string_types):
657-        module, attr = loader.rsplit('.', 1)
658-        try:
659-            mod = import_module(module)
660-        except ImportError as e:
661-            raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
662-        try:
663-            TemplateLoader = getattr(mod, attr)
664-        except AttributeError as e:
665-            raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
666+        TemplateLoader = import_module_from_path(loader)
667 
668         if hasattr(TemplateLoader, 'load_template_source'):
669             func = TemplateLoader(*args)
670diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py
671index f8aadb3..e5a8555 100644
672--- a/django/utils/module_loading.py
673+++ b/django/utils/module_loading.py
674@@ -2,6 +2,32 @@ import imp
675 import os
676 import sys
677 
678+from django.core.exceptions import ImproperlyConfigured
679+from django.utils.importlib import import_module
680+
681+
682+def import_module_from_path(dotted_path, error_prefix=''):
683+    """
684+    Import a dotted module path and return the attribute/class designated by the
685+    last name in the path. Raise ImproperlyConfigured if something goes wrong.
686+    """
687+    try:
688+        module_path, class_name = dotted_path.rsplit('.', 1)
689+    except ValueError:
690+        raise ImproperlyConfigured("%s%s doesn't look like a module path" % (
691+            error_prefix, dotted_path))
692+    try:
693+        module = import_module(module_path)
694+    except ImportError as e:
695+        raise ImproperlyConfigured('%sError importing module %s: "%s"' % (
696+            error_prefix, module_path, e))
697+    try:
698+        attr = getattr(module, class_name)
699+    except AttributeError:
700+        raise ImproperlyConfigured('%sModule "%s" does not define a "%s" attribute/class' % (
701+            error_prefix, module_path, class_name))
702+    return attr
703+
704 
705 def module_has_submodule(package, module_name):
706     """See if 'module' is in 'package'."""
707diff --git a/django/views/debug.py b/django/views/debug.py
708index aaa7e40..6016e57 100644
709--- a/django/views/debug.py
710+++ b/django/views/debug.py
711@@ -13,8 +13,8 @@ from django.http import (HttpResponse, HttpResponseServerError,
712 from django.template import Template, Context, TemplateDoesNotExist
713 from django.template.defaultfilters import force_escape, pprint
714 from django.utils.html import escape
715-from django.utils.importlib import import_module
716 from django.utils.encoding import force_bytes, smart_text
717+from django.utils.module_loading import import_module_from_path
718 from django.utils import six
719 
720 HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE')
721@@ -76,17 +76,8 @@ def get_exception_reporter_filter(request):
722     global default_exception_reporter_filter
723     if default_exception_reporter_filter is None:
724         # Load the default filter for the first time and cache it.
725-        modpath = settings.DEFAULT_EXCEPTION_REPORTER_FILTER
726-        modname, classname = modpath.rsplit('.', 1)
727-        try:
728-            mod = import_module(modname)
729-        except ImportError as e:
730-            raise ImproperlyConfigured(
731-            'Error importing default exception reporter filter %s: "%s"' % (modpath, e))
732-        try:
733-            default_exception_reporter_filter = getattr(mod, classname)()
734-        except AttributeError:
735-            raise ImproperlyConfigured('Default exception reporter filter module "%s" does not define a "%s" class' % (modname, classname))
736+        default_exception_reporter_filter = import_module_from_path(
737+            settings.DEFAULT_EXCEPTION_REPORTER_FILTER)()
738     if request:
739         return getattr(request, 'exception_reporter_filter', default_exception_reporter_filter)
740     else:
741diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py
742index 595b65d..b782c4f 100644
743--- a/tests/regressiontests/file_storage/tests.py
744+++ b/tests/regressiontests/file_storage/tests.py
745@@ -55,9 +55,9 @@ class GetStorageClassTests(SimpleTestCase):
746         """
747         self.assertRaisesMessage(
748             ImproperlyConfigured,
749-            "NonExistingStorage isn't a storage module.",
750+            "Error importing module storage: \"No module named storage\"",
751             get_storage_class,
752-            'NonExistingStorage')
753+            'storage.NonExistingStorage')
754 
755     def test_get_nonexisting_storage_class(self):
756         """
757@@ -65,8 +65,8 @@ class GetStorageClassTests(SimpleTestCase):
758         """
759         self.assertRaisesMessage(
760             ImproperlyConfigured,
761-            'Storage module "django.core.files.storage" does not define a '\
762-                '"NonExistingStorage" class.',
763+            'Module "django.core.files.storage" does not define a '
764+            '"NonExistingStorage" attribute/class',
765             get_storage_class,
766             'django.core.files.storage.NonExistingStorage')
767 
768@@ -77,8 +77,8 @@ class GetStorageClassTests(SimpleTestCase):
769         # Error message may or may not be the fully qualified path.
770         six.assertRaisesRegex(self,
771             ImproperlyConfigured,
772-            ('Error importing storage module django.core.files.non_existing_'
773-                'storage: "No module named .*non_existing_storage'),
774+            'Error importing module django.core.files.non_existing_storage: '
775+            '"No module named non_existing_storage"',
776             get_storage_class,
777             'django.core.files.non_existing_storage.NonExistingStorage'
778         )
779diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py
780index dffb519..2385fc5 100644
781--- a/tests/regressiontests/utils/module_loading.py
782+++ b/tests/regressiontests/utils/module_loading.py
783@@ -3,9 +3,10 @@ import sys
784 import imp
785 from zipimport import zipimporter
786 
787+from django.core.exceptions import ImproperlyConfigured
788 from django.utils import unittest
789 from django.utils.importlib import import_module
790-from django.utils.module_loading import module_has_submodule
791+from django.utils.module_loading import import_module_from_path, module_has_submodule
792 
793 
794 class DefaultLoader(unittest.TestCase):
795@@ -102,6 +103,23 @@ class EggLoader(unittest.TestCase):
796         self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
797         self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
798 
799+
800+class ModuleImportTestCase(unittest.TestCase):
801+    def test_import_from_path(self):
802+        cls = import_module_from_path(
803+            'django.utils.module_loading.import_module_from_path')
804+        self.assertEqual(cls, import_module_from_path)
805+
806+        # Test exceptions raised
807+        for path in ('no_dots_in_path', 'unexistent.path',
808+                'tests.regressiontests.utils.unexistent'):
809+            self.assertRaises(ImproperlyConfigured, import_module_from_path, path)
810+
811+        with self.assertRaises(ImproperlyConfigured) as cm:
812+            import_module_from_path('unexistent.module.path', error_prefix="Foo")
813+        self.assertTrue(str(cm.exception).startswith('Foo'))
814+
815+
816 class ProxyFinder(object):
817     def __init__(self):
818         self._cache = {}
819diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py
820index 11dd7c3..726df3d 100644
821--- a/tests/regressiontests/utils/tests.py
822+++ b/tests/regressiontests/utils/tests.py
823@@ -20,7 +20,8 @@ from .html import TestUtilsHtml
824 from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests
825 from .ipv6 import TestUtilsIPv6
826 from .jslex import JsToCForGettextTest, JsTokensTest
827-from .module_loading import CustomLoader, DefaultLoader, EggLoader
828+from .module_loading import (CustomLoader, DefaultLoader, EggLoader,
829+    ModuleImportTestCase)
830 from .numberformat import TestNumberFormat
831 from .os_utils import SafeJoinTests
832 from .regex_helper import NormalizeTests
833diff --git a/tests/regressiontests/wsgi/tests.py b/tests/regressiontests/wsgi/tests.py
834index 6c1483d..0b8c9d2 100644
835--- a/tests/regressiontests/wsgi/tests.py
836+++ b/tests/regressiontests/wsgi/tests.py
837@@ -85,7 +85,7 @@ class GetInternalWSGIApplicationTest(unittest.TestCase):
838     def test_bad_module(self):
839         with six.assertRaisesRegex(self,
840             ImproperlyConfigured,
841-            r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; could not import module 'regressiontests.wsgi.noexist':"):
842+            r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; Error importing.*"):
843 
844             get_internal_wsgi_application()
845 
846@@ -94,6 +94,6 @@ class GetInternalWSGIApplicationTest(unittest.TestCase):
847     def test_bad_name(self):
848         with six.assertRaisesRegex(self,
849             ImproperlyConfigured,
850-            r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; can't find 'noexist' in module 'regressiontests.wsgi.wsgi':"):
851+            r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; Module.*"):
852 
853             get_internal_wsgi_application()