diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index bbcc16e..395db12 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -15,6 +15,7 @@ from django.conf import global_settings
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.functional import LazyObject, empty
 from django.utils import importlib
+from django.utils.module_loading import import_module_from_path
 from django.utils import six
 
 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
@@ -68,9 +69,7 @@ class LazySettings(LazyObject):
         if self.LOGGING_CONFIG:
             from django.utils.log import DEFAULT_LOGGING
             # First find the logging configuration function ...
-            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
-            logging_config_module = importlib.import_module(logging_config_path)
-            logging_config_func = getattr(logging_config_module, logging_config_func_name)
+            logging_config_func = import_module_from_path(self.LOGGING_CONFIG)
 
             logging_config_func(DEFAULT_LOGGING)
 
diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py
index 7c62c1a..9f20e59 100644
--- a/django/contrib/admin/tests.py
+++ b/django/contrib/admin/tests.py
@@ -1,5 +1,5 @@
 from django.test import LiveServerTestCase
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 from django.utils.unittest import SkipTest
 from django.utils.translation import ugettext as _
 
@@ -9,11 +9,7 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
     @classmethod
     def setUpClass(cls):
         try:
-            # Import and start the WebDriver class.
-            module, attr = cls.webdriver_class.rsplit('.', 1)
-            mod = import_module(module)
-            WebDriver = getattr(mod, attr)
-            cls.selenium = WebDriver()
+            cls.selenium = import_module_from_path(cls.webdriver_class)()
         except Exception as e:
             raise SkipTest('Selenium webdriver "%s" not installed or not '
                            'operational: %s' % (cls.webdriver_class, str(e)))
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 99348d3..e3b6796 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -1,8 +1,8 @@
 import re
 
-from django.core.exceptions import ImproperlyConfigured, PermissionDenied
-from django.utils.importlib import import_module
 from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
+from django.utils.module_loading import import_module_from_path
 
 SESSION_KEY = '_auth_user_id'
 BACKEND_SESSION_KEY = '_auth_user_backend'
@@ -10,19 +10,7 @@ REDIRECT_FIELD_NAME = 'next'
 
 
 def load_backend(path):
-    i = path.rfind('.')
-    module, attr = path[:i], path[i + 1:]
-    try:
-        mod = import_module(module)
-    except ImportError as e:
-        raise ImproperlyConfigured('Error importing authentication backend %s: "%s"' % (path, e))
-    except ValueError:
-        raise ImproperlyConfigured('Error importing authentication backends. Is AUTHENTICATION_BACKENDS a correctly defined list or tuple?')
-    try:
-        cls = getattr(mod, attr)
-    except AttributeError:
-        raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr))
-    return cls()
+    return import_module_from_path(path)()
 
 
 def get_backends():
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 0fd9144..e14de82 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -12,6 +12,7 @@ from django.utils.encoding import force_bytes, force_str
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.crypto import (
     pbkdf2, constant_time_compare, get_random_string)
+from django.utils.module_loading import import_module_from_path
 from django.utils.translation import ugettext_noop as _
 
 
@@ -84,13 +85,7 @@ def load_hashers(password_hashers=None):
     if not password_hashers:
         password_hashers = settings.PASSWORD_HASHERS
     for backend in password_hashers:
-        try:
-            mod_path, cls_name = backend.rsplit('.', 1)
-            mod = importlib.import_module(mod_path)
-            hasher_cls = getattr(mod, cls_name)
-        except (AttributeError, ImportError, ValueError):
-            raise ImproperlyConfigured("hasher not found: %s" % backend)
-        hasher = hasher_cls()
+        hasher = import_module_from_path(backend)()
         if not getattr(hasher, 'algorithm'):
             raise ImproperlyConfigured("hasher doesn't specify an "
                                        "algorithm name: %s" % backend)
diff --git a/django/contrib/formtools/tests/wizard/loadstorage.py b/django/contrib/formtools/tests/wizard/loadstorage.py
index 267dee0..bb0b06e 100644
--- a/django/contrib/formtools/tests/wizard/loadstorage.py
+++ b/django/contrib/formtools/tests/wizard/loadstorage.py
@@ -1,8 +1,6 @@
 from django.test import TestCase
 
