Commit 99bd39ef authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Added the ability to supply custom app configs.

parent 517c24bc
Loading
Loading
Loading
Loading
+64 −15
Original line number Diff line number Diff line
from importlib import import_module

from django.utils.module_loading import module_has_submodule
from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import import_by_path, module_has_submodule
from django.utils._os import upath


@@ -14,16 +15,33 @@ class AppConfig(object):

    def __init__(self, app_name):
        # Full Python path to the application eg. 'django.contrib.admin'.
        # This is the value that appears in INSTALLED_APPS.
        self.name = app_name

        # Root module for the application eg. <module 'django.contrib.admin'
        # from 'django/contrib/admin/__init__.pyc'>.
        self.app_module = import_module(app_name)

        # The following attributes could be defined at the class level in a
        # subclass, hence the test-and-set pattern.

        # Last component of the Python path to the application eg. 'admin'.
        # This value must be unique across a Django project.
        if not hasattr(self, 'label'):
            self.label = app_name.rpartition(".")[2]

        # Root module eg. <module 'django.contrib.admin' from
        # 'django/contrib/admin/__init__.pyc'>.
        self.app_module = import_module(app_name)
        # Human-readable name for the application eg. "Admin".
        if not hasattr(self, 'verbose_name'):
            self.verbose_name = self.label.title()

        # Filesystem path to the application directory eg.
        # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. May be
        # None if the application isn't a bona fide package eg. if it's an
        # egg. Otherwise it's a unicode on Python 2 and a str on Python 3.
        if not hasattr(self, 'path'):
            try:
                self.path = upath(self.app_module.__path__[0])
            except AttributeError:
                self.path = None

        # Module containing models eg. <module 'django.contrib.admin.models'
        # from 'django/contrib/admin/models.pyc'>. Set by import_models().
@@ -34,17 +52,48 @@ class AppConfig(object):
        # None to prevent accidental access before import_models() runs.
        self.models = None

        # Filesystem path to the application directory eg.
        # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. May be
        # None if the application isn't a bona fide package eg. if it's an
        # egg. Otherwise it's a unicode on Python 2 and a str on Python 3.
    def __repr__(self):
        return '<AppConfig: %s>' % self.label

    @classmethod
    def create(cls, entry):
        """
        Factory that creates an app config from an entry in INSTALLED_APPS.
        """
        try:
            self.path = upath(self.app_module.__path__[0])
            # If import_module succeeds, entry is a path to an app module.
            # Otherwise, entry is a path to an app config class or an error.
            import_module(entry)

        except ImportError:
            # Raise the original exception when entry cannot be a path to an
            # app config class. Since module names are allowable here, the
            # standard exception message from import_by_path is unsuitable.
            if '.' not in entry:
                raise

            cls = import_by_path(entry)

            # Check for obvious errors. (This check prevents duck typing, but
            # it could be removed if it became a problem in practice.)
            if not issubclass(cls, AppConfig):
                raise ImproperlyConfigured(
                    "%r isn't a subclass of AppConfig." % entry)

            # Obtain app name here rather than in AppClass.__init__ to keep
            # all error checking for entries in INSTALLED_APPS in one place.
            try:
                app_name = cls.name
            except AttributeError:
            self.path = None
                raise ImproperlyConfigured(
                    "%r must supply a name attribute." % entry)

    def __repr__(self):
        return '<AppConfig: %s>' % self.label
            # Entry is a path to an app config class.
            return cls(app_name)

        else:
            # Entry is a path to an app module.
            return cls(entry)

    def import_models(self, all_models):
        # Dictionary of models for this app, stored in the 'all_models'
+3 −3
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ class AppCache(object):
            # especially not other application modules, even indirectly.
            # Therefore we simply import them sequentially.
            for app_name in settings.INSTALLED_APPS:
                app_config = AppConfig(app_name)
                app_config = AppConfig.create(app_name)
                self.app_configs[app_config.label] = app_config

            self._apps_loaded = True
@@ -347,7 +347,7 @@ class AppCache(object):

    def _begin_with_app(self, app_name):
        # Returns an opaque value that can be passed to _end_with_app().
        app_config = AppConfig(app_name)
        app_config = AppConfig.create(app_name)
        if app_config.label in self.app_configs:
            return None
        else:
@@ -412,7 +412,7 @@ class AppCache(object):
        warnings.warn(
            "load_app(app_name) is deprecated.",
            PendingDeprecationWarning, stacklevel=2)
        app_config = AppConfig(app_name)
        app_config = AppConfig.create(app_name)
        app_config.import_models(self.all_models[app_config.label])
        self.app_configs[app_config.label] = app_config
        return app_config.models_module
+2 −2
Original line number Diff line number Diff line
@@ -226,7 +226,7 @@ class ModulesTestsPackages(IgnoreAllDeprecationWarningsMixin, unittest.TestCase)
        "Check that the get_tests helper function can find tests in a directory"
        from django.core.apps.base import AppConfig
        from django.test.simple import get_tests
        app_config = AppConfig('test_runner.valid_app')
        app_config = AppConfig.create('test_runner.valid_app')
        app_config.import_models({})
        tests = get_tests(app_config)
        self.assertIsInstance(tests, types.ModuleType)
@@ -235,7 +235,7 @@ class ModulesTestsPackages(IgnoreAllDeprecationWarningsMixin, unittest.TestCase)
        "Test for #12658 - Tests with ImportError's shouldn't fail silently"
        from django.core.apps.base import AppConfig
        from django.test.simple import get_tests
        app_config = AppConfig('test_runner_invalid_app')
        app_config = AppConfig.create('test_runner_invalid_app')
        app_config.import_models({})
        with self.assertRaises(ImportError):
            get_tests(app_config)