diff --git a/composer/schema/browser/schema.py b/composer/schema/browser/schema.py index 72ca606..71bc9b5 100644 --- a/composer/schema/browser/schema.py +++ b/composer/schema/browser/schema.py @@ -32,6 +32,8 @@ from cybertools.composer.schema.interfaces import IClientFactory class SchemaView(BaseView): + formState = None + @Lazy def fields(self): return self.context.fields @@ -41,8 +43,8 @@ class SchemaView(BaseView): return self.getData() def getData(self): + form = self.request.form if not self.clientName: - form = self.request.form self.clientName = form.get('id') clientName = self.clientName if not clientName: @@ -54,6 +56,7 @@ class SchemaView(BaseView): instance = IInstance(client) instance.template = self.context return instance.applyTemplate() + # TODO: overwrite data with values from form def update(self): form = self.request.form @@ -68,12 +71,17 @@ class SchemaView(BaseView): if client is None: return True else: + # if not self.hasData(form) and 'submit' not in form: + # self.request.response.redirect(self.nextUrl()) + # return False client = IClientFactory(manager)() clientName = self.clientName = manager.addClient(client) instance = component.getAdapter(client, IInstance, name='editor') instance.template = self.context - instance.applyTemplate(form) - #return True + self.formState = formState = instance.applyTemplate(form) + if formState.severity > 0: + # show form again + return True self.request.response.redirect(self.nextUrl()) return False diff --git a/composer/schema/field.py b/composer/schema/field.py index e405696..51bd6f4 100644 --- a/composer/schema/field.py +++ b/composer/schema/field.py @@ -17,7 +17,7 @@ # """ -Schema fields +Schema fields and related classes. $Id$ """ @@ -26,24 +26,62 @@ from zope.interface import implements from zope import schema from cybertools.composer.base import Component -from cybertools.composer.schema.interfaces import IField +from cybertools.composer.schema.interfaces import IField, IFieldState +from cybertools.util.format import toStr, toUnicode class Field(Component): implements(IField) + required = False + def __init__(self, name, title=None, renderFactory=None, **kw): assert name self.__name__ = name title = title or u'' self.renderFactory = renderFactory # use for rendering field content super(Field, self).__init__(title, __name__=name, **kw) + self.title = title + for k, v in kw.items(): + setattr(self, k, v) @property def name(self): return self.__name__ - @property - def title(self): + #@property + #def title(self): + # return self.title or self.name + + def getTitleValue(self): return self.title or self.name + + def marshallValue(self, value): + return toStr(value) + + def displayValue(self, value): + return toStr(value) + + def unmarshallValue(self, strValue): + return toUnicode(strValue) or u'' + + def validateValue(self, value): + errors = [] + severity = 0 + if not value and self.required: + errors.append('required_missing') + severity = 5 + return FieldState(self.name, errors, severity) + + +class FieldState(object): + + implements(IFieldState) + + def __init__(self, name, errors=[], severity=0, change=None): + self.name = self.__name__ = name + self.errors = errors + self.severity = severity + self.change = change + diff --git a/composer/schema/instance.py b/composer/schema/instance.py index ba034b2..ad47fd0 100644 --- a/composer/schema/instance.py +++ b/composer/schema/instance.py @@ -29,6 +29,7 @@ from zope.interface import implements from cybertools.composer.instance import Instance from cybertools.composer.interfaces import IInstance from cybertools.composer.schema.interfaces import IClient +from cybertools.composer.schema.schema import FormState class Editor(Instance): @@ -84,7 +85,9 @@ class ClientInstanceEditor(ClientInstance): def applyTemplate(self, data={}, **kw): """ Store the attributes described by self.template (a schema) using corresponding values from the data argument. + Return the resulting form state (an object providing IFormState). """ + formState = FormState() attrs = getattr(self.context, self.attrsName, None) if attrs is None: attrs = OOBTree() @@ -92,8 +95,17 @@ class ClientInstanceEditor(ClientInstance): template = self.template values = attrs.setdefault(self.aspect, OOBTree()) if template is not None: - for c in template.components: - name = c.name + for f in template.fields: + name = f.name + value = f.unmarshallValue(data.get(name)) + fieldState = f.validateValue(value) if name in data: - values[name] = data[name] + oldValue = values.get(name) + if value != oldValue: + values[name] = value + fieldState.change = (oldValue, value) + formState.changed = True + formState.fieldStates.append(fieldState) + formState.severity = max(formState.severity, fieldState.severity) + return formState diff --git a/composer/schema/interfaces.py b/composer/schema/interfaces.py index a53497e..4c44d77 100644 --- a/composer/schema/interfaces.py +++ b/composer/schema/interfaces.py @@ -78,7 +78,7 @@ class IField(IComponent): description=_(u'The type of the field'), required=True, default='textline', - values=('textline', 'textarea', 'date')) + values=('textline', 'textarea', 'date', 'checkbox')) defaultValue = schema.TextLine( title=_(u'Default'), description=_(u'Value with which to pre-set the field contents'), @@ -87,6 +87,61 @@ class IField(IComponent): title=_(u'Required'), description=_(u'Must a value been entered into this field?'), required=False,) + width = schema.Int( + title=_(u'Width'), + description=_(u'The horizontal size of the field in pixels'), + default=300, + required=False,) + height = schema.Int( + title=_(u'Height'), + description=_(u'The vertical size of the field in lines ' + '(only for type textarea)'), + default=3, + required=False,) + # validator = schema.Text(), + # marshaller = schema.Text(), + + def marshallValue(value): + """ Return a string (possibly unicode) representation of the + value given that may be used for editing. + """ + + def displayValue(value): + """ Return a string (possibly unicode) representation of the + value given that may be used for presentation. + """ + + def unmarshallValue(strValue): + """ Return the internal (real) value corresponding to the string + value given. + """ + + def validateValue(value): + """ Check if the value given is valid. Return an object implementing + IFieldState. + """ + + +class IFieldState(Interface): + """ Represents the state of a field used for editing. + """ + + name = Attribute('Field name.') + change = Attribute('A tuple ``(oldValue, newValue)`` or None.') + errors = Attribute('A sequence of error infos.') + severity = Attribute("An integer giving a state or error " + "code, 0 meaning 'OK'.") + + +class IFormState(Interface): + """ Represents the state of all fields when editing. + """ + + fieldStates = Attribute('A mapping ``{fieldName: fieldState, ...}``.') + changed = Attribute('True if one of the fields has been changed or False.') + severity = Attribute("An integer giving an overall state or error " + "code, typically the maximum of the field states' " + "severities.") # clients diff --git a/composer/schema/schema.py b/composer/schema/schema.py index 8fb28d9..9a6b1eb 100644 --- a/composer/schema/schema.py +++ b/composer/schema/schema.py @@ -26,7 +26,8 @@ from zope.interface import implements from cybertools.composer.base import Component, Element, Compound from cybertools.composer.base import Template -from cybertools.composer.schema.interfaces import ISchema +from cybertools.composer.schema.interfaces import ISchema, IFormState +from cybertools.util.jeep import Jeep class Schema(Template): @@ -57,3 +58,14 @@ class Schema(Template): def getManager(self): return self.manager + + +class FormState(object): + + implements(IFormState) + + def __init__(self, fieldStates=[], changed=False, severity=0): + self.fieldStates = Jeep(fieldStates) + self.changed = changed + self.severity = severity + diff --git a/util/format.py b/util/format.py index 9bd984a..660430a 100644 --- a/util/format.py +++ b/util/format.py @@ -48,3 +48,16 @@ def formatNumber(num, type='decimal', lang='de'): fmt = de.numbers.getFormatter(type) return fmt.format(num) + +def toStr(value, encoding='UTF-8'): + if isinstance(value, unicode): + return value.encode(encoding) + return str(value) + +def toUnicode(value, encoding='UTF-8'): + if isinstance(value, unicode): + return value + elif isinstance(value, str): + return value.decode(encoding) + else: + return u''