-from django.contrib.formtools.wizard.storage import (get_storage,
-                                                     MissingStorageModule,
-                                                     MissingStorageClass)
+from django.contrib.formtools.wizard.storage import get_storage, MissingStorage
 from django.contrib.formtools.wizard.storage.base import BaseStorage
 
 
@@ -12,11 +10,9 @@ class TestLoadStorage(TestCase):
             type(get_storage('django.contrib.formtools.wizard.storage.base.BaseStorage', 'wizard1')),
             BaseStorage)
 
-    def test_missing_module(self):
-        self.assertRaises(MissingStorageModule, get_storage,
+    def test_missing_storage(self):
+        self.assertRaises(MissingStorage, get_storage,
             'django.contrib.formtools.wizard.storage.idontexist.IDontExistStorage', 'wizard1')
-
-    def test_missing_class(self):
-        self.assertRaises(MissingStorageClass, get_storage,
+        self.assertRaises(MissingStorage, get_storage,
             'django.contrib.formtools.wizard.storage.base.IDontExistStorage', 'wizard1')
 
diff --git a/django/contrib/formtools/wizard/storage/__init__.py b/django/contrib/formtools/wizard/storage/__init__.py
index f2293c9..b30d1dc 100644
--- a/django/contrib/formtools/wizard/storage/__init__.py
+++ b/django/contrib/formtools/wizard/storage/__init__.py
@@ -1,22 +1,14 @@
-from django.utils.importlib import import_module
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.module_loading import import_module_from_path
 
 from django.contrib.formtools.wizard.storage.base import BaseStorage
 from django.contrib.formtools.wizard.storage.exceptions import (
-    MissingStorageModule, MissingStorageClass, NoFileStorageConfigured)
+    MissingStorage, NoFileStorageConfigured)
 
 
 def get_storage(path, *args, **kwargs):
-    i = path.rfind('.')
-    module, attr = path[:i], path[i+1:]
     try:
-        mod = import_module(module)
-    except ImportError as e:
-        raise MissingStorageModule(
-            'Error loading storage %s: "%s"' % (module, e))
-    try:
-        storage_class = getattr(mod, attr)
-    except AttributeError:
-        raise MissingStorageClass(
-            'Module "%s" does not define a storage named "%s"' % (module, attr))
+        storage_class = import_module_from_path(path)
+    except ImproperlyConfigured as e:
+        raise MissingStorage('Error loading storage: %s' % e)
     return storage_class(*args, **kwargs)
-
diff --git a/django/contrib/formtools/wizard/storage/exceptions.py b/django/contrib/formtools/wizard/storage/exceptions.py
index eab9030..e273a86 100644
--- a/django/contrib/formtools/wizard/storage/exceptions.py
+++ b/django/contrib/formtools/wizard/storage/exceptions.py
@@ -1,9 +1,6 @@
 from django.core.exceptions import ImproperlyConfigured
 
-class MissingStorageModule(ImproperlyConfigured):
-    pass
-
-class MissingStorageClass(ImproperlyConfigured):
+class MissingStorage(ImproperlyConfigured):
     pass
 
 class NoFileStorageConfigured(ImproperlyConfigured):
diff --git a/django/contrib/messages/storage/__init__.py b/django/contrib/messages/storage/__init__.py
index a584acc..8f3142b 100644
--- a/django/contrib/messages/storage/__init__.py
+++ b/django/contrib/messages/storage/__init__.py
@@ -1,28 +1,5 @@
 from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.importlib import import_module
-
-
-def get_storage(import_path):
-    """
-    Imports the message storage class described by import_path, where
-    import_path is the full Python path to the class.
-    """
-    try:
-        dot = import_path.rindex('.')
-    except ValueError:
-        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
-    module, classname = import_path[:dot], import_path[dot + 1:]
-    try:
-        mod = import_module(module)
-    except ImportError as e:
-        raise ImproperlyConfigured('Error importing module %s: "%s"' %
-                                   (module, e))
-    try:
-        return getattr(mod, classname)
-    except AttributeError:
-        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
-                                   'class.' % (module, classname))
+from django.utils.module_loading import import_module_from_path as get_storage
 
 
 # Callable with the same interface as the storage classes i.e.  accepts a
diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
index 9b06c2c..685376e 100644
--- a/django/contrib/staticfiles/finders.py
+++ b/django/contrib/staticfiles/finders.py
@@ -4,7 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.core.files.storage import default_storage, Storage, FileSystemStorage
 from django.utils.datastructures import SortedDict
 from django.utils.functional import empty, memoize, LazyObject
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 from django.utils._os import safe_join
 from django.utils import six
 
