base.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import sys
  2. import six
  3. from django.core.exceptions import ImproperlyConfigured
  4. from .utils import import_attribute
  5. class AppConfOptions(object):
  6. def __init__(self, meta, prefix=None):
  7. self.prefix = prefix
  8. self.holder_path = getattr(meta, 'holder', 'django.conf.settings')
  9. self.holder = import_attribute(self.holder_path)
  10. self.proxy = getattr(meta, 'proxy', False)
  11. self.required = getattr(meta, 'required', [])
  12. self.configured_data = {}
  13. def prefixed_name(self, name):
  14. if name.startswith(self.prefix.upper()):
  15. return name
  16. return "%s_%s" % (self.prefix.upper(), name.upper())
  17. def contribute_to_class(self, cls, name):
  18. cls._meta = self
  19. self.names = {}
  20. self.defaults = {}
  21. class AppConfMetaClass(type):
  22. def __new__(cls, name, bases, attrs):
  23. super_new = super(AppConfMetaClass, cls).__new__
  24. parents = [b for b in bases if isinstance(b, AppConfMetaClass)]
  25. if not parents:
  26. return super_new(cls, name, bases, attrs)
  27. # Create the class.
  28. module = attrs.pop('__module__')
  29. new_class = super_new(cls, name, bases, {'__module__': module})
  30. attr_meta = attrs.pop('Meta', None)
  31. if attr_meta:
  32. meta = attr_meta
  33. else:
  34. attr_meta = type('Meta', (object,), {})
  35. meta = getattr(new_class, 'Meta', None)
  36. prefix = getattr(meta, 'prefix', getattr(meta, 'app_label', None))
  37. if prefix is None:
  38. # Figure out the prefix by looking one level up.
  39. # For 'django.contrib.sites.models', this would be 'sites'.
  40. model_module = sys.modules[new_class.__module__]
  41. prefix = model_module.__name__.split('.')[-2]
  42. new_class.add_to_class('_meta', AppConfOptions(meta, prefix))
  43. new_class.add_to_class('Meta', attr_meta)
  44. for parent in parents[::-1]:
  45. if hasattr(parent, '_meta'):
  46. new_class._meta.names.update(parent._meta.names)
  47. new_class._meta.defaults.update(parent._meta.defaults)
  48. new_class._meta.configured_data.update(
  49. parent._meta.configured_data)
  50. for name in filter(str.isupper, list(attrs.keys())):
  51. prefixed_name = new_class._meta.prefixed_name(name)
  52. new_class._meta.names[name] = prefixed_name
  53. new_class._meta.defaults[prefixed_name] = attrs.pop(name)
  54. # Add all attributes to the class.
  55. for name, value in attrs.items():
  56. new_class.add_to_class(name, value)
  57. new_class._configure()
  58. for name, value in six.iteritems(new_class._meta.configured_data):
  59. prefixed_name = new_class._meta.prefixed_name(name)
  60. setattr(new_class._meta.holder, prefixed_name, value)
  61. new_class.add_to_class(name, value)
  62. # Confirm presence of required settings.
  63. for name in new_class._meta.required:
  64. prefixed_name = new_class._meta.prefixed_name(name)
  65. if not hasattr(new_class._meta.holder, prefixed_name):
  66. raise ImproperlyConfigured('The required setting %s is'
  67. ' missing.' % prefixed_name)
  68. return new_class
  69. def add_to_class(cls, name, value):
  70. if hasattr(value, 'contribute_to_class'):
  71. value.contribute_to_class(cls, name)
  72. else:
  73. setattr(cls, name, value)
  74. def _configure(cls):
  75. # the ad-hoc settings class instance used to configure each value
  76. obj = cls()
  77. for name, prefixed_name in six.iteritems(obj._meta.names):
  78. default_value = obj._meta.defaults.get(prefixed_name)
  79. value = getattr(obj._meta.holder, prefixed_name, default_value)
  80. callback = getattr(obj, "configure_%s" % name.lower(), None)
  81. if callable(callback):
  82. value = callback(value)
  83. cls._meta.configured_data[name] = value
  84. cls._meta.configured_data = obj.configure()
  85. class AppConf(six.with_metaclass(AppConfMetaClass)):
  86. """
  87. An app setting object to be used for handling app setting defaults
  88. gracefully and providing a nice API for them.
  89. """
  90. def __init__(self, **kwargs):
  91. for name, value in six.iteritems(kwargs):
  92. setattr(self, name, value)
  93. def __dir__(self):
  94. return sorted(set(self._meta.names.keys()))
  95. # For instance access..
  96. @property
  97. def configured_data(self):
  98. return self._meta.configured_data
  99. def __getattr__(self, name):
  100. if self._meta.proxy:
  101. return getattr(self._meta.holder, name)
  102. raise AttributeError("%s not found. Use '%s' instead." %
  103. (name, self._meta.holder_path))
  104. def __setattr__(self, name, value):
  105. if name == name.upper():
  106. setattr(self._meta.holder,
  107. self._meta.prefixed_name(name), value)
  108. object.__setattr__(self, name, value)
  109. def configure(self):
  110. """
  111. Hook for doing any extra configuration, returning a dictionary
  112. containing the configured data.
  113. """
  114. return self.configured_data