|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import inspect
- import os
- from importlib import import_module
-
- from django.core.exceptions import ImproperlyConfigured
- from django.utils.functional import cached_property
- from django.utils.module_loading import import_string, module_has_submodule
-
- APPS_MODULE_NAME = "apps"
- MODELS_MODULE_NAME = "models"
-
-
- class AppConfig:
- """Class representing a Django application and its configuration."""
-
- def __init__(self, app_name, app_module):
- # Full Python path to the application e.g. 'django.contrib.admin'.
- self.name = app_name
-
- # Root module for the application e.g. <module 'django.contrib.admin'
- # from 'django/contrib/admin/__init__.py'>.
- self.module = app_module
-
- # Reference to the Apps registry that holds this AppConfig. Set by the
- # registry when it registers the AppConfig instance.
- self.apps = None
-
- # 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 e.g. 'admin'.
- # This value must be unique across a Django project.
- if not hasattr(self, "label"):
- self.label = app_name.rpartition(".")[2]
- if not self.label.isidentifier():
- raise ImproperlyConfigured(
- "The app label '%s' is not a valid Python identifier." % self.label
- )
-
- # Human-readable name for the application e.g. "Admin".
- if not hasattr(self, "verbose_name"):
- self.verbose_name = self.label.title()
-
- # Filesystem path to the application directory e.g.
- # '/path/to/django/contrib/admin'.
- if not hasattr(self, "path"):
- self.path = self._path_from_module(app_module)
-
- # Module containing models e.g. <module 'django.contrib.admin.models'
- # from 'django/contrib/admin/models.py'>. Set by import_models().
- # None if the application doesn't have a models module.
- self.models_module = None
-
- # Mapping of lowercase model names to model classes. Initially set to
- # None to prevent accidental access before import_models() runs.
- self.models = None
-
- def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, self.label)
-
- @cached_property
- def default_auto_field(self):
- from django.conf import settings
-
- return settings.DEFAULT_AUTO_FIELD
-
- @property
- def _is_default_auto_field_overridden(self):
- return self.__class__.default_auto_field is not AppConfig.default_auto_field
-
- def _path_from_module(self, module):
- """Attempt to determine app's filesystem path from its module."""
- # See #21874 for extended discussion of the behavior of this method in
- # various cases.
- # Convert to list because __path__ may not support indexing.
- paths = list(getattr(module, "__path__", []))
- if len(paths) != 1:
- filename = getattr(module, "__file__", None)
- if filename is not None:
- paths = [os.path.dirname(filename)]
- else:
- # For unknown reasons, sometimes the list returned by __path__
- # contains duplicates that must be removed (#25246).
- paths = list(set(paths))
- if len(paths) > 1:
- raise ImproperlyConfigured(
- "The app module %r has multiple filesystem locations (%r); "
- "you must configure this app with an AppConfig subclass "
- "with a 'path' class attribute." % (module, paths)
- )
- elif not paths:
- raise ImproperlyConfigured(
- "The app module %r has no filesystem location, "
- "you must configure this app with an AppConfig subclass "
- "with a 'path' class attribute." % module
- )
- return paths[0]
-
- @classmethod
- def create(cls, entry):
- """
- Factory that creates an app config from an entry in INSTALLED_APPS.
- """
- # create() eventually returns app_config_class(app_name, app_module).
- app_config_class = None
- app_name = None
- app_module = None
-
- # If import_module succeeds, entry points to the app module.
- try:
- app_module = import_module(entry)
- except Exception:
- pass
- else:
- # If app_module has an apps submodule that defines a single
- # AppConfig subclass, use it automatically.
- # To prevent this, an AppConfig subclass can declare a class
- # variable default = False.
- # If the apps module defines more than one AppConfig subclass,
- # the default one can declare default = True.
- if module_has_submodule(app_module, APPS_MODULE_NAME):
- mod_path = "%s.%s" % (entry, APPS_MODULE_NAME)
- mod = import_module(mod_path)
- # Check if there's exactly one AppConfig candidate,
- # excluding those that explicitly define default = False.
- app_configs = [
- (name, candidate)
- for name, candidate in inspect.getmembers(mod, inspect.isclass)
- if (
- issubclass(candidate, cls)
- and candidate is not cls
- and getattr(candidate, "default", True)
- )
- ]
- if len(app_configs) == 1:
- app_config_class = app_configs[0][1]
- else:
- # Check if there's exactly one AppConfig subclass,
- # among those that explicitly define default = True.
- app_configs = [
- (name, candidate)
- for name, candidate in app_configs
- if getattr(candidate, "default", False)
- ]
- if len(app_configs) > 1:
- candidates = [repr(name) for name, _ in app_configs]
- raise RuntimeError(
- "%r declares more than one default AppConfig: "
- "%s." % (mod_path, ", ".join(candidates))
- )
- elif len(app_configs) == 1:
- app_config_class = app_configs[0][1]
-
- # Use the default app config class if we didn't find anything.
- if app_config_class is None:
- app_config_class = cls
- app_name = entry
-
- # If import_string succeeds, entry is an app config class.
- if app_config_class is None:
- try:
- app_config_class = import_string(entry)
- except Exception:
- pass
- # If both import_module and import_string failed, it means that entry
- # doesn't have a valid value.
- if app_module is None and app_config_class is None:
- # If the last component of entry starts with an uppercase letter,
- # then it was likely intended to be an app config class; if not,
- # an app module. Provide a nice error message in both cases.
- mod_path, _, cls_name = entry.rpartition(".")
- if mod_path and cls_name[0].isupper():
- # We could simply re-trigger the string import exception, but
- # we're going the extra mile and providing a better error
- # message for typos in INSTALLED_APPS.
- # This may raise ImportError, which is the best exception
- # possible if the module at mod_path cannot be imported.
- mod = import_module(mod_path)
- candidates = [
- repr(name)
- for name, candidate in inspect.getmembers(mod, inspect.isclass)
- if issubclass(candidate, cls) and candidate is not cls
- ]
- msg = "Module '%s' does not contain a '%s' class." % (
- mod_path,
- cls_name,
- )
- if candidates:
- msg += " Choices are: %s." % ", ".join(candidates)
- raise ImportError(msg)
- else:
- # Re-trigger the module import exception.
- import_module(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(app_config_class, AppConfig):
- raise ImproperlyConfigured("'%s' 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.
- if app_name is None:
- try:
- app_name = app_config_class.name
- except AttributeError:
- raise ImproperlyConfigured("'%s' must supply a name attribute." % entry)
-
- # Ensure app_name points to a valid module.
- try:
- app_module = import_module(app_name)
- except ImportError:
- raise ImproperlyConfigured(
- "Cannot import '%s'. Check that '%s.%s.name' is correct."
- % (
- app_name,
- app_config_class.__module__,
- app_config_class.__qualname__,
- )
- )
-
- # Entry is a path to an app config class.
- return app_config_class(app_name, app_module)
-
- def get_model(self, model_name, require_ready=True):
- """
- Return the model with the given case-insensitive model_name.
-
- Raise LookupError if no model exists with this name.
- """
- if require_ready:
- self.apps.check_models_ready()
- else:
- self.apps.check_apps_ready()
- try:
- return self.models[model_name.lower()]
- except KeyError:
- raise LookupError(
- "App '%s' doesn't have a '%s' model." % (self.label, model_name)
- )
-
- def get_models(self, include_auto_created=False, include_swapped=False):
- """
- Return an iterable of models.
-
- By default, the following models aren't included:
-
- - auto-created models for many-to-many relations without
- an explicit intermediate table,
- - models that have been swapped out.
-
- Set the corresponding keyword argument to True to include such models.
- Keyword arguments aren't documented; they're a private API.
- """
- self.apps.check_models_ready()
- for model in self.models.values():
- if model._meta.auto_created and not include_auto_created:
- continue
- if model._meta.swapped and not include_swapped:
- continue
- yield model
-
- def import_models(self):
- # Dictionary of models for this app, primarily maintained in the
- # 'all_models' attribute of the Apps this AppConfig is attached to.
- self.models = self.apps.all_models[self.label]
-
- if module_has_submodule(self.module, MODELS_MODULE_NAME):
- models_module_name = "%s.%s" % (self.name, MODELS_MODULE_NAME)
- self.models_module = import_module(models_module_name)
-
- def ready(self):
- """
- Override this method in subclasses to run code when Django starts.
- """
|