@@ -258,17 +258,7 @@ def _get_finder(import_path):
     Imports the staticfiles finder class described by import_path, where
     import_path is the full Python path to the class.
     """
-    module, attr = import_path.rsplit('.', 1)
-    try:
-        mod = import_module(module)
-    except ImportError as e:
-        raise ImproperlyConfigured('Error importing module %s: "%s"' %
-                                   (module, e))
-    try:
-        Finder = getattr(mod, attr)
-    except AttributeError:
-        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
-                                   'class.' % (module, attr))
+    Finder = import_module_from_path(import_path)
     if not issubclass(Finder, BaseFinder):
         raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
                                    (Finder, BaseFinder))
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index 562bcc2..d3a84fc 100644
--- a/django/core/cache/__init__.py
+++ b/django/core/cache/__init__.py
@@ -25,6 +25,8 @@ from django.core.cache.backends.base import (
     InvalidCacheBackendError, CacheKeyWarning, BaseCache)
 from django.core.exceptions import ImproperlyConfigured
 from django.utils import importlib
+from django.utils.module_loading import import_module_from_path
+
 
 __all__ = [
     'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS'
@@ -86,11 +88,10 @@ def parse_backend_conf(backend, **kwargs):
     else:
         try:
             # Trying to import the given backend, in case it's a dotted path
-            mod_path, cls_name = backend.rsplit('.', 1)
-            mod = importlib.import_module(mod_path)
-            backend_cls = getattr(mod, cls_name)
-        except (AttributeError, ImportError, ValueError):
-            raise InvalidCacheBackendError("Could not find backend '%s'" % backend)
+            backend_cls = import_module_from_path(backend)
+        except ImproperlyConfigured as e:
+            raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
+                backend, e))
         location = kwargs.pop('LOCATION', '')
         return backend, location, kwargs
 
@@ -126,10 +127,8 @@ def get_cache(backend, **kwargs):
             backend_cls = mod.CacheClass
         else:
             backend, location, params = parse_backend_conf(backend, **kwargs)
-            mod_path, cls_name = backend.rsplit('.', 1)
-            mod = importlib.import_module(mod_path)
-            backend_cls = getattr(mod, cls_name)
-    except (AttributeError, ImportError) as e:
+            backend_cls = import_module_from_path(backend)
+    except (AttributeError, ImportError, ImproperlyConfigured) as e:
         raise InvalidCacheBackendError(
             "Could not find backend '%s': %s" % (backend, e))
     cache = backend_cls(location, params)
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index 7234d3c..20c2993 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -4,7 +4,7 @@ from __future__ import unicode_literals
 import warnings
 
 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 
 
 class InvalidCacheBackendError(ImproperlyConfigured):
@@ -40,9 +40,7 @@ def get_key_func(key_func):
         if callable(key_func):
             return key_func
         else:
-            key_func_module_path, key_func_name = key_func.rsplit('.', 1)
-            key_func_module = import_module(key_func_module_path)
-            return getattr(key_func_module, key_func_name)
+            return import_module_from_path(key_func)
     return default_key_func
 
 
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 650373f..2e3f809 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -8,12 +8,12 @@ import itertools
 from datetime import datetime
 
 from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
+from django.core.exceptions import SuspiciousOperation
 from django.core.files import locks, File
 from django.core.files.move import file_move_safe
 from django.utils.encoding import force_text, filepath_to_uri
 from django.utils.functional import LazyObject
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 from django.utils.text import get_valid_filename
 from django.utils._os import safe_join, abspathu
 
@@ -277,21 +277,7 @@ class FileSystemStorage(Storage):
         return datetime.fromtimestamp(os.path.getmtime(self.path(name)))
 
 def get_storage_class(import_path=None):
-    if import_path is None:
-        import_path = settings.DEFAULT_FILE_STORAGE
-    try:
-        dot = import_path.rindex('.')
-    except ValueError:
-        raise ImproperlyConfigured("%s isn't a storage module." % import_path)
-    module, classname = import_path[:dot], import_path[dot+1:]
-    try:
-        mod = import_module(module)
-    except ImportError as e:
-        raise ImproperlyConfigured('Error importing storage module %s: "%s"' % (module, e))
-    try:
-        return getattr(mod, classname)
-    except AttributeError:
-        raise ImproperlyConfigured('Storage module "%s" does not define a "%s" class.' % (module, classname))
+    return import_module_from_path(import_path or settings.DEFAULT_FILE_STORAGE)
 
 class DefaultStorage(LazyObject):
     def _setup(self):
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
index c422945..c3b7ec9 100644
--- a/django/core/files/uploadhandler.py
+++ b/django/core/files/uploadhandler.py
@@ -7,10 +7,9 @@ from __future__ import unicode_literals
 from io import BytesIO
 
 from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
 from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
-from django.utils import importlib
 from django.utils.encoding import python_2_unicode_compatible
+from django.utils.module_loading import import_module_from_path
 
 __all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler',
            'TemporaryFileUploadHandler', 'MemoryFileUploadHandler',
@@ -201,17 +200,4 @@ def load_handler(path, *args, **kwargs):
         <TemporaryFileUploadHandler object at 0x...>
 
     """
-    i = path.rfind('.')
-    module, attr = path[:i], path[i+1:]
-    try:
-        mod = importlib.import_module(module)
-    except ImportError as e:
-        raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
-    except ValueError:
-        raise ImproperlyConfigured('Error importing upload handler module.'
-            'Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
-    try:
-        cls = getattr(mod, attr)
-    except AttributeError:
-        raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
-    return cls(*args, **kwargs)
+    return import_module_from_path(path)(*args, **kwargs)
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 0239475..2a49263 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -9,8 +9,9 @@ from django.conf import settings
 from django.core import exceptions
 from django.core import urlresolvers
 from django.core import signals
+from django.core.exceptions import MiddlewareNotUsed, PermissionDenied
 from django.utils.encoding import force_text
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 from django.utils import six
 from django.views import debug
 
@@ -43,21 +44,10 @@ class BaseHandler(object):
 
         request_middleware = []
         for middleware_path in settings.MIDDLEWARE_CLASSES:
-            try:
-                mw_module, mw_classname = middleware_path.rsplit('.', 1)
-            except ValueError:
-                raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
-            try:
-                mod = import_module(mw_module)
-            except ImportError as e:
-                raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e))
-            try:
-                mw_class = getattr(mod, mw_classname)
-            except AttributeError:
-                raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
+            mw_class = import_module_from_path(middleware_path)
             try:
                 mw_instance = mw_class()
-            except exceptions.MiddlewareNotUsed:
+            except MiddlewareNotUsed:
                 continue
 
             if hasattr(mw_instance, 'process_request'):
@@ -154,7 +144,7 @@ class BaseHandler(object):
                     except:
                         signals.got_request_exception.send(sender=self.__class__, request=request)
                         response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
