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:
parent
a4dfb9b209
commit
625d14a68c
6 changed files with 128 additions and 48 deletions
|
@ -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 -
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
if name in data:
|
||||||
fi = formState.fieldInstances[name]
|
fi = formState.fieldInstances[name]
|
||||||
value = fi.unmarshall(data.get(name))
|
value = fi.unmarshall(data.get(name))
|
||||||
if name in data:
|
|
||||||
oldValue = values.get(name)
|
oldValue = values.get(name)
|
||||||
if value != oldValue:
|
if value != oldValue:
|
||||||
values[name] = value
|
values[name] = value
|
||||||
|
|
|
@ -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. '
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue