| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- import sys
- import traceback
-
- from django.conf import settings
- from django.db.models import get_model as django_get_model
-
- from oscar.core.exceptions import (ModuleNotFoundError, ClassNotFoundError,
- AppNotFoundError)
-
-
- def get_class(module_label, classname):
- """
- Dynamically import a single class from the given module.
-
- This is a simple wrapper around `get_classes` for the case of loading a
- single class.
-
- Args:
- module_label (str): Module label comprising the app label and the
- module name, separated by a dot. For example, 'catalogue.forms'.
- classname (str): Name of the class to be imported.
-
- Returns:
- The requested class object or `None` if it can't be found
- """
- return get_classes(module_label, [classname])[0]
-
-
- def get_classes(module_label, classnames):
- """
- Dynamically import a list of classes from the given module.
-
- This works by looping over ``INSTALLED_APPS`` and looking for a match
- against the passed module label. If the requested class can't be found in
- the matching module, then we attempt to import it from the corresponding
- core Oscar app (assuming the matched module isn't in Oscar).
-
- This is very similar to ``django.db.models.get_model`` function for
- dynamically loading models. This function is more general though as it can
- load any class from the matching app, not just a model.
-
- Args:
- module_label (str): Module label comprising the app label and the
- module name, separated by a dot. For example, 'catalogue.forms'.
- classname (str): Name of the class to be imported.
-
- Returns:
- The requested class object or ``None`` if it can't be found
-
- Examples:
-
- Load a single class:
-
- >>> get_class('dashboard.catalogue.forms', 'ProductForm')
- oscar.apps.dashboard.catalogue.forms.ProductForm
-
- Load a list of classes:
-
- >>> get_classes('dashboard.catalogue.forms',
- ... ['ProductForm', 'StockRecordForm'])
- [oscar.apps.dashboard.catalogue.forms.ProductForm,
- oscar.apps.dashboard.catalogue.forms.StockRecordForm]
-
- Raises:
-
- AppNotFoundError: If no app is found in ``INSTALLED_APPS`` that matches
- the passed module label.
-
- ImportError: If the attempted import of a class raises an
- ``ImportError``, it is re-raised
- """
-
- # e.g. split 'dashboard.catalogue.forms' in 'dashboard.catalogue', 'forms'
- package, module = module_label.rsplit('.', 1)
-
- # import from Oscar package (should succeed in most cases)
- # e.g. 'oscar.apps.dashboard.catalogue.forms'
- oscar_module_label = "oscar.apps.%s" % module_label
- oscar_module = _import_oscar_module(oscar_module_label, classnames)
-
- # returns e.g. 'oscar.apps.dashboard.catalogue',
- # 'yourproject.apps.dashboard.catalogue' or 'dashboard.catalogue'
- installed_apps_entry = _get_installed_apps_entry(package)
- if not installed_apps_entry.startswith('oscar.apps.'):
- # Attempt to import the classes from the local module
- # e.g. 'yourproject.dashboard.catalogue.forms'
- local_module_label = installed_apps_entry + '.' + module
- local_module = _import_local_module(local_module_label, classnames)
- else:
- # The entry is obviously an Oscar one, we don't import again
- local_module = None
-
- if oscar_module is local_module is None:
- # This intentionally doesn't rise an ImportError, because it would get
- # masked by in some circular import scenarios.
- raise ModuleNotFoundError(
- "The module with label '%s' could not be imported. This either"
- "means that it indeed does not exist, or you might have a problem"
- " with a circular import." % module_label
- )
-
- # return imported classes, giving preference to ones from the local package
- return _pluck_classes([local_module, oscar_module], classnames)
-
-
- def _import_local_module(local_module_label, classnames):
- try:
- return __import__(local_module_label, fromlist=classnames)
- except ImportError:
- # There are 2 reasons why there is ImportError:
- # 1. local_app does not exist
- # 2. local_app exists but is corrupted (ImportError inside of the app)
- #
- # Obviously, for the reason #1 we want to fall back to use Oscar app.
- # For the reason #2 we want to propagate error (the dev obviously wants
- # to override app and not use Oscar app)
- #
- # ImportError does not provide easy way to distinguish those two cases.
- # Fortunately, the traceback of the ImportError starts at __import__
- # statement. If the traceback has more than one frame, it means that
- # application was found and ImportError originates within the local app
- __, __, exc_traceback = sys.exc_info()
- frames = traceback.extract_tb(exc_traceback)
- if len(frames) > 1:
- raise
-
-
- def _import_oscar_module(oscar_module_label, classnames):
- try:
- return __import__(oscar_module_label, fromlist=classnames)
- except ImportError:
- # Oscar does not have this application, can't fallback to it
- return None
-
-
- def _pluck_classes(modules, classnames):
- klasses = []
- for classname in classnames:
- klass = None
- for module in modules:
- if hasattr(module, classname):
- klass = getattr(module, classname)
- break
- if not klass:
- packages = [m.__name__ for m in modules if m is not None]
- raise ClassNotFoundError("No class '%s' found in %s" % (
- classname, ", ".join(packages)))
- klasses.append(klass)
- return klasses
-
-
- def _get_installed_apps_entry(app_name):
- """
- Walk through INSTALLED_APPS and return the first match. This does depend
- on the order of INSTALLED_APPS and will break if e.g. 'dashboard.catalogue'
- comes before 'catalogue' in INSTALLED_APPS.
- """
- for installed_app in settings.INSTALLED_APPS:
- if installed_app.endswith(app_name):
- return installed_app
- raise AppNotFoundError("No app found matching '%s'" % app_name)
-
-
- def get_profile_class():
- """
- Return the profile model class
- """
- setting = getattr(settings, 'AUTH_PROFILE_MODULE', None)
- if setting is None:
- return None
- app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
- return get_model(app_label, model_name)
-
-
- def feature_hidden(feature_name):
- """
- Test if a certain Oscar feature is disabled.
- """
- return (feature_name is not None and
- feature_name in settings.OSCAR_HIDDEN_FEATURES)
-
-
- def get_model(app_label, model_name, *args, **kwargs):
- """
- Gets a model class by it's app label and model name. Fails loudly if the
- model class can't be imported.
- This is merely a thin wrapper around Django's get_model function.
- """
- model = django_get_model(app_label, model_name, *args, **kwargs)
- if model is None:
- raise ImportError(
- "{app_label}.{model_name} could not be imported.".format(
- app_label=app_label, model_name=model_name))
- return model
|