You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

loading.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import sys
  2. import traceback
  3. from django.conf import settings
  4. from django.db.models import get_model as django_get_model
  5. from oscar.core.exceptions import (ModuleNotFoundError, ClassNotFoundError,
  6. AppNotFoundError)
  7. def get_class(module_label, classname):
  8. """
  9. Dynamically import a single class from the given module.
  10. This is a simple wrapper around `get_classes` for the case of loading a
  11. single class.
  12. Args:
  13. module_label (str): Module label comprising the app label and the
  14. module name, separated by a dot. For example, 'catalogue.forms'.
  15. classname (str): Name of the class to be imported.
  16. Returns:
  17. The requested class object or `None` if it can't be found
  18. """
  19. return get_classes(module_label, [classname])[0]
  20. def get_classes(module_label, classnames):
  21. """
  22. Dynamically import a list of classes from the given module.
  23. This works by looping over ``INSTALLED_APPS`` and looking for a match
  24. against the passed module label. If the requested class can't be found in
  25. the matching module, then we attempt to import it from the corresponding
  26. core Oscar app (assuming the matched module isn't in Oscar).
  27. This is very similar to ``django.db.models.get_model`` function for
  28. dynamically loading models. This function is more general though as it can
  29. load any class from the matching app, not just a model.
  30. Args:
  31. module_label (str): Module label comprising the app label and the
  32. module name, separated by a dot. For example, 'catalogue.forms'.
  33. classname (str): Name of the class to be imported.
  34. Returns:
  35. The requested class object or ``None`` if it can't be found
  36. Examples:
  37. Load a single class:
  38. >>> get_class('dashboard.catalogue.forms', 'ProductForm')
  39. oscar.apps.dashboard.catalogue.forms.ProductForm
  40. Load a list of classes:
  41. >>> get_classes('dashboard.catalogue.forms',
  42. ... ['ProductForm', 'StockRecordForm'])
  43. [oscar.apps.dashboard.catalogue.forms.ProductForm,
  44. oscar.apps.dashboard.catalogue.forms.StockRecordForm]
  45. Raises:
  46. AppNotFoundError: If no app is found in ``INSTALLED_APPS`` that matches
  47. the passed module label.
  48. ImportError: If the attempted import of a class raises an
  49. ``ImportError``, it is re-raised
  50. """
  51. # e.g. split 'dashboard.catalogue.forms' in 'dashboard.catalogue', 'forms'
  52. package, module = module_label.rsplit('.', 1)
  53. # import from Oscar package (should succeed in most cases)
  54. # e.g. 'oscar.apps.dashboard.catalogue.forms'
  55. oscar_module_label = "oscar.apps.%s" % module_label
  56. oscar_module = _import_oscar_module(oscar_module_label, classnames)
  57. # returns e.g. 'oscar.apps.dashboard.catalogue',
  58. # 'yourproject.apps.dashboard.catalogue' or 'dashboard.catalogue'
  59. installed_apps_entry = _get_installed_apps_entry(package)
  60. if not installed_apps_entry.startswith('oscar.apps.'):
  61. # Attempt to import the classes from the local module
  62. # e.g. 'yourproject.dashboard.catalogue.forms'
  63. local_module_label = installed_apps_entry + '.' + module
  64. local_module = _import_local_module(local_module_label, classnames)
  65. else:
  66. # The entry is obviously an Oscar one, we don't import again
  67. local_module = None
  68. if oscar_module is local_module is None:
  69. # This intentionally doesn't rise an ImportError, because it would get
  70. # masked by in some circular import scenarios.
  71. raise ModuleNotFoundError(
  72. "The module with label '%s' could not be imported. This either"
  73. "means that it indeed does not exist, or you might have a problem"
  74. " with a circular import." % module_label
  75. )
  76. # return imported classes, giving preference to ones from the local package
  77. return _pluck_classes([local_module, oscar_module], classnames)
  78. def _import_local_module(local_module_label, classnames):
  79. try:
  80. return __import__(local_module_label, fromlist=classnames)
  81. except ImportError:
  82. # There are 2 reasons why there is ImportError:
  83. # 1. local_app does not exist
  84. # 2. local_app exists but is corrupted (ImportError inside of the app)
  85. #
  86. # Obviously, for the reason #1 we want to fall back to use Oscar app.
  87. # For the reason #2 we want to propagate error (the dev obviously wants
  88. # to override app and not use Oscar app)
  89. #
  90. # ImportError does not provide easy way to distinguish those two cases.
  91. # Fortunately, the traceback of the ImportError starts at __import__
  92. # statement. If the traceback has more than one frame, it means that
  93. # application was found and ImportError originates within the local app
  94. __, __, exc_traceback = sys.exc_info()
  95. frames = traceback.extract_tb(exc_traceback)
  96. if len(frames) > 1:
  97. raise
  98. def _import_oscar_module(oscar_module_label, classnames):
  99. try:
  100. return __import__(oscar_module_label, fromlist=classnames)
  101. except ImportError:
  102. # Oscar does not have this application, can't fallback to it
  103. return None
  104. def _pluck_classes(modules, classnames):
  105. klasses = []
  106. for classname in classnames:
  107. klass = None
  108. for module in modules:
  109. if hasattr(module, classname):
  110. klass = getattr(module, classname)
  111. break
  112. if not klass:
  113. packages = [m.__name__ for m in modules if m is not None]
  114. raise ClassNotFoundError("No class '%s' found in %s" % (
  115. classname, ", ".join(packages)))
  116. klasses.append(klass)
  117. return klasses
  118. def _get_installed_apps_entry(app_name):
  119. """
  120. Walk through INSTALLED_APPS and return the first match. This does depend
  121. on the order of INSTALLED_APPS and will break if e.g. 'dashboard.catalogue'
  122. comes before 'catalogue' in INSTALLED_APPS.
  123. """
  124. for installed_app in settings.INSTALLED_APPS:
  125. if installed_app.endswith(app_name):
  126. return installed_app
  127. raise AppNotFoundError("No app found matching '%s'" % app_name)
  128. def get_profile_class():
  129. """
  130. Return the profile model class
  131. """
  132. setting = getattr(settings, 'AUTH_PROFILE_MODULE', None)
  133. if setting is None:
  134. return None
  135. app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
  136. return get_model(app_label, model_name)
  137. def feature_hidden(feature_name):
  138. """
  139. Test if a certain Oscar feature is disabled.
  140. """
  141. return (feature_name is not None and
  142. feature_name in settings.OSCAR_HIDDEN_FEATURES)
  143. def get_model(app_label, model_name, *args, **kwargs):
  144. """
  145. Gets a model class by it's app label and model name. Fails loudly if the
  146. model class can't be imported.
  147. This is merely a thin wrapper around Django's get_model function.
  148. """
  149. model = django_get_model(app_label, model_name, *args, **kwargs)
  150. if model is None:
  151. raise ImportError(
  152. "{app_label}.{model_name} could not be imported.".format(
  153. app_label=app_label, model_name=model_name))
  154. return model