summaryrefslogtreecommitdiffstats
path: root/forum/deps/livesettings/values.py
diff options
context:
space:
mode:
Diffstat (limited to 'forum/deps/livesettings/values.py')
-rw-r--r--forum/deps/livesettings/values.py628
1 files changed, 628 insertions, 0 deletions
diff --git a/forum/deps/livesettings/values.py b/forum/deps/livesettings/values.py
new file mode 100644
index 00000000..f00fdc97
--- /dev/null
+++ b/forum/deps/livesettings/values.py
@@ -0,0 +1,628 @@
+"""Taken and modified from the dbsettings project.
+
+http://code.google.com/p/django-values/
+"""
+from decimal import Decimal
+from django import forms
+from django.core.exceptions import ImproperlyConfigured
+from django.utils import simplejson
+from django.utils.datastructures import SortedDict
+from django.utils.encoding import smart_str
+from django.utils.translation import gettext, ugettext_lazy as _
+from forum.deps.livesettings.models import find_setting, LongSetting, Setting, SettingNotSet
+from forum.deps.livesettings.overrides import get_overrides
+from forum.deps.livesettings.utils import load_module, is_string_like, is_list_or_tuple
+import datetime
+import logging
+import signals
+
+__all__ = ['BASE_GROUP', 'ConfigurationGroup', 'Value', 'BooleanValue', 'DecimalValue', 'DurationValue',
+ 'FloatValue', 'IntegerValue', 'ModuleValue', 'PercentValue', 'PositiveIntegerValue', 'SortedDotDict',
+ 'StringValue', 'LongStringValue', 'MultipleStringValue']
+
+_WARN = {}
+
+log = logging.getLogger('configuration')
+
+NOTSET = object()
+
+class SortedDotDict(SortedDict):
+
+ def __getattr__(self, key):
+ try:
+ return self[key]
+ except:
+ raise AttributeError, key
+
+ def __iter__(self):
+ vals = self.values()
+ for k in vals:
+ yield k
+
+ def values(self):
+ vals = super(SortedDotDict, self).values()
+ vals = [v for v in vals if isinstance(v, (ConfigurationGroup, Value))]
+ vals.sort()
+ return vals
+
+class ConfigurationGroup(SortedDotDict):
+ """A simple wrapper for a group of configuration values"""
+ def __init__(self, key, name, *args, **kwargs):
+ """Create a new ConfigurationGroup.
+
+ Arguments:
+ - key
+ - group name - for display to user
+
+ Named Arguments:
+ - ordering: integer, optional, defaults to 1.
+ - requires: See `Value` requires. The default `requires` all member values will have if not overridden.
+ - requiresvalue: See `Values` requires_value. The default `requires_value` if not overridden on the `Value` objects.
+ """
+ self.key = key
+ self.name = name
+ self.ordering = kwargs.pop('ordering', 1)
+ self.requires = kwargs.pop('requires', None)
+ if self.requires:
+ reqval = kwargs.pop('requiresvalue', key)
+ if not is_list_or_tuple(reqval):
+ reqval = (reqval, reqval)
+
+ self.requires_value = reqval[0]
+ self.requires.add_choice(reqval)
+
+ super(ConfigurationGroup, self).__init__(*args, **kwargs)
+
+ def __cmp__(self, other):
+ return cmp((self.ordering, self.name), (other.ordering, other.name))
+
+ def __eq__(self, other):
+ return (type(self) == type(other)
+ and self.ordering == other.ordering
+ and self.name == other.name)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def dict_values(self, load_modules=True):
+ vals = {}
+ keys = super(ConfigurationGroup, self).keys()
+ for key in keys:
+ v = self[key]
+ if isinstance(v, Value):
+ value = v.value
+ else:
+ value = v
+ vals[key] = value
+ return vals
+
+ def values(self):
+ vals = super(ConfigurationGroup, self).values()
+ return [v for v in vals if v.enabled()]
+
+BASE_GROUP = ConfigurationGroup('BASE', _('Base Settings'), ordering=0)
+
+class Value(object):
+
+ creation_counter = 0
+
+ def __init__(self, group, key, **kwargs):
+ """
+ Create a new Value object for configuration.
+
+ Args:
+ - `ConfigurationGroup`
+ - key - a string key
+
+ Named arguments:
+ - `description` - Will be passed to the field for form usage. Should be a translation proxy. Ex: _('example')
+ - `help_text` - Will be passed to the field for form usage.
+ - `choices` - If given, then the form field will use a select box
+ - `ordering` - Defaults to alphabetical by key if not given.
+ - `requires` - If given as a `Value`, then this field will only be rendered if that Value evaluates true (for Boolean requires) or the proper key is in the associated value.
+ - `requiresvalue` - If set, then this field will only be rendered if that value is in the list returned by self.value. Defaults to self.key.
+ - `hidden` - If true, then render a hidden field.
+ - `default` - If given, then this Value will return that default whenever it has no assocated `Setting`.
+ - `update_callback` - if given, then this value will call the callback whenever updated
+ """
+ self.group = group
+ self.key = key
+ self.description = kwargs.get('description', None)
+ self.help_text = kwargs.get('help_text')
+ self.choices = kwargs.get('choices',[])
+ self.ordering = kwargs.pop('ordering', 0)
+ self.hidden = kwargs.pop('hidden', False)
+ self.update_callback = kwargs.pop('update_callback', None)
+ self.requires = kwargs.pop('requires', None)
+ if self.requires:
+ reqval = kwargs.pop('requiresvalue', key)
+ if not is_list_or_tuple(reqval):
+ reqval = (reqval, reqval)
+
+ self.requires_value = reqval[0]
+ self.requires.add_choice(reqval)
+
+ elif group.requires:
+ self.requires = group.requires
+ self.requires_value = group.requires_value
+
+ if kwargs.has_key('default'):
+ self.default = kwargs.pop('default')
+ self.use_default = True
+ else:
+ self.use_default = False
+
+ self.creation_counter = Value.creation_counter
+ Value.creation_counter += 1
+
+ def __cmp__(self, other):
+ return cmp((self.ordering, self.description, self.creation_counter), (other.ordering, other.description, other.creation_counter))
+
+ def __eq__(self, other):
+ if type(self) == type(other):
+ return self.value == other.value
+ else:
+ return self.value == other
+
+ def __iter__(self):
+ return iter(self.value)
+
+ def __unicode__(self):
+ return unicode(self.value)
+
+ def __str__(self):
+ return str(self.value)
+
+ def add_choice(self, choice):
+ """Add a choice if it doesn't already exist."""
+ if not is_list_or_tuple(choice):
+ choice = (choice, choice)
+ skip = False
+ for k, v in self.choices:
+ if k == choice[0]:
+ skip = True
+ break
+ if not skip:
+ self.choices += (choice, )
+
+ def choice_field(self, **kwargs):
+ if self.hidden:
+ kwargs['widget'] = forms.MultipleHiddenInput()
+ return forms.ChoiceField(choices=self.choices, **kwargs)
+
+ def _choice_values(self):
+ choices = self.choices
+ vals = self.value
+ return [x for x in choices if x[0] in vals]
+
+ choice_values = property(fget=_choice_values)
+
+ def copy(self):
+ new_value = self.__class__(self.key)
+ new_value.__dict__ = self.__dict__.copy()
+ return new_value
+
+ def _default_text(self):
+ if not self.use_default:
+ note = ""
+ else:
+ if self.default == "":
+ note = _('Default value: ""')
+
+ elif self.choices:
+ work = []
+ for x in self.choices:
+ if x[0] in self.default:
+ work.append(smart_str(x[1]))
+ note = gettext('Default value: ') + ", ".join(work)
+
+ else:
+ note = _("Default value: %s") % unicode(self.default)
+
+ return note
+
+ default_text = property(fget=_default_text)
+
+ def enabled(self):
+ enabled = False
+ try:
+ if not self.requires:
+ enabled = True
+ else:
+ v = self.requires.value
+ if self.requires.choices:
+ enabled = self.requires_value == v or self.requires_value in v
+ elif v:
+ enabled = True
+ except SettingNotSet:
+ pass
+ return enabled
+
+ def make_field(self, **kwargs):
+ if self.choices:
+ if self.hidden:
+ kwargs['widget'] = forms.MultipleHiddenInput()
+ field = self.choice_field(**kwargs)
+ else:
+ if self.hidden:
+ kwargs['widget'] = forms.HiddenInput()
+ field = self.field(**kwargs)
+
+ field.group = self.group
+ field.default_text = self.default_text
+ return field
+
+ def make_setting(self, db_value):
+ log.debug('new setting %s.%s', self.group.key, self.key)
+ return Setting(group=self.group.key, key=self.key, value=db_value)
+
+ def _setting(self):
+ return find_setting(self.group.key, self.key)
+
+ setting = property(fget = _setting)
+
+ def _value(self):
+ use_db, overrides = get_overrides()
+
+ if not use_db:
+ try:
+ val = overrides[self.group.key][self.key]
+ except KeyError:
+ if self.use_default:
+ val = self.default
+ else:
+ raise SettingNotSet('%s.%s is not in your LIVESETTINGS_OPTIONS' % (self.group.key, self.key))
+
+ else:
+ try:
+ val = self.setting.value
+
+ except SettingNotSet, sns:
+ if self.use_default:
+ val = self.default
+ if overrides:
+ # maybe override the default
+ grp = overrides.get(self.group.key, {})
+ if grp.has_key(self.key):
+ val = grp[self.key]
+ else:
+ val = NOTSET
+
+ except AttributeError, ae:
+ log.error("Attribute error: %s", ae)
+ log.error("%s: Could not get _value of %s", self.key, self.setting)
+ raise(ae)
+
+ except Exception, e:
+ global _WARN
+ log.error(e)
+ if str(e).find("configuration_setting") > -1:
+ if not _WARN.has_key('configuration_setting'):
+ log.warn('Error loading setting %s.%s from table, OK if you are in syncdb', self.group.key, self.key)
+ _WARN['configuration_setting'] = True
+
+ if self.use_default:
+ val = self.default
+ else:
+ raise ImproperlyConfigured("All settings used in startup must have defaults, %s.%s does not", self.group.key, self.key)
+ else:
+ import traceback
+ traceback.print_exc()
+ log.warn("Problem finding settings %s.%s, %s", self.group.key, self.key, e)
+ raise SettingNotSet("Startup error, couldn't load %s.%s" %(self.group.key, self.key))
+ return val
+
+ def update(self, value):
+ use_db, overrides = get_overrides()
+
+ if use_db:
+ current_value = self.value
+
+ new_value = self.to_python(value)
+ if current_value != new_value:
+ if self.update_callback:
+ new_value = apply(self.update_callback, (current_value, new_value))
+
+ db_value = self.get_db_prep_save(new_value)
+
+ try:
+ s = self.setting
+ s.value = db_value
+
+ except SettingNotSet:
+ s = self.make_setting(db_value)
+
+ if self.use_default and self.default == new_value:
+ if s.id:
+ log.info("Deleted setting %s.%s", self.group.key, self.key)
+ s.delete()
+ else:
+ log.info("Updated setting %s.%s = %s", self.group.key, self.key, value)
+ s.save()
+
+ signals.configuration_value_changed.send(self, old_value=current_value, new_value=new_value, setting=self)
+
+ return True
+ else:
+ log.debug('not updating setting %s.%s - forum.deps.livesettings db is disabled',self.group.key, self.key)
+
+ return False
+
+ @property
+ def value(self):
+ val = self._value()
+ return self.to_python(val)
+
+ @property
+ def editor_value(self):
+ val = self._value()
+ return self.to_editor(val)
+
+ # Subclasses should override the following methods where applicable
+
+ def to_python(self, value):
+ "Returns a native Python object suitable for immediate use"
+ if value == NOTSET:
+ value = None
+ return value
+
+ def get_db_prep_save(self, value):
+ "Returns a value suitable for storage into a CharField"
+ if value == NOTSET:
+ value = ""
+ return unicode(value)
+
+ def to_editor(self, value):
+ "Returns a value suitable for display in a form widget"
+ if value == NOTSET:
+ return NOTSET
+ return unicode(value)
+
+###############
+# VALUE TYPES #
+###############
+
+class BooleanValue(Value):
+
+ class field(forms.BooleanField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.BooleanField.__init__(self, *args, **kwargs)
+
+ def add_choice(self, choice):
+ # ignore choice adding for boolean types
+ pass
+
+ def to_python(self, value):
+ if value in (True, 't', 'True', 1, '1'):
+ return True
+ return False
+
+ to_editor = to_python
+
+class DecimalValue(Value):
+ class field(forms.DecimalField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.DecimalField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value==NOTSET:
+ return Decimal("0")
+
+ try:
+ return Decimal(value)
+ except TypeError, te:
+ log.warning("Can't convert %s to Decimal for settings %s.%s", value, self.group.key, self.key)
+ raise TypeError(te)
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+# DurationValue has a lot of duplication and ugliness because of issue #2443
+# Until DurationField is sorted out, this has to do some extra work
+class DurationValue(Value):
+
+ class field(forms.CharField):
+ def clean(self, value):
+ try:
+ return datetime.timedelta(seconds=float(value))
+ except (ValueError, TypeError):
+ raise forms.ValidationError('This value must be a real number.')
+ except OverflowError:
+ raise forms.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ if isinstance(value, datetime.timedelta):
+ return value
+ try:
+ return datetime.timedelta(seconds=float(value))
+ except (ValueError, TypeError):
+ raise forms.ValidationError('This value must be a real number.')
+ except OverflowError:
+ raise forms.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
+
+ def get_db_prep_save(self, value):
+ if value == NOTSET:
+ return NOTSET
+ else:
+ return unicode(value.days * 24 * 3600 + value.seconds + float(value.microseconds) / 1000000)
+
+class FloatValue(Value):
+
+ class field(forms.FloatField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.FloatField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ return float(value)
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+class IntegerValue(Value):
+ class field(forms.IntegerField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.IntegerField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ return int(value)
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+
+class PercentValue(Value):
+
+ class field(forms.DecimalField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.DecimalField.__init__(self, 100, 0, 5, 2, *args, **kwargs)
+
+ class widget(forms.TextInput):
+ def render(self, *args, **kwargs):
+ # Place a percent sign after a smaller text field
+ attrs = kwargs.pop('attrs', {})
+ attrs['size'] = attrs['max_length'] = 6
+ return forms.TextInput.render(self, attrs=attrs, *args, **kwargs) + '%'
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ return Decimal(value) / 100
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+class PositiveIntegerValue(IntegerValue):
+
+ class field(forms.IntegerField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['min_value'] = 0
+ forms.IntegerField.__init__(self, *args, **kwargs)
+
+
+class StringValue(Value):
+
+ class field(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = ""
+ return unicode(value)
+
+ to_editor = to_python
+
+class LongStringValue(Value):
+
+ class field(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ kwargs['widget'] = forms.Textarea()
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def make_setting(self, db_value):
+ log.debug('new long setting %s.%s', self.group.key, self.key)
+ return LongSetting(group=self.group.key, key=self.key, value=db_value)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = ""
+ return unicode(value)
+
+ to_editor = to_python
+
+
+class MultipleStringValue(Value):
+
+ class field(forms.CharField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def choice_field(self, **kwargs):
+ kwargs['required'] = False
+ return forms.MultipleChoiceField(choices=self.choices, **kwargs)
+
+ def get_db_prep_save(self, value):
+ if is_string_like(value):
+ value = [value]
+ return simplejson.dumps(value)
+
+ def to_python(self, value):
+ if not value or value == NOTSET:
+ return []
+ if is_list_or_tuple(value):
+ return value
+ else:
+ try:
+ return simplejson.loads(value)
+ except:
+ if is_string_like(value):
+ return [value]
+ else:
+ log.warning('Could not decode returning empty list: %s', value)
+ return []
+
+
+ to_editor = to_python
+
+class ModuleValue(Value):
+ """Handles setting modules, storing them as strings in the db."""
+
+ class field(forms.CharField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def load_module(self, module):
+ """Load a child module"""
+ value = self._value()
+ if value == NOTSET:
+ raise SettingNotSet("%s.%s", self.group.key, self.key)
+ else:
+ return load_module("%s.%s" % (value, module))
+
+ def to_python(self, value):
+ if value == NOTSET:
+ v = {}
+ else:
+ v = load_module(value)
+ return v
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ value = ""
+ return value
+