{% 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 %}
{{ 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: