diff --git a/composer/schema/README.txt b/composer/schema/README.txt index 348db3d..3943641 100644 --- a/composer/schema/README.txt +++ b/composer/schema/README.txt @@ -24,17 +24,20 @@ objects. ... pass The schema will be connected with an object via an instance adapter. +In addition, we need a field instance adapter that cares for the +correct conversion of input data to context attributes. >>> from cybertools.composer.schema.instance import Editor + >>> from cybertools.composer.schema.field import FieldInstance >>> from zope import component >>> component.provideAdapter(Editor, (Service,), name="service.edit") + >>> component.provideAdapter(FieldInstance) >>> srv = Service() >>> inst = component.getAdapter(srv, name='service.edit') >>> inst.template = serviceSchema - >>> inst.applyTemplate() - title - - description - - start - - end - - capacity - + >>> inst.applyTemplate(data=dict(title='Service', capacity='30')) + <...FormState object ...> + + >>> srv.title, srv.description, srv.capacity + (u'Service', u'', u'30') diff --git a/composer/schema/browser/schema_macros.pt b/composer/schema/browser/schema_macros.pt index a3d8b4f..c50d19f 100755 --- a/composer/schema/browser/schema_macros.pt +++ b/composer/schema/browser/schema_macros.pt @@ -7,12 +7,11 @@ - +
- +
@@ -43,7 +42,7 @@ + view.schemaMacros[field.inputRenderer]" /> @@ -61,36 +60,45 @@ - + style python: + 'width: %s' % (width and str(width)+'px' or '450px'); + value data/?name" /> - + + + + + - diff --git a/composer/schema/field.py b/composer/schema/field.py index 2e18d31..b661c5d 100644 --- a/composer/schema/field.py +++ b/composer/schema/field.py @@ -40,8 +40,8 @@ class Field(Component): required = False standardFieldName = None vocabulary = None - defaultValue = None renderFactory = None + default = None def __init__(self, name, title=None, fieldType='textline', **kw): assert name @@ -57,6 +57,24 @@ class Field(Component): def name(self): return self.__name__ + @property + def defaultValue(self): + if callable(self.default): + return self.default() + return self.default + + @property + def fieldRenderer(self): + return self.getFieldTypeInfo().fieldRenderer + + @property + def inputRenderer(self): + return self.getFieldTypeInfo().inputRenderer + + @property + def storeData(self): + return self.getFieldTypeInfo().storeData + def getTitleValue(self): return self.title or self.name @@ -66,7 +84,7 @@ class Field(Component): voc = voc.splitlines() return [dict(token=t, title=t) for t in voc if t.strip()] else: - return [dict(token=t.token, title=t.title) for t in voc] + return [dict(token=t.token, title=t.title or t.value) for t in voc] def getFieldTypeInfo(self): return fieldTypes.getTerm(self.fieldType) @@ -135,3 +153,12 @@ class NumberFieldInstance(FieldInstance): int(value) except (TypeError, ValueError): self.setError('invalid_number') + + +class FileUploadFieldInstance(FieldInstance): + + def marshall(self, value): + return value + + def unmarshall(self, value): + return value diff --git a/composer/schema/instance.py b/composer/schema/instance.py index 747e87d..0156d39 100644 --- a/composer/schema/instance.py +++ b/composer/schema/instance.py @@ -47,14 +47,13 @@ class Instance(BaseInstance): template = self.template if template is not None: for f in template.components: - fieldType = f.getFieldTypeInfo() - if not fieldType.storeData: + if not f.storeData: # a dummy field, e.g. a spacer continue fi = f.getFieldInstance() name = f.name - #value = getattr(self.context, name, field.default) - value = getattr(self.context, name, u'') + value = getattr(self.context, name, f.defaultValue) + #value = getattr(self.context, name, u'') value = (mode == 'view' and fi.display(value)) or fi.marshall(value) result[name] = value return result @@ -69,12 +68,44 @@ class Editor(BaseInstance): template = None def applyTemplate(self, data={}, *args, **kw): - for c in self.template.components: - # TODO: implement the real stuff - # save data (if available) in context - # build sequence of fields with data from context - # or directly use request... - print c.name, getattr(self.context, c.name, '-') + fieldHandlers = kw.get('fieldHandlers', {}) + template = self.template + context = self.context + formState = self.validate(data) + if template is None: + return formState + if formState.severity > 0: + # don't do anything if there is an error + return formState + for f in template.components: + if not f.storeData: + # a dummy field, e.g. a spacer + continue + name = f.name + ftype = f.fieldType + fi = formState.fieldInstances[name] + value = fi.unmarshall(data.get(name, u'')) + if ftype in fieldHandlers: # caller wants special treatment of field + fieldHandlers[ftype](context, value, fi, formState) + else: + oldValue = getattr(context, name, None) + if value != oldValue: + setattr(context, name, value) + fi.change = (oldValue, value) + formState.changed = True + return formState + + def validate(self, data): + formState = FormState() + if self.template is None: + return formState + for f in self.template.fields: + fi = f.getFieldInstance() + value = data.get(f.name) + fi.validate(value) + formState.fieldInstances.append(fi) + formState.severity = max(formState.severity, fi.severity) + return formState class ClientInstance(object): @@ -111,8 +142,7 @@ class ClientInstance(object): if template is not None: values = attrs.get(self.aspect, {}) for f in template.fields: - fieldType = f.getFieldTypeInfo() - if not fieldType.storeData: + if not f.storeData: # a dummy field, e.g. a spacer continue fi = f.getFieldInstance() @@ -147,13 +177,12 @@ class ClientInstanceEditor(ClientInstance): values = attrs.setdefault(self.aspect, OOBTree()) for f in template.fields: name = f.name - fieldType = f.getFieldTypeInfo() - if not fieldType.storeData: + if not f.storeData: # a dummy field, e.g. a spacer continue - fi = formState.fieldInstances[name] - value = fi.unmarshall(data.get(name)) if name in data: + fi = formState.fieldInstances[name] + value = fi.unmarshall(data.get(name)) oldValue = values.get(name) if value != oldValue: values[name] = value diff --git a/composer/schema/interfaces.py b/composer/schema/interfaces.py index 299e086..143e6f5 100644 --- a/composer/schema/interfaces.py +++ b/composer/schema/interfaces.py @@ -63,8 +63,8 @@ class FieldType(SimpleTerm): def __init__(self, value, token=None, title=None, **kw): super(FieldType, self).__init__(value, token, title) self.name = value - self.fieldMacro = 'field' - self.inputMacro = 'input_' + self.name + self.fieldRenderer = 'field' + self.inputRenderer = 'input_' + self.name self.storeData = True self.instanceName = '' for k, v in kw.items(): @@ -74,13 +74,15 @@ class FieldType(SimpleTerm): fieldTypes = SimpleVocabulary(( FieldType('textline', 'textline', u'Textline'), FieldType('textarea', 'textarea', u'Textarea'), - FieldType('number', 'number', u'Number', inputMacro='input_textline', - instanceName='number'), + FieldType('number', 'number', u'Number', + inputRenderer='input_textline', instanceName='number'), #FieldType('date', 'date', u'Date'), + FieldType('fileupload', 'fileupload', u'File upload', + instanceName='fileupload'), #FieldType('checkbox', 'checkbox', u'Checkbox'), FieldType('dropdown', 'dropdown', u'Drop-down selection'), - FieldType('spacer', 'spacer', u'Spacer', fieldMacro='field_spacer', - storeData=False), + FieldType('spacer', 'spacer', u'Spacer', + fieldRenderer='field_spacer', storeData=False), )) standardFieldNames = SimpleVocabulary(( @@ -146,6 +148,17 @@ class IField(IComponent): '(only for dropdown and other selection fields)'), required=False,) + fieldRenderer = Attribute('Name of a renderer (i.e. a ZPT macro or ' + 'an adapter) that is responsible for rendering ' + '(presenting) the field as a whole.') + inputRenderer = Attribute('Name of a renderer (i.e. a ZPT macro or ' + 'an adapter) that is responsible for rendering ' + '(presenting) the part of the field that allows ' + 'data input.') + storeData = Attribute('Boolean value, true when this field provides ' + 'data that may be stored in a context object, ' + 'false for dummy fields like spacers.') + renderFactory = Attribute('A class or another factory providing an ' 'object used for rendering the data e.g. as a ' 'cell on a tabular report. See cybertools.reporter. ' diff --git a/composer/schema/util.py b/composer/schema/util.py index 5c38c8e..a81e4e9 100644 --- a/composer/schema/util.py +++ b/composer/schema/util.py @@ -47,10 +47,10 @@ def getSchemaFromInterface(ifc, manager): info = fieldMapping[field.__class__] voc = getattr(field, 'vocabulary', ()) or getattr(field, 'vocabularyName', None) f = Field(field.getName(), - fieldType = info[0], + fieldType=info[0], required=field.required, default=field.default, - default_method=getattr(field, 'default_method', None), + #default_method=getattr(field, 'default_method', None), vocabulary=voc, title=field.title, description=field.description)