backoffice: allow fields of BlockField in csv import for cards (#72799) #91

Merged
lguerin merged 1 commits from wip/72799-import-csv-carddata-with-blockfield into main 2023-03-24 10:33:25 +01:00
3 changed files with 154 additions and 4 deletions

View File

@ -617,6 +617,123 @@ def test_backoffice_cards_import_data_csv_no_backoffice_fields(pub):
assert carddef.data_class().count() == 2
def test_backoffice_cards_import_data_csv_blockfield(pub):
user = create_user(pub)
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='1', label='Foo', varname='foo'),
fields.StringField(id='2', label='Bar', varname='bar'),
]
block.digest_template = '{{ block_var_foo }}'
block.store()
CardDef.wipe()
carddef = CardDef()
carddef.backoffice_submission_roles = user.roles
carddef.name = 'test'
carddef.fields = [
fields.StringField(id='1', label='String'),
fields.BlockField(id='2', label='Block', varname='blockdata', type='block:foobar', max_items=2),
]
carddef.store()
app = login(get_app(pub))
sample_resp = app.get('/backoffice/data/test/data-sample-csv')
assert sample_resp.text.splitlines()[0] == '"String","Block"'
assert (
sample_resp.text.splitlines()[1]
== '"value","will be ignored - type Field Block (foobar) not supported"'
)
# block is required, error
data = b'''\
"String","Block"
"test","item1"
"test","item2"
'''
resp = app.get('/backoffice/data/test/import-file')
resp.forms[0]['file'] = Upload('test.csv', data, 'text/csv')
resp = resp.forms[0].submit()
assert 'Block is required but cannot be filled from CSV.' in resp
# block is not required
carddef.fields[1].required = False
carddef.store()
resp = app.get('/backoffice/data/test/import-file')
resp.forms[0]['file'] = Upload('test.csv', data, 'text/csv')
resp = resp.forms[0].submit().follow()
assert carddef.data_class().count() == 2
carddata1, carddata2 = carddef.data_class().select(order_by='id')
assert carddata1.data == {'1': 'test', '2': None, '2_display': None}
assert carddata2.data == {'1': 'test', '2': None, '2_display': None}
# required, but max_items = 1
carddef.fields[1].required = True
carddef.fields[1].max_items = 1
carddef.store()
sample_resp = app.get('/backoffice/data/test/data-sample-csv')
assert sample_resp.text.splitlines()[0] == '"String","Block - Foo","Block - Bar"'
assert sample_resp.text.splitlines()[1] == '"value","value","value"'
data = b'''\
"String","Block - Foo","Block - Bar"
"test","foo1","bar1"
'''
resp = app.get('/backoffice/data/test/import-file')
resp.forms[0]['file'] = Upload('test.csv', data, 'text/csv')
resp = resp.forms[0].submit().follow()
assert carddef.data_class().count() == 3
carddata1, carddata2, carddata3 = carddef.data_class().select(order_by='id')
assert carddata1.data == {'1': 'test', '2': None, '2_display': None}
assert carddata2.data == {'1': 'test', '2': None, '2_display': None}
assert carddata3.data == {
'1': 'test',
'2': {'data': [{'1': 'foo1', '2': 'bar1'}], 'schema': {'1': 'string', '2': 'string'}},
'2_display': 'foo1',
}
# not required, with another BlockField
carddef.fields[1].required = False
carddef.fields.append(
fields.BlockField(id='3', label='Block2', varname='blockdata2', type='block:foobar', max_items=1)
)
carddef.store()
sample_resp = app.get('/backoffice/data/test/data-sample-csv')
assert (
sample_resp.text.splitlines()[0]
== '"String","Block - Foo","Block - Bar","Block2 - Foo","Block2 - Bar"'
)
assert sample_resp.text.splitlines()[1] == '"value","value","value","value","value"'
data = b'''\
"String","Block - Foo","Block - Bar","Block2 - Foo","Block2 - Bar"
"test","foo2","","foo","bar"
'''
resp = app.get('/backoffice/data/test/import-file')
resp.forms[0]['file'] = Upload('test.csv', data, 'text/csv')
resp = resp.forms[0].submit().follow()
assert carddef.data_class().count() == 4
carddata1, carddata2, carddata3, carddata4 = carddef.data_class().select(order_by='id')
assert carddata1.data == {'1': 'test', '2': None, '2_display': None, '3': None, '3_display': None}
assert carddata2.data == {'1': 'test', '2': None, '2_display': None, '3': None, '3_display': None}
assert carddata3.data == {
'1': 'test',
'2': {'data': [{'1': 'foo1', '2': 'bar1'}], 'schema': {'1': 'string', '2': 'string'}},
'2_display': 'foo1',
'3': None,
'3_display': None,
}
assert carddata4.data == {
'1': 'test',
'2': {'data': [{'1': 'foo2'}], 'schema': {'1': 'string'}},
'2_display': 'foo2',
'3': {'data': [{'1': 'foo', '2': 'bar'}], 'schema': {'1': 'string', '2': 'string'}},
'3_display': 'foo',
}
def test_backoffice_cards_import_data_from_json(pub):
user = create_user(pub)

View File

@ -48,7 +48,21 @@ def get_import_csv_fields(carddef):
data['_user'] = value
# skip non-data fields
csv_fields = [x for x in (carddef.fields or []) if isinstance(x, fields.WidgetField)]
csv_fields = []
for field in carddef.iter_fields(include_block_fields=True, with_backoffice_fields=False):
if not isinstance(field, fields.WidgetField):
continue
if field.key == 'block' and field.max_items == 1:
# ignore BlockField if only one item
continue
block_field = getattr(field, 'block_field', None)
if block_field:
if block_field.max_items > 1:
# ignore fields of BlockField if more than one item
continue
# complete field label
field.label = '%s - %s' % (block_field.label, field.label)
csv_fields.append(field)
if carddef.user_support == 'optional':
return [UserField()] + csv_fields
return csv_fields
@ -416,6 +430,7 @@ class ImportFromCsvAfterJob(AfterJob):
for csv_line in self.kwargs['data_lines']:
data_instance = carddata_class()
data_instance.data = {}
block_data = {}
for i, field in enumerate(carddef_fields):
value = csv_line[i].strip()
@ -425,7 +440,21 @@ class ImportFromCsvAfterJob(AfterJob):
# skip unsupported field types
if field.convert_value_from_str is None:
continue
field.set_value(data_instance.data, field.convert_value_from_str(value))
block_field = getattr(field, 'block_field', None)
if not block_field:
field.set_value(data_instance.data, field.convert_value_from_str(value))
continue
# field in a BlockField
if not block_data.get(block_field.id):
block_data[block_field.id] = {'data': [{}], 'schema': {}, 'block_field': block_field}
field.set_value(block_data[block_field.id]['data'][0], field.convert_value_from_str(value))
block_data[block_field.id]['schema'][field.id] = field.key
# fill BlockFields
for data in block_data.values():
block_field = data.pop('block_field')
block_field.set_value(data_instance.data, data)
user_value = data_instance.data.pop('_user', None)
data_instance.user = self.user_lookup(user_value)

View File

@ -503,7 +503,7 @@ class FormDef(StorableObject):
def get_all_fields(self):
return (self.fields or []) + self.workflow.get_backoffice_fields()
def iter_fields(self, include_block_fields=False):
def iter_fields(self, include_block_fields=False, with_backoffice_fields=True):
def _iter_fields(fields, block_field=None):
for field in fields:
# add contextual_id/contextual_varname attributes
@ -531,7 +531,11 @@ class FormDef(StorableObject):
yield from _iter_fields(field.block.fields, block_field=field)
field._block = None # reset cache
yield from _iter_fields(self.get_all_fields())
if with_backoffice_fields:
fields = self.get_all_fields()
else:
fields = self.fields or []
yield from _iter_fields(fields)
def get_widget_fields(self):
return [field for field in self.fields or [] if isinstance(field, fields.WidgetField)]