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
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')

View file

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

View file

@ -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

View file

@ -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

View file

@ -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. '

View file

@ -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)