diff --git a/html/templates/modalFrames/configFrame.html b/html/templates/modalFrames/configFrame.html index 6fa4563..7dd2689 100644 --- a/html/templates/modalFrames/configFrame.html +++ b/html/templates/modalFrames/configFrame.html @@ -23,28 +23,59 @@
{{plugin.screenName}}
{% if 'select' in config.name %} - + + {% elif config.curType() == 'complex' %} + {% if config.value.use_checkboxes %} + {% if not config.required %} + + {% endif %} + {% for v in config.value.options %} + + {% endfor %} + {% else %} + + {% endif %} {% else %} - {% if config.curType() == 'int' and 'enable' not in config.name -%} - {% set inputType = 'number" step="any'%} - {% elif config.curType() == 'bool' or 'enable' in config.name%} - {% if config.value %} - - {# this is a little dodgy. we append the checked attr with manipulating the string that is set for the input type#} - {% set inputType = 'checkbox" checked="checked'%} - {% else %} - {% set inputType = 'checkbox'%} - {% endif %} - - {% elif 'password' in config.name %} - {% set inputType = 'password'%} - {% else %} - {% set inputType = 'text'%} - {%- endif %} + {% if config.curType() == 'int' and 'enable' not in config.name %} + {% set inputType = 'number" step="any'%} + {% elif config.curType() == 'bool' or 'enable' in config.name%} + {% if config.value %} + + {# this is a little dodgy. we append the checked attr with manipulating the string that is set for the input type#} + {% set inputType = 'checkbox" checked="checked'%} + {% else %} + {% set inputType = 'checkbox'%} + {% endif %} + + {% elif 'password' in config.name %} + {% set inputType = 'password'%} + {% else %} + {% set inputType = 'text'%} + {%- endif %} {% endif %}
diff --git a/html/templates/settings.html b/html/templates/settings.html index 59d176d..adb8fd3 100644 --- a/html/templates/settings.html +++ b/html/templates/settings.html @@ -63,29 +63,60 @@

{{ plugin.screenName }}{% if plugin._type != 'MediaTypeManager'%} ({{ plugin
{% if 'select' in config.name %} - + + {% elif config.curType() == 'complex' %} + {% if config.value.use_checkboxes %} + {% if not config.required %} + + {% endif %} + {% for v in config.value.options %} + + {% endfor %} + {% else %} + + {% endif %} {% else %} - {% if config.curType() == 'int' and 'enable' not in config.name -%} - {% set inputType = 'number" step="any'%} - {% elif config.curType() == 'bool' or 'enable' in config.name%} - {% if config.value %} - - {# this is a little dodgy. we append the checked attr with manipulating the string that is set for the input type#} - {% set inputType = 'checkbox" checked="checked'%} - {% else %} - {% set inputType = 'checkbox'%} - {% endif %} - - {% elif 'password' in config.name %} - {% set inputType = 'password'%} - {% else %} - {% set inputType = 'text'%} - {%- endif %} - + {% if config.curType() == 'int' and 'enable' not in config.name -%} + {% set inputType = 'number" step="any'%} + {% elif config.curType() == 'bool' or 'enable' in config.name%} + {% if config.value %} + + {# this is a little dodgy. we append the checked attr with manipulating the string that is set for the input type#} + {% set inputType = 'checkbox" checked="checked'%} + {% else %} + {% set inputType = 'checkbox'%} + {% endif %} + + {% elif 'password' in config.name %} + {% set inputType = 'password'%} + {% else %} + {% set inputType = 'text'%} + {%- endif %} + {% endif %}
diff --git a/xdm/api/__init__.py b/xdm/api/__init__.py index e6127b9..70c4dd0 100644 --- a/xdm/api/__init__.py +++ b/xdm/api/__init__.py @@ -94,7 +94,7 @@ def rest(self, *args, **kwargs): for name in p_function.args: log('function %s needs %s' % (method, name)) if name in kwargs: - fn_args.append(convertV(kwargs[name])) + fn_args.append(convertV(kwargs[name], None)) #fixme: None correct? try: log("calling %s with %s" % (p_function, fn_args)) diff --git a/xdm/classes.py b/xdm/classes.py index f025d71..a7b3524 100644 --- a/xdm/classes.py +++ b/xdm/classes.py @@ -787,6 +787,8 @@ def _get_value(self): return datetime.replace(tzinfo=None) else: return self._value_datetime + #elif self._value_data != None: + # return self._value_data else: return self._value_char @@ -838,6 +840,82 @@ def __cmp__(self, other): return -1 return 1 +class ComplexDataType(object): + value = None + _skipStorage = ("_skipStorage",) # don't store those attributes in the database (most likely class attributes, not settings) + + def __init__(self, **kwargs): + for arg, value in kwargs.iteritems(): + setattr(self, arg, value) + + # recursively merge self._skipStorage + #fixme:; may be used for self._config inheritance later on + ss = [] + for parentClass in self.__class__.__mro__[:-1]: + ss += list(parentClass._skipStorage) + self._skipStorage = ss + + def __eq__(self, other): + """ + the data of this instance equals other? implement! + """ + raise NotImplementedError + + def dump(self): + base = dict(self.__class__.__dict__) + base.update({"_className_": self.__class__.__name__}) + base.update(dict(self.__dict__)) + + # drop internals, we currently don't need them saved + dumped = dict((key, base[key]) for key in [key for key in base.keys() if not key.startswith("__") and not key.endswith("__")\ + and key not in self._skipStorage]) + return json.dumps(dumped) + + @staticmethod + def load(data): + if "_className_" in data: + cls = data.pop("_className_") + return complexDataTypes[cls](**data) + return data + +class ListType(ComplexDataType): + multiple = False + required = False #fixme: handle this if true + _options = None + + def __eq__(self, other): + """ + compares self.selected to other["selected"], expects other to be a dict + """ + if not type(other) == types.DictType or ("selected" in other and not type(other["selected"] == types.ListType)): + raise ValueError + return self.selected == other["selected"] + + # self.value holds the selected value list + def _get_selected(self): + return self.value + def _set_selected(self, value): + self.value = value + + selected = property(_get_selected, _set_selected) + + # self._options holds the available options + def _get_options(self): + return self._options + + def _set_options(self, value): + self._options = value + + options = property(_get_options, _set_options) + +class Select(ListType): + pass + +class MultiSelect(ListType): + multiple = True + use_checkboxes = False # use multiple checkboxes instead of select[type=multiple]; can be True or "inline" + +complexDataTypes = dict([(cls.__name__, cls) for cls in (Select, MultiSelect)]) class Config(BaseModel): module = CharField(default='system') # system, plugin ... you know this kind of thing @@ -850,11 +928,16 @@ class Config(BaseModel): _value_int = FloatField(True) _value_char = CharField(True) _value_bool = BooleanField(True) + _value_data = TextField(True) class Meta: database = xdm.CONFIG_DATABASE order_by = ('name',) + @classmethod + def _migrate(cls): + return cls._migrateNewField(cls._value_data) + def copy(self): new = Config() new.module = self.module @@ -874,25 +957,58 @@ def _get_value(self): if float(self._value_int).is_integer(): return int(self._value_int) return self._value_int - else: - return unicode(self._value_char) + elif self._value_data != None: + # try json decoding, based on complex data types + try: + return json.loads(self._value_data, object_hook=ComplexDataType.load) + except ValueError, e: + raise Exception("couldn't decode value for config %s, value: %s; error: %s" % (self.name, self._value_data, e)) + return unicode(self._value_char) def _set_value(self, value): - if type(value).__name__ in ('float', 'int'): - self._value_char = None - self._value_bool = None + _type = type(value).__name__ + + oldValueData = self.value if self._value_data else None + self._value_char = None + self._value_bool = None + self._value_int = None + self._value_data = None + + if _type in ('float', 'int'): self._value_int = value return - if type(value).__name__ in ('str', 'unicode'): - self._value_bool = None - self._value_int = None + + if _type in ('str', 'unicode'): self._value_char = value return - if type(value).__name__ in ('bool', 'NoneType'): - self._value_char = None - self._value_int = None + + if _type in ('bool', 'NoneType'): self._value_bool = value return + + if _type == "dict": + if not "selected" in value: + raise Exception("need to supply 'selected' to a ComplexType in config") + + obj = None + selType = type(value["selected"]) + + # single selection + if selType in types.StringTypes: + cls = Select + # multi selection + elif selType in (types.ListType, types.TupleType): + cls = MultiSelect + + if not "options" in value: + # "options" is missing, we're coming from a form which only supplies the selected values. reuse old config instance and update it + oldValueData.selected = value["selected"] + self._value_data = oldValueData.dump() + return + + self._value_data = cls(**value).dump() + return + raise Exception('unknown config save type %s for config %s' % (type(value), self.name)) value = property(_get_value, _set_value) @@ -902,6 +1018,8 @@ def curType(self): return 'bool' elif self._value_int: return 'int' + elif self._value_data: + return "complex" else: return 'str' diff --git a/xdm/helper.py b/xdm/helper.py index a8c86fe..b1545b7 100644 --- a/xdm/helper.py +++ b/xdm/helper.py @@ -30,6 +30,7 @@ from xdm import common import xdm import shutil +import types import base64 import random @@ -305,8 +306,19 @@ def sameElements(a, b): return False return True -def convertV(cur_v): +def convertV(cur_v, _type): """helper function to convert kwargs to python typed values""" + + # this is a pretty nasty hack around the weird handling below to support lists which do not consist of ["on", "off"] (enabled checkbox). + # it basically is a multiselect scenario + #fixme: rewrite the whole area + if _type == "complex": + if cur_v != "__none__": + cur_v = [cur_v] if type(cur_v) != types.ListType else cur_v + else: + cur_v = [] + + return {"selected": cur_v} try: f = float(cur_v) if f.is_integer(): diff --git a/xdm/web/ajax.py b/xdm/web/ajax.py index a0b2583..3fb8d8b 100755 --- a/xdm/web/ajax.py +++ b/xdm/web/ajax.py @@ -46,7 +46,7 @@ def _globals(self): @cherrypy.expose def pluginCall(self, **kwargs): - log("Plugin ajay call with: %s" % kwargs) + log("Plugin ajax call with: %s" % kwargs) p_type = kwargs['p_type'] p_instance = kwargs['p_instance'] action = kwargs['action'] @@ -58,13 +58,14 @@ def pluginCall(self, **kwargs): log('function %s needs %s' % (action, name)) field_name = 'field_%s' % name if field_name in kwargs: - fn_args.append(convertV(kwargs[field_name])) + fn_args.append(convertV(kwargs[field_name], None)) #fixme: None correct? continue else: log("Field '%s' not found in kwargs. tring array" % field_name) field_name = 'field_%s[]' % name if field_name in kwargs: - fn_args.append(convertV(kwargs[field_name])) + fn_args.append(convertV(kwargs[field_name], None)) #fixme: None correct? + else: log.warning("Field %s not found in kwargs. this will probably not work out" % field_name) @@ -383,8 +384,6 @@ def _save(self, **kwargs): except UnicodeDecodeError: k = k.decode('latin-1') # for some obscure reason cherrypy (i think) encodes param key into latin-1 some times k = k.encode('utf-8') # but i like utf-8 - # print k, repr(k) - # print v, repr(v) log(u"config K:%s V:%s" % (k, v)) parts = k.split('-') @@ -396,6 +395,7 @@ def _save(self, **kwargs): class_name = parts[0] instance_name = parts[1] config_name = parts[2] + config_type = parts[3] if len(parts) > 3 else None _cacheName = "%s %s" % (class_name, instance_name) if _cacheName in _plugin_cache: plugin = _plugin_cache[_cacheName] @@ -404,10 +404,10 @@ def _save(self, **kwargs): _plugin_cache[_cacheName] = plugin if plugin: log(u"We have a plugin: %s (%s)" % (class_name, instance_name)) - new_value = helper.convertV(v) + new_value = helper.convertV(v, config_type) if element is None: # normal settings page old_value = getattr(plugin.c, config_name) - new_value = helper.convertV(v) + new_value = helper.convertV(v, config_type) if old_value == new_value: continue if element is not None: # we got an element id so its an element config @@ -419,7 +419,7 @@ def _save(self, **kwargs): cur_c.value = new_value cur_c.save() else: # normal settings page - setattr(plugin.c, config_name, convertV(v)) # saving value + setattr(plugin.c, config_name, convertV(v, config_type)) # saving value if plugin.config_meta[config_name] and element is None: # this returns none if 'on_change_actions' in plugin.config_meta[config_name] and old_value != new_value: