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.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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_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 installed_apps_entry.startswith('oscar.apps.'):
  61. # The entry is obviously an Oscar one, we don't import again
  62. local_module = None
  63. else:
  64. # Attempt to import the classes from the local module
  65. # e.g. 'yourproject.dashboard.catalogue.forms'
  66. local_module_label = installed_apps_entry + '.' + module
  67. local_module = _import_module(local_module_label, classnames)
  68. if oscar_module is local_module is None:
  69. # This intentionally doesn't raise an ImportError, because that could
  70. # get masked 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_module(module_label, classnames):
  79. """
  80. Imports the module with the given name.
  81. Returns None if the module doesn't exist, but propagates any import errors.
  82. """
  83. try:
  84. return __import__(module_label, fromlist=classnames)
  85. except ImportError:
  86. # There are 2 reasons why there could be an ImportError:
  87. #
  88. # 1. Module does not exist. In that case, we ignore the import and
  89. # return None
  90. # 2. Module exists but another ImportError occurred when trying to
  91. # import the module. In that case, it is important to propagate the
  92. # error.
  93. #
  94. # ImportError does not provide easy way to distinguish those two cases.
  95. # Fortunately, the traceback of the ImportError starts at __import__
  96. # statement. If the traceback has more than one frame, it means that
  97. # application was found and ImportError originates within the local app
  98. __, __, exc_traceback = sys.exc_info()
  99. frames = traceback.extract_tb(exc_traceback)
  100. if len(frames) > 1:
  101. raise
  102. def _pluck_classes(modules, classnames):
  103. """
  104. Gets a list of class names and a list of modules to pick from.
  105. For each class name, will return the class from the first module that has a
  106. matching class.
  107. """
  108. klasses = []
  109. for classname in classnames:
  110. klass = None
  111. for module in modules:
  112. if hasattr(module, classname):
  113. klass = getattr(module, classname)
  114. break
  115. if not klass:
  116. packages = [m.__name__ for m in modules if m is not None]
  117. raise ClassNotFoundError("No class '%s' found in %s" % (
  118. classname, ", ".join(packages)))
  119. klasses.append(klass)
  120. return klasses
  121. def _get_installed_apps_entry(app_name):
  122. """
  123. Walk through INSTALLED_APPS and return the first match. This does depend
  124. on the order of INSTALLED_APPS and will break if e.g. 'dashboard.catalogue'
  125. comes before 'catalogue' in INSTALLED_APPS.
  126. """
  127. for installed_app in settings.INSTALLED_APPS:
  128. if installed_app.endswith(app_name):
  129. return installed_app
  130. raise AppNotFoundError("No app found matching '%s'" % app_name)
  131. def get_profile_class():
  132. """
  133. Return the profile model class
  134. """
  135. # The AUTH_PROFILE_MODULE setting was deprecated in Django 1.5, but it
  136. # makes sense for Oscar to continue to use it. Projects built on Django
  137. # 1.4 are likely to have used a profile class and it's very difficult to
  138. # upgrade to a single user model. Hence, we should continue to support
  139. # having a separate profile class even if Django doesn't.
  140. setting = getattr(settings, 'AUTH_PROFILE_MODULE', None)
  141. if setting is None:
  142. return None
  143. app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
  144. return get_model(app_label, model_name)
  145. def feature_hidden(feature_name):
  146. """
  147. Test if a certain Oscar feature is disabled.
  148. """
  149. return (feature_name is not None and
  150. feature_name in settings.OSCAR_HIDDEN_FEATURES)
  151. def get_model(app_label, model_name, *args, **kwargs):
  152. """
  153. Gets a model class by it's app label and model name. Fails loudly if the
  154. model class can't be imported.
  155. This is merely a thin wrapper around Django's get_model function.
  156. """
  157. model = django_get_model(app_label, model_name, *args, **kwargs)
  158. if model is None:
  159. raise ImportError(
  160. "{app_label}.{model_name} could not be imported.".format(
  161. app_label=app_label, model_name=model_name))
  162. return model