composer.schema ready for form/dialog handling

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2064 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-09-22 14:37:48 +00:00
parent a4dfb9b209
commit 625d14a68c
6 changed files with 128 additions and 48 deletions

View file

@ -24,17 +24,20 @@ objects.
... pass ... pass
The schema will be connected with an object via an instance adapter. 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.instance import Editor
>>> from cybertools.composer.schema.field import FieldInstance
>>> from zope import component >>> from zope import component
>>> component.provideAdapter(Editor, (Service,), name="service.edit") >>> component.provideAdapter(Editor, (Service,), name="service.edit")
>>> component.provideAdapter(FieldInstance)
>>> srv = Service() >>> srv = Service()
>>> inst = component.getAdapter(srv, name='service.edit') >>> inst = component.getAdapter(srv, name='service.edit')
>>> inst.template = serviceSchema >>> inst.template = serviceSchema
>>> inst.applyTemplate() >>> inst.applyTemplate(data=dict(title='Service', capacity='30'))
title - <...FormState object ...>
description -
start - >>> srv.title, srv.description, srv.capacity
end - (u'Service', u'', u'30')
capacity -

View file

@ -7,12 +7,11 @@
<tal:define define="manageMode manageMode|view/manageMode|nothing; <tal:define define="manageMode manageMode|view/manageMode|nothing;
fields view/fields; fields view/fields;
data view/data"> data view/data">
<table class="listing" style="width: auto"> <table style="width: 100%">
<tal:fields repeat="field fields"> <tal:fields repeat="field fields">
<tal:field define="name field/name; <tal:field define="name field/name;
typeInfo field/getFieldTypeInfo;
errors state/fieldInstances/?name/errors|python: [];"> errors state/fieldInstances/?name/errors|python: [];">
<metal:field use-macro="python: view.schemaMacros[typeInfo.fieldMacro]" /> <metal:field use-macro="python: view.schemaMacros[field.fieldRenderer]" />
</tal:field> </tal:field>
</tal:fields> </tal:fields>
</table> </table>
@ -43,7 +42,7 @@
</td> </td>
<td class="field" style="border-top: none"> <td class="field" style="border-top: none">
<metal:input use-macro="python: <metal:input use-macro="python:
view.schemaMacros[typeInfo.inputMacro]" /> view.schemaMacros[field.inputRenderer]" />
</td> </td>
</tal:field> </tal:field>
</tr> </tr>
@ -61,36 +60,45 @@
<metal:textline define-macro="input_textline"> <metal:textline define-macro="input_textline">
<input type="text" name="field" style="width: 300px" <input type="text" name="field" style="width: 450px"
tal:define="width field/width|python:300" tal:define="width field/width|nothing"
tal:attributes="name name; tal:attributes="name name;
style python: 'width: %ipx' % (width or 300); style python:
value data/?name|field/defaultValue|string:" /> 'width: %s' % (width and str(width)+'px' or '450px');
value data/?name" />
</metal:textline> </metal:textline>
<metal:textarea define-macro="input_textarea"> <metal:textarea define-macro="input_textarea">
<textarea name="field" rows="3" style="width: 300px" <textarea name="field" rows="3" style="width: 450px"
tal:define="width field/width|python:300; tal:define="width field/width|nothing;
height field/height|python:3" height field/height|python:3"
tal:attributes="name name; tal:attributes="name name;
rows python: height or 3; rows python: height or 3;
style python: 'width: %ipx' % (width or 300)" style python:
tal:content="data/?name|field/defaultValue|string:"> 'width: %s' % (width and str(width)+'px' or '450px');"
tal:content="data/?name">
</textarea> </textarea>
</metal:textarea> </metal:textarea>
<metal:upload define-macro="input_fileupload">
<input type="file" name="field"
tal:attributes="name name;" />
</metal:upload>
<metal:dropdown define-macro="input_dropdown"> <metal:dropdown define-macro="input_dropdown">
<select name="field" style="width: 100px" <select name="field" style="width: auto"
tal:define="width field/width|python:100" tal:define="width field/width|nothing"
tal:attributes="name name; tal:attributes="name name;
style python: 'width: %ipx' % (width or 100);"> style python:
'width: %s' % (width and str(width)+'px' or '');">
<option tal:repeat="item field/getVocabularyItems" <option tal:repeat="item field/getVocabularyItems"
tal:content="item/title" tal:content="item/title"
tal:attributes="value item/token; tal:attributes="value item/token;
selected python: selected python:
item['token'] == data.get(name, field.defaultValue)">Mrs</option> item['token'] == data[name]">Mrs</option>
</select> </select>
</metal:dropdown> </metal:dropdown>

