formdef: add support for import/export of complex workflow options (#14043) #982

Merged
fpeters merged 1 commits from wip/14043-import-export-complex-form-options into main 2024-01-12 15:14:55 +01:00
2 changed files with 76 additions and 33 deletions

View File

@ -282,6 +282,28 @@ def test_workflow_options_with_boolean(pub):
assert formdef.workflow_options['foo'] == fd2.workflow_options['foo']
def test_workflow_options_with_int(pub):
formdef = FormDef()
formdef.name = 'foo'
formdef.workflow_options = {'foo': 123}
fd2 = assert_xml_import_export_works(formdef)
assert formdef.workflow_options['foo'] == fd2.workflow_options['foo']
def test_workflow_options_with_list(pub):
formdef = FormDef()
formdef.name = 'foo'
formdef.workflow_options = {
'foo': ['a', 'b', 'c'],
'foo2': [True, False],
'foo3': [{'id': 1, 'text': 'blah'}],
}
fd2 = assert_xml_import_export_works(formdef)
assert formdef.workflow_options['foo'] == fd2.workflow_options['foo']
assert formdef.workflow_options['foo2'] == fd2.workflow_options['foo2']
assert formdef.workflow_options['foo3'] == fd2.workflow_options['foo3']
def test_workflow_reference(pub):
Workflow.wipe()
FormDef.wipe()

View File

@ -1312,30 +1312,42 @@ class FormDef(StorableObject):
if sub is not None:
sub.attrib['role_key'] = role_key
def make_xml_value(element, value):
if isinstance(value, str):
element.text = value
elif hasattr(value, 'base_filename'):
element.attrib['type'] = 'file'
ET.SubElement(element, 'filename').text = value.base_filename
ET.SubElement(element, 'content_type').text = value.content_type or 'application/octet-stream'
ET.SubElement(element, 'content').text = force_str(base64.b64encode(value.get_content()))
elif isinstance(value, time.struct_time):
element.text = time.strftime('%Y-%m-%d', value)
element.attrib['type'] = 'date'
elif isinstance(value, bool):
element.text = 'true' if value else 'false'
element.attrib['type'] = 'bool'
elif isinstance(value, int):
element.attrib['type'] = 'int'
element.text = str(value)
elif isinstance(value, (set, tuple, list)):
element.attrib['type'] = 'list'
for child_value in value:
sub_element = ET.SubElement(element, 'item')
make_xml_value(sub_element, child_value)
elif isinstance(value, dict):
element.attrib['type'] = 'dict'
for child_key, child_value in value.items():
sub_element = ET.SubElement(element, child_key)
make_xml_value(sub_element, child_value)
else:
assert value is None, 'option variable of unknown type (%s)' % type(value)
options = ET.SubElement(root, 'options')
for option in sorted(self.workflow_options or []):
element = ET.SubElement(options, 'option')
element.attrib['varname'] = option
option_value = self.workflow_options.get(option)
if isinstance(option_value, str):
element.text = force_str(self.workflow_options.get(option, ''), charset)
elif hasattr(option_value, 'base_filename'):
element.attrib['type'] = 'file'
ET.SubElement(element, 'filename').text = option_value.base_filename
ET.SubElement(element, 'content_type').text = (
option_value.content_type or 'application/octet-stream'
)
ET.SubElement(element, 'content').text = force_str(
base64.b64encode(option_value.get_content())
)
elif isinstance(option_value, time.struct_time):
element.text = time.strftime('%Y-%m-%d', option_value)
element.attrib['type'] = 'date'
elif isinstance(option_value, bool):
element.text = 'true' if option_value else 'false'
element.attrib['type'] = 'bool'
else:
pass # TODO: extend support to other types
make_xml_value(element, option_value)
custom_views_element = ET.SubElement(root, 'custom_views')
if hasattr(self, '_custom_views'):
@ -1471,21 +1483,30 @@ class FormDef(StorableObject):
]
formdef.workflow_options = {}
for option in tree.findall('options/option'):
option_value = None
if option.attrib.get('type') == 'date':
option_value = time.strptime(option.text, '%Y-%m-%d')
elif option.attrib.get('type') == 'bool':
option_value = bool(option.text == 'true')
elif option.attrib.get('type') == 'file' or option.findall('filename'):
option_value = PicklableUpload(
orig_filename=xml_node_text(option.find('filename')),
content_type=xml_node_text(option.find('content_type')),
def get_value_from_xml(element):
if element.attrib.get('type') == 'int':
return int(xml_node_text(element))
if element.attrib.get('type') == 'date':
return time.strptime(element.text, '%Y-%m-%d')
elif element.attrib.get('type') == 'bool':
return bool(element.text == 'true')
elif element.attrib.get('type') == 'file' or element.findall('filename'):
value = PicklableUpload(
orig_filename=xml_node_text(element.find('filename')),
content_type=xml_node_text(element.find('content_type')),
)
option_value.receive([base64.decodebytes(force_bytes(xml_node_text(option.find('content'))))])
elif option.text:
option_value = xml_node_text(option)
formdef.workflow_options[option.attrib.get('varname')] = option_value
value.receive([base64.decodebytes(force_bytes(xml_node_text(element.find('content'))))])
return value
elif element.attrib.get('type') == 'list':
return [get_value_from_xml(x) for x in element.findall('item')]
elif element.attrib.get('type') == 'dict':
return {x.tag: get_value_from_xml(x) for x in element.findall('*')}
elif element.text:
return xml_node_text(element)
for option in tree.findall('options/option'):
formdef.workflow_options[option.attrib.get('varname')] = get_value_from_xml(option)
formdef._custom_views = []
for view in tree.findall('custom_views/%s' % get_publisher().custom_view_class.xml_root_node):