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
|
||||
|
||||
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')
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. '
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue