wcs/tests/test_formdef_import.py

700 lines
25 KiB
Python

# -*- coding: utf-8 -*-
import pytest
import shutil
import time
import xml.etree.ElementTree as ET
from django.utils.six import BytesIO, StringIO
from quixote import cleanup
from wcs.categories import Category
from wcs.carddef import CardDef
from wcs.formdef import FormDef, fields, FormdefImportError
from wcs.workflows import Workflow
from wcs.roles import Role
from wcs.qommon.misc import indent_xml as indent
from utilities import create_temporary_pub
def setup_module(module):
cleanup()
global pub
pub = create_temporary_pub()
def teardown_module(module):
shutil.rmtree(pub.APP_DIR)
def export_to_indented_xml(formdef, include_id=False):
formdef_xml = ET.fromstring(ET.tostring(formdef.export_to_xml(include_id=include_id)))
indent(formdef_xml)
return formdef_xml
def assert_compare_formdef(formdef1, formdef2, include_id=False):
assert ET.tostring(export_to_indented_xml(formdef1, include_id=include_id)) == \
ET.tostring(export_to_indented_xml(formdef2, include_id=include_id))
assert formdef1.export_to_json(include_id=include_id, indent=2) == \
formdef2.export_to_json(include_id=include_id, indent=2)
def assert_xml_import_export_works(formdef, include_id=False):
formdef_xml = formdef.export_to_xml(include_id=include_id)
formdef2 = FormDef.import_from_xml_tree(formdef_xml, include_id=include_id)
assert_compare_formdef(formdef, formdef2, include_id=include_id)
return formdef2
def assert_json_import_export_works(formdef, include_id=False):
formdef2 = FormDef.import_from_json(
StringIO(formdef.export_to_json(include_id=include_id)), include_id=include_id)
assert_compare_formdef(formdef, formdef2, include_id=include_id)
return formdef2
def test_empty():
formdef = FormDef()
formdef.name = 'empty'
assert_xml_import_export_works(formdef)
assert_json_import_export_works(formdef)
def test_text_attributes():
formdef = FormDef()
formdef.name = 'Foo'
formdef.url_name = 'foo'
f2 = assert_xml_import_export_works(formdef)
assert f2.url_name == formdef.url_name
f2 = assert_json_import_export_works(formdef)
assert f2.url_name == formdef.url_name
def test_empty_description_tag():
formdef = FormDef()
formdef.name = 'empty'
assert_xml_import_export_works(formdef)
export = ET.tostring(export_to_indented_xml(formdef))
# add empty description tag
export = export.replace(b'<name>empty</name>', b'<name>empty</name><description></description>')
formdef2 = FormDef.import_from_xml_tree(ET.fromstring(export))
assert not formdef2.description
def test_boolean_attributes():
formdef = FormDef()
formdef.name = 'Foo'
formdef.url_name = 'foo'
formdef.confirmation = True
formdef.enable_tracking_codes = True
f2 = assert_xml_import_export_works(formdef)
assert f2.enable_tracking_codes == formdef.enable_tracking_codes
assert f2.confirmation == formdef.confirmation
f2 = assert_json_import_export_works(formdef)
assert f2.enable_tracking_codes == formdef.enable_tracking_codes
assert f2.confirmation == formdef.confirmation
def test_a_field():
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40')
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
f2 = assert_json_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
def test_more_fields():
formdef = FormDef()
formdef.name = 'Blah'
formdef.fields = [
fields.TextField(type='text', label='Bar', pre=True),
fields.EmailField(type='email', label='Bar'),
fields.BoolField(type='bool', label='Bar'),
fields.DateField(type='date', label='Bar', minimum_date='2014-01-01'),
fields.ItemField(type='item', label='Bar', items=['foo', 'bar', 'baz']),
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[2].type == formdef.fields[2].type
assert f2.fields[3].minimum_date == formdef.fields[3].minimum_date
assert f2.fields[4].items == formdef.fields[4].items
f2 = assert_json_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[2].type == formdef.fields[2].type
assert f2.fields[3].minimum_date == formdef.fields[3].minimum_date
assert f2.fields[4].items == formdef.fields[4].items
def test_item_radio():
formdef = FormDef()
formdef.name = 'Blah'
formdef.fields = [
fields.ItemField(
type='item',
label='Bar',
items=['foo', 'bar', 'baz'],
list_mode='radio',
id='1',
),
]
# test new mode
assert_json_import_export_works(formdef, include_id=True)
# test conversion of legacy show_as_radio attribute
formdef_xml = formdef.export_to_xml(include_id=True)
field = formdef_xml.findall('fields/field')[0]
ET.SubElement(field, 'show_as_radio').text = 'True'
field.remove(field.find('display_mode'))
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
assert fd2.fields[0].display_mode == 'radio'
# test conversion of legacy show_as_radio attribute
formdef_xml = formdef.export_to_xml(include_id=True)
field = formdef_xml.findall('fields/field')[0]
ET.SubElement(field, 'show_as_radio').text = 'False'
field.remove(field.find('display_mode'))
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
assert fd2.fields[0].display_mode == 'list'
def test_include_id():
formdef = FormDef()
formdef.name = 'Blah'
formdef.fields = [
fields.TextField(type='text', label='Bar', pre=True),
fields.EmailField(type='email', label='Bar'),
fields.BoolField(type='bool', label='Bar'),
fields.DateField(type='date', label='Bar', minimum_date='2014-01-01'),
fields.ItemField(type='item', label='Bar', items=['foo', 'bar', 'baz']),
]
for field in formdef.fields:
field.id = formdef.get_new_field_id()
formdef.fields[4].id = '10'
f2 = assert_xml_import_export_works(formdef, include_id=True)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].id == formdef.fields[0].id
assert f2.fields[4].id == formdef.fields[4].id
f2 = assert_json_import_export_works(formdef, include_id=True)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].id == formdef.fields[0].id
assert f2.fields[4].id == formdef.fields[4].id
def test_modification_time():
formdef = FormDef()
formdef.name = 'empty'
formdef.last_modification_time = time.localtime()
assert_xml_import_export_works(formdef)
f2 = assert_json_import_export_works(formdef)
assert tuple(f2.last_modification_time)[:6] == tuple(formdef.last_modification_time)[:6]
def test_workflow_options():
formdef = FormDef()
formdef.name = 'workflow options'
formdef.workflow_options = {'foo': 'bar',
'foo2': 'baré'}
fd2 = assert_xml_import_export_works(formdef)
assert fd2.workflow_options == formdef.workflow_options
fd2 = assert_json_import_export_works(formdef)
assert fd2.workflow_options == formdef.workflow_options
def test_workflow_options_with_no_values():
formdef = FormDef()
formdef.name = 'foo'
formdef.workflow_options = {'foo': None,
'foo2': None}
fd2 = assert_xml_import_export_works(formdef)
assert fd2.workflow_options == formdef.workflow_options
fd2 = assert_json_import_export_works(formdef)
assert fd2.workflow_options == formdef.workflow_options
def test_workflow_options_with_file():
from quixote.http_request import Upload
from wcs.qommon.form import UploadedFile
upload = Upload('/foo/bar', content_type='application/vnd.oasis.opendocument.text')
file_content = b'''PK\x03\x04\x14\x00\x00\x08\x00\x00\'l\x8eG^\xc62\x0c\'\x00'''
upload.fp = BytesIO()
upload.fp.write(file_content)
upload.fp.seek(0)
model_file = UploadedFile(pub.APP_DIR, None, upload)
formdef = FormDef()
formdef.name = 'foo'
formdef.workflow_options = {'foo': model_file}
fd2 = assert_xml_import_export_works(formdef)
assert formdef.workflow_options['foo'].filename == fd2.workflow_options['foo'].filename
assert formdef.workflow_options['foo'].get_content() == fd2.workflow_options['foo'].get_content()
fd2 = assert_json_import_export_works(formdef)
assert formdef.workflow_options['foo'].filename == fd2.workflow_options['foo'].filename
assert formdef.workflow_options['foo'].get_content() == fd2.workflow_options['foo'].get_content()
def test_workflow_options_with_date():
formdef = FormDef()
formdef.name = 'foo'
formdef.workflow_options = {'foo': time.strptime('2014-02-02', '%Y-%m-%d')}
fd2 = assert_xml_import_export_works(formdef)
assert formdef.workflow_options['foo'] == fd2.workflow_options['foo']
def test_workflow_reference():
Workflow.wipe()
FormDef.wipe()
wf = Workflow()
wf.name = 'test workflow'
wf.store()
formdef = FormDef()
formdef.name = 'foo'
formdef.workflow_id = wf.id
f2 = assert_xml_import_export_works(formdef)
assert f2.workflow_id == formdef.workflow_id
f2 = assert_xml_import_export_works(formdef, include_id=True)
assert f2.workflow_id == formdef.workflow_id
formdef_xml_with_id = formdef.export_to_xml(include_id=True)
formdef_xml_without_id = formdef.export_to_xml(include_id=False)
# check there's no reference to a non-existing workflow
Workflow.wipe()
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).workflow_id is None
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).workflow_id is None
# check an import that is not using id fields will find the workflow by its
# name
wf = Workflow()
wf.id = '2'
wf.name = 'test workflow'
wf.store()
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).workflow_id == '2'
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).workflow_id is None
def test_category_reference():
Category.wipe()
FormDef.wipe()
cat = Category()
cat.name = 'test category'
cat.store()
formdef = FormDef()
formdef.name = 'foo'
formdef.category_id = cat.id
f2 = assert_xml_import_export_works(formdef)
assert f2.category_id == formdef.category_id
f2 = assert_xml_import_export_works(formdef, include_id=True)
assert f2.category_id == formdef.category_id
formdef_xml_with_id = formdef.export_to_xml(include_id=True)
formdef_xml_without_id = formdef.export_to_xml(include_id=False)
# check there's no reference to a non-existing category
Category.wipe()
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).category_id is None
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).category_id is None
# check an import that is not using id fields will find the category by its
# name
cat = Category()
cat.id = '2'
cat.name = 'test category'
cat.store()
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).category_id == '2'
assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).category_id is None
def test_file_field():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [fields.FileField(type='file', id='1', document_type={
'id': 'justificatif-de-domicile',
'fargo': True,
'mimetypes': ['application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/*'],
})]
assert_xml_import_export_works(formdef, include_id=True)
assert_xml_import_export_works(formdef)
assert_json_import_export_works(formdef, include_id=True)
assert_json_import_export_works(formdef)
def test_invalid_field_type():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [fields.StringField(id='1', type='XXX')]
export = ET.tostring(export_to_indented_xml(formdef))
with pytest.raises(FormdefImportError):
FormDef.import_from_xml(BytesIO(export), include_id=True)
def test_invalid_data_source():
# manually edited exports
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'xxx'})]
export = ET.tostring(export_to_indented_xml(formdef))
export = export.replace(b'\n', b'').replace(b' ', b'')
export = export.replace(b'<data_source><type>xxx</type></data_source>', b'<data_source><type/></data_source>')
formdef2 = FormDef.import_from_xml(BytesIO(export))
assert formdef2.fields[0].data_source == {}
export = export.replace(b'<data_source><type/></data_source>', b'<data_source> </data_source>')
formdef2 = FormDef.import_from_xml(BytesIO(export))
assert formdef2.fields[0].data_source == {}
def test_unknown_data_source():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'json', 'value': 'http://example.net'})]
export = ET.tostring(export_to_indented_xml(formdef))
FormDef.import_from_xml(BytesIO(export))
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'foobar'})]
export = ET.tostring(export_to_indented_xml(formdef))
with pytest.raises(FormdefImportError, match='Unknown datasources'):
FormDef.import_from_xml(BytesIO(export))
# carddef as datasource
carddef = CardDef()
carddef.name = 'foo'
carddef.fields = [
fields.StringField(id='1', label='Test', type='string', varname='foo'),
]
carddef.store()
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'carddef:foo'})]
export = ET.tostring(export_to_indented_xml(formdef))
FormDef.import_from_xml(BytesIO(export))
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'carddef:unknown'})]
export = ET.tostring(export_to_indented_xml(formdef))
with pytest.raises(FormdefImportError):
FormDef.import_from_xml(BytesIO(export))
# carddef custom view as datasource
pub.custom_view_class.wipe()
custom_view = pub.custom_view_class()
custom_view.title = 'card view'
custom_view.formdef = carddef
custom_view.columns = {'list': [{'id': 'id'}]}
custom_view.filters = {}
custom_view.visibility = 'datasource'
custom_view.store()
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'carddef:foo:card-view'})]
export = ET.tostring(export_to_indented_xml(formdef))
FormDef.import_from_xml(BytesIO(export))
formdef.fields = [fields.StringField(
id='1', type='string',
data_source={'type': 'carddef:foo:unknown'})]
export = ET.tostring(export_to_indented_xml(formdef))
with pytest.raises(FormdefImportError):
FormDef.import_from_xml(BytesIO(export))
def test_duplicated_field_ids():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [
fields.StringField(id='2', type='string'),
fields.StringField(id='2', type='string'),
fields.StringField(id='1', type='string')]
export = ET.tostring(export_to_indented_xml(formdef, include_id=True))
with pytest.raises(FormdefImportError):
FormDef.import_from_xml(BytesIO(export))
with pytest.raises(FormdefImportError):
FormDef.import_from_xml(BytesIO(export), include_id=True)
formdef2 = FormDef.import_from_xml(BytesIO(export), fix_on_error=True)
assert formdef2.fields[0].id == '1'
assert formdef2.fields[1].id == '2'
assert formdef2.fields[2].id == '3'
def test_wrong_max_field_id():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [
fields.StringField(id='1', type='string'),
fields.StringField(id='2', type='string'),]
formdef.max_field_id = 1
export = ET.tostring(export_to_indented_xml(formdef, include_id=True))
formdef2 = FormDef.import_from_xml(BytesIO(export), include_id=True)
assert formdef2.max_field_id == 2
def test_page_condition():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [
fields.PageField(id='1', type='page',
condition={'type': 'python', 'value': 'blah'})
]
fd2 = assert_xml_import_export_works(formdef)
assert fd2.fields[0].type == 'page'
assert fd2.fields[0].condition == formdef.fields[0].condition
# test legacy condition (simple string)
formdef_xml = formdef.export_to_xml(include_id=True)
condition = formdef_xml.findall('fields/field/condition')[0]
condition.clear()
condition.text = 'blah'
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
assert fd2.fields[0].condition == formdef.fields[0].condition
def test_page_post_conditions():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = [
fields.PageField(id='1', type='page',
post_conditions=[{'condition': {'type': 'python', 'value': 'blah'}, 'error_message': 'bar'}]),
]
fd2 = assert_xml_import_export_works(formdef)
assert fd2.fields[0].type == 'page'
assert fd2.fields[0].post_conditions == formdef.fields[0].post_conditions
# test legacy condition (simple string)
formdef_xml = formdef.export_to_xml(include_id=True)
post_condition = formdef_xml.findall('fields/field/post_conditions/post_condition/condition')[0]
post_condition.clear()
post_condition.text = 'blah'
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
assert fd2.fields[0].post_conditions[0]['condition'] == {'type': 'python', 'value': 'blah'}
assert fd2.fields[0].post_conditions[0]['error_message'] == 'bar'
# test incomplete post condition (not allowed anymore but old formdefs may
# have this)
formdef.fields = [
fields.PageField(id='1', type='page',
post_conditions=[{'condition': {'type': 'python', 'value': 'blah'}, 'error_message': None}]),
]
formdef_xml = formdef.export_to_xml(include_id=True)
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
assert fd2.fields[0].post_conditions[0]['condition'] == {'type': 'python', 'value': 'blah'}
assert fd2.fields[0].post_conditions[0]['error_message'] == ''
def test_workflow_roles():
Role.wipe()
role = Role(name='blah')
role.store()
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = []
formdef.workflow_roles = {'_receiver': role.id}
fd2 = assert_xml_import_export_works(formdef, include_id=True)
assert fd2.workflow_roles.get('_receiver') == role.id
fd2 = assert_xml_import_export_works(formdef, include_id=False)
assert fd2.workflow_roles.get('_receiver') == role.id
xml_export = export_to_indented_xml(formdef, include_id=True)
xml_export_no_id = export_to_indented_xml(formdef, include_id=False)
# same id, different name
role.name = 'blah 2'
role.store()
fd2 = FormDef.import_from_xml_tree(xml_export, include_id=True)
assert fd2.workflow_roles.get('_receiver') == role.id
fd2 = FormDef.import_from_xml_tree(xml_export, include_id=False)
assert fd2.workflow_roles.get('_receiver') is None
role.remove_self()
fd2 = FormDef.import_from_xml_tree(xml_export, include_id=True)
assert fd2.workflow_roles.get('_receiver') is None
def test_geolocations():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = []
formdef.geolocations = {'base': 'Base'}
fd2 = assert_xml_import_export_works(formdef, include_id=True)
assert fd2.geolocations == formdef.geolocations
fd3 = assert_json_import_export_works(formdef)
assert fd3.geolocations == formdef.geolocations
def test_user_roles():
Role.wipe()
role = Role(name='blah')
role.store()
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = []
formdef.roles = ['logged-users', role.id]
fd2 = assert_xml_import_export_works(formdef, include_id=True)
assert fd2.roles == formdef.roles
formdef_xml = formdef.export_to_xml(include_id=True)
formdef_xml_no_id = formdef.export_to_xml(include_id=False)
role.remove_self()
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
assert fd2.roles == ['logged-users']
fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=False)
assert fd2.roles == ['logged-users']
fd2 = FormDef.import_from_xml_tree(formdef_xml_no_id, include_id=True)
assert fd2.roles == ['logged-users']
fd2 = FormDef.import_from_xml_tree(formdef_xml_no_id, include_id=False)
assert fd2.roles == ['logged-users']
def test_backoffice_submission_roles():
Role.wipe()
role = Role(name='blah')
role.store()
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = []
formdef.backoffice_submission_roles = [role.id]
fd2 = assert_xml_import_export_works(formdef, include_id=True)
assert fd2.backoffice_submission_roles == formdef.backoffice_submission_roles
def test_required_authentication_contexts():
formdef = FormDef()
formdef.name = 'foo'
formdef.fields = []
formdef.required_authentication_contexts = ['fedict']
fd2 = assert_xml_import_export_works(formdef, include_id=True)
assert fd2.required_authentication_contexts == formdef.required_authentication_contexts
def test_field_condition():
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40',
condition={'type': 'django', 'value': '1'})
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].condition == {'type': 'django', 'value': '1'}
def test_field_validation():
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40', validation={})
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert not f2.fields[0].validation
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40', validation=None)
]
formdef_xml = formdef.export_to_xml()
f2 = FormDef.import_from_xml_tree(formdef_xml)
assert len(f2.fields) == len(formdef.fields)
assert not f2.fields[0].validation
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40',
validation={'type': 'regex', 'value': r'\d'})
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].validation == formdef.fields[0].validation
# backward compatibility
formdef_xml = formdef.export_to_xml()
old_format = ET.tostring(formdef_xml).replace(
b'<validation><type>regex</type><value>\\d</value></validation>',
b'<validation>\\d</validation>')
f2 = FormDef.import_from_xml(BytesIO(old_format))
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].validation == {'type': 'regex', 'value': '\\d'}
def test_digest_template():
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = []
formdef.digest_template = '{{form_number}}'
f2 = assert_xml_import_export_works(formdef)
assert f2.digest_template == formdef.digest_template
def test_field_prefill():
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40',
prefill={'type': 'string', 'value': 'plop'})
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].prefill == {'type': 'string', 'value': 'plop'}
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40',
prefill={'type': 'string', 'value': 'plop', 'locked': True})
]
f2 = assert_xml_import_export_works(formdef)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].prefill == {'type': 'string', 'value': 'plop', 'locked': True}
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40',
prefill={'type': 'string', 'value': 'plop', 'locked': False})
]
formdef_xml = formdef.export_to_xml()
f2 = FormDef.import_from_xml_tree(formdef_xml)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].prefill == {'type': 'string', 'value': 'plop'}
formdef.fields = [
fields.StringField(type='string', id=1, label='Bar', size='40',
prefill={'type': 'string', 'value': 'plop', 'locked': False})
]
formdef_xml_str = ET.tostring(formdef.export_to_xml())
formdef_xml_str = formdef_xml_str.replace(b'<value>plop</value>', b'')
formdef_xml = ET.fromstring(formdef_xml_str)
f2 = FormDef.import_from_xml_tree(formdef_xml)
assert len(f2.fields) == len(formdef.fields)
assert f2.fields[0].prefill == {'type': 'string', 'value': None}