View file

@ -40,8 +40,8 @@ class Field(Component):
required = False required = False
standardFieldName = None standardFieldName = None
vocabulary = None vocabulary = None
defaultValue = None
renderFactory = None renderFactory = None
default = None
def __init__(self, name, title=None, fieldType='textline', **kw): def __init__(self, name, title=None, fieldType='textline', **kw):
assert name assert name
@ -57,6 +57,24 @@ class Field(Component):
def name(self): def name(self):
return self.__name__ 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): def getTitleValue(self):
return self.title or self.name return self.title or self.name
@ -66,7 +84,7 @@ class Field(Component):
voc = voc.splitlines() voc = voc.splitlines()
return [dict(token=t, title=t) for t in voc if t.strip()] return [dict(token=t, title=t) for t in voc if t.strip()]
else: 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): def getFieldTypeInfo(self):
return fieldTypes.getTerm(self.fieldType) return fieldTypes.getTerm(self.fieldType)
@ -135,3 +153,12 @@ class NumberFieldInstance(FieldInstance):
int(value) int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
self.setError('invalid_number') self.setError('invalid_number')
class FileUploadFieldInstance(FieldInstance):
def marshall(self, value):
return value
def unmarshall(self, value):
return value

View file

@ -47,14 +47,13 @@ class Instance(BaseInstance):
template = self.template template = self.template
if template is not None: if template is not None:
for f in template.components: for f in template.components:
fieldType = f.getFieldTypeInfo() if not f.storeData:
if not fieldType.storeData:
# a dummy field, e.g. a spacer # a dummy field, e.g. a spacer
continue continue
fi = f.getFieldInstance() fi = f.getFieldInstance()
name = f.name name = f.name
#value = getattr(self.context, name, field.default) value = getattr(self.context, name, f.defaultValue)
value = getattr(self.context, name, u'') #value = getattr(self.context, name, u'')
value = (mode == 'view' and fi.display(value)) or fi.marshall(value) value = (mode == 'view' and fi.display(value)) or fi.marshall(value)
result[name] = value result[name] = value
return result return result
@ -69,12 +68,44 @@ class Editor(BaseInstance):
template = None template = None
def applyTemplate(self, data={}, *args, **kw): def applyTemplate(self, data={}, *args, **kw):
for c in self.template.components: fieldHandlers = kw.get('fieldHandlers', {})
# TODO: implement the real stuff template = self.template
# save data (if available) in context context = self.context
# build sequence of fields with data from context formState = self.validate(data)
# or directly use request... if template is None:
print c.name, getattr(self.context, c.name, '-') 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): class ClientInstance(object):
@ -111,8 +142,7 @@ class ClientInstance(object):
if template is not None: if template is not None:
values = attrs.get(self.aspect, {}) values = attrs.get(self.aspect, {})
for f in template.fields: for f in template.fields:
fieldType = f.getFieldTypeInfo() if not f.storeData:
if not fieldType.storeData:
# a dummy field, e.g. a spacer # a dummy field, e.g. a spacer
continue continue
fi = f.getFieldInstance() fi = f.getFieldInstance()
@ -147,13 +177,12 @@ class ClientInstanceEditor(ClientInstance):
values = attrs.setdefault(self.aspect, OOBTree()) values = attrs.setdefault(self.aspect, OOBTree())
for f in template.fields: for f in template.fields:
name = f.name name = f.name
fieldType = f.getFieldTypeInfo() if not f.storeData:
if not fieldType.storeData:
# a dummy field, e.g. a spacer # a dummy field, e.g. a spacer
continue continue
fi = formState.fieldInstances[name]
value = fi.unmarshall(data.get(name))
if name in data: if name in data:
fi = formState.fieldInstances[name]
value = fi.unmarshall(data.get(name))
oldValue = values.get(name) oldValue = values.get(name)
if value != oldValue: if value != oldValue:
values[name] = value values[name] = value

View file

@ -63,8 +63,8 @@ class FieldType(SimpleTerm):
def __init__(self, value, token=None, title=None, **kw): def __init__(self, value, token=None, title=None, **kw):
super(FieldType, self).__init__(value, token, title) super(FieldType, self).__init__(value, token, title)
self.name = value self.name = value
self.fieldMacro = 'field' self.fieldRenderer = 'field'
self.inputMacro = 'input_' + self.name self.inputRenderer = 'input_' + self.name
self.storeData = True self.storeData = True
self.instanceName = '' self.instanceName = ''
for k, v in kw.items(): for k, v in kw.items():
@ -74,13 +74,15 @@ class FieldType(SimpleTerm):
fieldTypes = SimpleVocabulary(( fieldTypes = SimpleVocabulary((
FieldType('textline', 'textline', u'Textline'), FieldType('textline', 'textline', u'Textline'),
FieldType('textarea', 'textarea', u'Textarea'), FieldType('textarea', 'textarea', u'Textarea'),
FieldType('number', 'number', u'Number', inputMacro='input_textline', FieldType('number', 'number', u'Number',
instanceName='number'), inputRenderer='input_textline', instanceName='number'),
#FieldType('date', 'date', u'Date'), #FieldType('date', 'date', u'Date'),
FieldType('fileupload', 'fileupload', u'File upload',
instanceName='fileupload'),
#FieldType('checkbox', 'checkbox', u'Checkbox'), #FieldType('checkbox', 'checkbox', u'Checkbox'),
FieldType('dropdown', 'dropdown', u'Drop-down selection'), FieldType('dropdown', 'dropdown', u'Drop-down selection'),
FieldType('spacer', 'spacer', u'Spacer', fieldMacro='field_spacer', FieldType('spacer', 'spacer', u'Spacer',
storeData=False), fieldRenderer='field_spacer', storeData=False),
)) ))
standardFieldNames = SimpleVocabulary(( standardFieldNames = SimpleVocabulary((
@ -146,6 +148,17 @@ class IField(IComponent):
'(only for dropdown and other selection fields)'), '(only for dropdown and other selection fields)'),
required=False,) 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 ' renderFactory = Attribute('A class or another factory providing an '
'object used for rendering the data e.g. as a ' 'object used for rendering the data e.g. as a '
'cell on a tabular report. See cybertools.reporter. ' 'cell on a tabular report. See cybertools.reporter. '

View file

@ -47,10 +47,10 @@ def getSchemaFromInterface(ifc, manager):
info = fieldMapping[field.__class__] info = fieldMapping[field.__class__]
voc = getattr(field, 'vocabulary', ()) or getattr(field, 'vocabularyName', None) voc = getattr(field, 'vocabulary', ()) or getattr(field, 'vocabularyName', None)
f = Field(field.getName(), f = Field(field.getName(),
fieldType = info[0], fieldType=info[0],
required=field.required, required=field.required,
default=field.default, default=field.default,
default_method=getattr(field, 'default_method', None), #default_method=getattr(field, 'default_method', None),
vocabulary=voc, vocabulary=voc,
title=field.title, title=field.title,
description=field.description) description=field.description)