-            except exceptions.PermissionDenied:
+            except PermissionDenied:
                 logger.warning(
                     'Forbidden (Permission denied): %s', request.path,
                     extra={
diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py
index 08f9702..51b9bf9 100644
--- a/django/core/mail/__init__.py
+++ b/django/core/mail/__init__.py
@@ -4,8 +4,7 @@ Tools for sending email.
 from __future__ import unicode_literals
 
 from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 
 # Imported for backwards compatibility, and for the sake
 # of a cleaner namespace. These symbols used to be in
@@ -27,18 +26,7 @@ def get_connection(backend=None, fail_silently=False, **kwds):
     Both fail_silently and other keyword arguments are used in the
     constructor of the backend.
     """
-    path = backend or settings.EMAIL_BACKEND
-    try:
-        mod_name, klass_name = path.rsplit('.', 1)
-        mod = import_module(mod_name)
-    except ImportError as e:
-        raise ImproperlyConfigured(('Error importing email backend module %s: "%s"'
-                                    % (mod_name, e)))
-    try:
-        klass = getattr(mod, klass_name)
-    except AttributeError:
-        raise ImproperlyConfigured(('Module "%s" does not define a '
-                                    '"%s" class' % (mod_name, klass_name)))
+    klass = import_module_from_path(backend or settings.EMAIL_BACKEND)
     return klass(fail_silently=fail_silently, **kwds)
 
 
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 68ca0c1..f4bd74b 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -21,11 +21,9 @@ from django.utils.six.moves import socketserver
 from wsgiref import simple_server
 from wsgiref.util import FileWrapper   # for backwards compatibility
 
-import django
-from django.core.exceptions import ImproperlyConfigured
 from django.core.management.color import color_style
 from django.core.wsgi import get_wsgi_application
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 
 __all__ = ['WSGIServer', 'WSGIRequestHandler']
 
@@ -49,22 +47,11 @@ def get_internal_wsgi_application():
     app_path = getattr(settings, 'WSGI_APPLICATION')
     if app_path is None:
         return get_wsgi_application()
-    module_name, attr = app_path.rsplit('.', 1)
-    try:
-        mod = import_module(module_name)
-    except ImportError as e:
-        raise ImproperlyConfigured(
-            "WSGI application '%s' could not be loaded; "
-            "could not import module '%s': %s" % (app_path, module_name, e))
-    try:
-        app = getattr(mod, attr)
-    except AttributeError as e:
-        raise ImproperlyConfigured(
-            "WSGI application '%s' could not be loaded; "
-            "can't find '%s' in module '%s': %s"
-            % (app_path, attr, module_name, e))
-
-    return app
+
+    return import_module_from_path(
+        app_path,
+        error_prefix="WSGI application '%s' could not be loaded; " % app_path
+    )
 
 
 class WSGIServerException(Exception):
diff --git a/django/core/signing.py b/django/core/signing.py
index 92ab968..67c2770 100644
--- a/django/core/signing.py
+++ b/django/core/signing.py
@@ -41,11 +41,10 @@ import time
 import zlib
 
 from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
 from django.utils import baseconv
 from django.utils.crypto import constant_time_compare, salted_hmac
 from django.utils.encoding import force_bytes, force_str, force_text
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 
 
 class BadSignature(Exception):
@@ -76,18 +75,7 @@ def base64_hmac(salt, value, key):
 
 
 def get_cookie_signer(salt='django.core.signing.get_cookie_signer'):
-    modpath = settings.SIGNING_BACKEND
-    module, attr = modpath.rsplit('.', 1)
-    try:
-        mod = import_module(module)
-    except ImportError as e:
-        raise ImproperlyConfigured(
-            'Error importing cookie signer %s: "%s"' % (modpath, e))
-    try:
-        Signer = getattr(mod, attr)
-    except AttributeError as e:
-        raise ImproperlyConfigured(
-            'Error importing cookie signer %s: "%s"' % (modpath, e))
+    Signer = import_module_from_path(settings.SIGNING_BACKEND)
     return Signer('django.http.cookies' + settings.SECRET_KEY, salt=salt)
 
 
diff --git a/django/db/utils.py b/django/db/utils.py
index 842fd35..8c34b77 100644
--- a/django/db/utils.py
+++ b/django/db/utils.py
@@ -5,6 +5,7 @@ from threading import local
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 from django.utils._os import upath
 from django.utils import six
 
@@ -110,17 +111,7 @@ class ConnectionRouter(object):
         self.routers = []
         for r in routers:
             if isinstance(r, six.string_types):
-                try:
-                    module_name, klass_name = r.rsplit('.', 1)
-                    module = import_module(module_name)
-                except ImportError as e:
-                    raise ImproperlyConfigured('Error importing database router %s: "%s"' % (klass_name, e))
-                try:
-                    router_class = getattr(module, klass_name)
-                except AttributeError:
-                    raise ImproperlyConfigured('Module "%s" does not define a database router name "%s"' % (module, klass_name))
-                else:
-                    router = router_class()
+                router = import_module_from_path(r)()
             else:
                 router = r
             self.routers.append(router)
diff --git a/django/template/context.py b/django/template/context.py
index 81aa194..9b7dc93 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -1,6 +1,5 @@
 from copy import copy
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.importlib import import_module
+from django.utils.module_loading import import_module_from_path
 
 # Cache of actual callables.
 _standard_context_processors = None
@@ -146,16 +145,7 @@ def get_standard_processors():
         collect.extend(_builtin_context_processors)
         collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS)
         for path in collect:
-            i = path.rfind('.')
-            module, attr = path[:i], path[i+1:]
-            try:
-                mod = import_module(module)
-            except ImportError as e:
-                raise ImproperlyConfigured('Error importing request processor module %s: "%s"' % (module, e))
-            try:
-                func = getattr(mod, attr)
-            except AttributeError:
-                raise ImproperlyConfigured('Module "%s" does not define a "%s" callable request processor' % (module, attr))
+            func = import_module_from_path(path)
             processors.append(func)
         _standard_context_processors = tuple(processors)
     return _standard_context_processors
diff --git a/django/template/loader.py b/django/template/loader.py
index cfffb40..8678164 100644
--- a/django/template/loader.py
+++ b/django/template/loader.py
@@ -27,8 +27,8 @@
 
 from django.core.exceptions import ImproperlyConfigured
 from django.template.base import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
-from django.utils.importlib import import_module
 from django.conf import settings
+from django.utils.module_loading import import_module_from_path
 from django.utils import six
 
 template_source_loaders = None
@@ -91,15 +91,7 @@ def find_template_loader(loader):
     else:
         args = []
     if isinstance(loader, six.string_types):
-        module, attr = loader.rsplit('.', 1)
-        try:
-            mod = import_module(module)
-        except ImportError as e:
-            raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
-        try:
-            TemplateLoader = getattr(mod, attr)
-        except AttributeError as e:
-            raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
+        TemplateLoader = import_module_from_path(loader)
 
         if hasattr(TemplateLoader, 'load_template_source'):
             func = TemplateLoader(*args)
diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py
index f8aadb3..e5a8555 100644
--- a/django/utils/module_loading.py
+++ b/django/utils/module_loading.py
@@ -2,6 +2,32 @@ import imp
 import os
 import sys
 
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+
+
+def import_module_from_path(dotted_path, error_prefix=''):
+    """
+    Import a dotted module path and return the attribute/class designated by the
+    last name in the path. Raise ImproperlyConfigured if something goes wrong.
+    """
+    try:
+        module_path, class_name = dotted_path.rsplit('.', 1)
+    except ValueError:
+        raise ImproperlyConfigured("%s%s doesn't look like a module path" % (
+            error_prefix, dotted_path))
+    try:
+        module = import_module(module_path)
+    except ImportError as e:
+        raise ImproperlyConfigured('%sError importing module %s: "%s"' % (
+            error_prefix, module_path, e))
+    try:
+        attr = getattr(module, class_name)
+    except AttributeError:
+        raise ImproperlyConfigured('%sModule "%s" does not define a "%s" attribute/class' % (
+            error_prefix, module_path, class_name))
+    return attr
+
 
 def module_has_submodule(package, module_name):
     """See if 'module' is in 'package'."""
diff --git a/django/views/debug.py b/django/views/debug.py
index e5f4c70..a586f16 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -13,8 +13,8 @@ from django.http import (HttpResponse, HttpResponseServerError,
 from django.template import Template, Context, TemplateDoesNotExist
 from django.template.defaultfilters import force_escape, pprint
 from django.utils.html import escape
-from django.utils.importlib import import_module
 from django.utils.encoding import force_bytes, smart_text
+from django.utils.module_loading import import_module_from_path
 from django.utils import six
 
 HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE')
@@ -76,17 +76,8 @@ def get_exception_reporter_filter(request):
     global default_exception_reporter_filter
     if default_exception_reporter_filter is None:
         # Load the default filter for the first time and cache it.
-        modpath = settings.DEFAULT_EXCEPTION_REPORTER_FILTER
-        modname, classname = modpath.rsplit('.', 1)
-        try:
-            mod = import_module(modname)
-        except ImportError as e:
-            raise ImproperlyConfigured(
-            'Error importing default exception reporter filter %s: "%s"' % (modpath, e))
-        try:
-            default_exception_reporter_filter = getattr(mod, classname)()
-        except AttributeError:
-            raise ImproperlyConfigured('Default exception reporter filter module "%s" does not define a "%s" class' % (modname, classname))
+        default_exception_reporter_filter = import_module_from_path(
+            settings.DEFAULT_EXCEPTION_REPORTER_FILTER)()
     if request:
         return getattr(request, 'exception_reporter_filter', default_exception_reporter_filter)
     else:
diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py
index 4872ce8..6ab7f90 100644
--- a/tests/regressiontests/file_storage/tests.py
+++ b/tests/regressiontests/file_storage/tests.py
@@ -57,9 +57,9 @@ class GetStorageClassTests(SimpleTestCase):
         """
         self.assertRaisesMessage(
             ImproperlyConfigured,
-            "NonExistingStorage isn't a storage module.",
+            "Error importing module storage: \"No module named storage\"",
             get_storage_class,
-            'NonExistingStorage')
+            'storage.NonExistingStorage')
 
     def test_get_nonexisting_storage_class(self):
         """
@@ -67,8 +67,8 @@ class GetStorageClassTests(SimpleTestCase):
         """
         self.assertRaisesMessage(
             ImproperlyConfigured,
-            'Storage module "django.core.files.storage" does not define a '\
-                '"NonExistingStorage" class.',
+            'Module "django.core.files.storage" does not define a '
+            '"NonExistingStorage" attribute/class',
             get_storage_class,
             'django.core.files.storage.NonExistingStorage')
 
@@ -79,8 +79,8 @@ class GetStorageClassTests(SimpleTestCase):
         # Error message may or may not be the fully qualified path.
         six.assertRaisesRegex(self,
             ImproperlyConfigured,
-            ('Error importing storage module django.core.files.non_existing_'
-                'storage: "No module named .*non_existing_storage'),
+            'Error importing module django.core.files.non_existing_storage: '
+            '"No module named non_existing_storage"',
             get_storage_class,
             'django.core.files.non_existing_storage.NonExistingStorage'
         )
diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py
index 3fc92b0..2e8df65 100644
--- a/tests/regressiontests/utils/module_loading.py
+++ b/tests/regressiontests/utils/module_loading.py
@@ -3,9 +3,10 @@ import sys
 import imp
 from zipimport import zipimporter
 
+from django.core.exceptions import ImproperlyConfigured
 from django.utils import unittest
 from django.utils.importlib import import_module
-from django.utils.module_loading import module_has_submodule
+from django.utils.module_loading import import_module_from_path, module_has_submodule
 from django.utils._os import upath
 
 
@@ -103,6 +104,23 @@ class EggLoader(unittest.TestCase):
         self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
         self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
 
+
+class ModuleImportTestCase(unittest.TestCase):
+    def test_import_from_path(self):
+        cls = import_module_from_path(
+            'django.utils.module_loading.import_module_from_path')
+        self.assertEqual(cls, import_module_from_path)
+
+        # Test exceptions raised
+        for path in ('no_dots_in_path', 'unexistent.path',
+                'tests.regressiontests.utils.unexistent'):
+            self.assertRaises(ImproperlyConfigured, import_module_from_path, path)
+
+        with self.assertRaises(ImproperlyConfigured) as cm:
+            import_module_from_path('unexistent.module.path', error_prefix="Foo")
+        self.assertTrue(str(cm.exception).startswith('Foo'))
+
+
 class ProxyFinder(object):
     def __init__(self):
         self._cache = {}
diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py
index 11dd7c3..726df3d 100644
--- a/tests/regressiontests/utils/tests.py
+++ b/tests/regressiontests/utils/tests.py
@@ -20,7 +20,8 @@ from .html import TestUtilsHtml
 from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests
 from .ipv6 import TestUtilsIPv6
 from .jslex import JsToCForGettextTest, JsTokensTest
-from .module_loading import CustomLoader, DefaultLoader, EggLoader
+from .module_loading import (CustomLoader, DefaultLoader, EggLoader,
+    ModuleImportTestCase)
 from .numberformat import TestNumberFormat
 from .os_utils import SafeJoinTests
 from .regex_helper import NormalizeTests
diff --git a/tests/regressiontests/wsgi/tests.py b/tests/regressiontests/wsgi/tests.py
index 6c1483d..0b8c9d2 100644
--- a/tests/regressiontests/wsgi/tests.py
+++ b/tests/regressiontests/wsgi/tests.py
@@ -85,7 +85,7 @@ class GetInternalWSGIApplicationTest(unittest.TestCase):
     def test_bad_module(self):
         with six.assertRaisesRegex(self,
             ImproperlyConfigured,
-            r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; could not import module 'regressiontests.wsgi.noexist':"):
+            r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; Error importing.*"):
 
             get_internal_wsgi_application()
 
@@ -94,6 +94,6 @@ class GetInternalWSGIApplicationTest(unittest.TestCase):
     def test_bad_name(self):
         with six.assertRaisesRegex(self,
             ImproperlyConfigured,
-            r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; can't find 'noexist' in module 'regressiontests.wsgi.wsgi':"):
+            r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; Module.*"):
 
             get_internal_wsgi_application()
