fields: implement block field json serialization (#68964) #24

Merged
vdeniaud merged 1 commits from wip/68964-representation-publique-du-conte into main 2023-01-16 09:41:07 +01:00
4 changed files with 180 additions and 3 deletions

View File

@ -14,6 +14,7 @@ from quixote import get_publisher
from wcs import fields, qommon
from wcs.api_access import ApiAccess
from wcs.api_utils import sign_url
from wcs.blocks import BlockDef
from wcs.categories import Category
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
@ -21,7 +22,7 @@ from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.upload_storage import PicklableUpload
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
from .utils import sign_uri
@ -965,3 +966,145 @@ def test_formdef_submit_structured(pub, local_user):
}
data_class.wipe()
def test_formdef_submit_structured_with_block_field(pub, local_user):
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.ItemField(
id='0',
label='foobar',
varname='foobar',
data_source={
'type': 'json',
'value': 'http://datasource.com',
},
),
fields.ItemField(
id='1',
label='foobar2',
varname='foobar2',
data_source={
'type': 'json',
'value': 'http://datasource.com/{{form_var_foobar_foo}}',
},
),
]
block.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.BlockField(id='0', label='test', varname='blockdata', type='block:foobar', max_items=3),
]
formdef.store()
data_class = formdef.data_class()
def url():
signed_url = sign_url(
'http://example.net/api/formdefs/test/submit'
'?format=json&orig=coucou&email=%s' % urllib.parse.quote(local_user.email),
'1234',
)
return signed_url[len('http://example.net') :]
with responses.RequestsMock() as rsps:
json_data = {
"data": [
{"id": 0, "text": "zéro", "foo": "bar"},
{"id": 2, "text": "deux", "foo": "bar2"},
]
}
rsps.get('http://datasource.com', json=json_data)
rsps.get('http://datasource.com/bar', json=json_data)
resp = get_app(pub).post_json(
url(),
{'data': {'0': [{'foobar': '0', 'foobar2': '2'}]}},
)
assert len(rsps.calls) == 2
assert rsps.calls[0].request.url == 'http://datasource.com/'
assert rsps.calls[1].request.url == 'http://datasource.com/bar'
formdata = data_class.get(resp.json['data']['id'])
assert formdata.status == 'wf-new'
blockdata = formdata.data['0']['data'][0]
assert blockdata['0'] == '0'
assert blockdata['0_display'] == 'zéro'
assert blockdata['0_structured'] == {
'id': 0,
'text': 'zéro',
'foo': 'bar',
}
assert blockdata['1'] == '2'
assert blockdata['1_display'] == 'deux'
assert blockdata['1_structured'] == {
'id': 2,
'text': 'deux',
'foo': 'bar2',
}
data_class.wipe()
def test_formdef_import_export_block(pub, admin_user):
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='0', label='Foo', varname='foo'),
fields.ItemField(id='1', label='Test', type='item', data_source={'type': 'foobar'}, varname='bar'),
fields.StringField(id='2', label='Unnamed', required=False),
fields.DateField(id='3', label='Date', type='date'),
]
block.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.BlockField(id='0', label='test', varname='blockdata', type='block:foobar', max_items=3),
]
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.data = {
'0': {
'data': [
{
'0': 'plop',
'1': '1',
'1_display': 'foo',
'1_structured': 'XXX',
'2': 'yop',
'3': time.strptime('2020-04-24', '%Y-%m-%d'),
},
{
'0': 'hop',
'1': '2',
'1_display': 'bar',
'1_structured': 'YYY',
'2': None,
'3': time.strptime('2020-04-24', '%Y-%m-%d'),
},
],
'schema': {'0': 'string', '1': 'item', '2': 'string', '3': 'date'},
},
'0_display': 'test',
}
formdata.just_created()
formdata.store()
app = login(get_app(pub))
resp = app.get('/api/forms/test/%s/' % formdata.id, status=200)
formdata_export = resp.json
formdef.data_class().wipe()
resp = app.post_json('/api/formdefs/test/submit?orig=coucou', formdata_export)
new_formdata = formdef.data_class().select()[0]
assert new_formdata.data == formdata.data

View File

@ -605,6 +605,15 @@ def test_backoffice_cards_import_data_from_json(pub):
),
}
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='1', label='Foo', varname='foo'),
fields.ItemField(id='2', label='List', varname='bar', data_source=data_source),
]
block.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
@ -614,6 +623,7 @@ def test_backoffice_cards_import_data_from_json(pub):
fields.ItemField(id='3', label='List', varname='item', data_source=data_source),
fields.FileField(id='4', label='File', varname='file'),
fields.DateField(id='5', label='Date', varname='date'),
fields.BlockField(id='6', label='Block', varname='blockdata', type='block:foobar', max_items=3),
]
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.backoffice_submission_roles = user.roles
@ -636,6 +646,7 @@ def test_backoffice_cards_import_data_from_json(pub):
'content': base64.encodebytes(b'%PDF-1.4 ...').decode(),
},
'date': '2022-07-19',
'blockdata': [{'foo': 'another string', 'bar': '2'}],
}
}
]
@ -661,6 +672,15 @@ def test_backoffice_cards_import_data_from_json(pub):
'url': 'http://example.net/api/cards/test/1/download?hash=73491aa2c82f099162e376a1edfabd2043b8825262b9ab6fd974a36f0024c47c',
},
'date': '2022-07-19',
'blockdata': 'foobar',
'blockdata_raw': [
{
'bar': 'deux',
'bar_raw': '2',
'bar_structured': {'id': '2', 'more': 'bar', 'text': 'deux'},
'foo': 'another string',
}
],
}
Review

Je verrais bien un test qui fasse le roundtrip export/import et assure que c'est ok. Là c'est un peu trop éparpillé avec juste dans tests/api/test_formdata.py::test_formdata une rapide vérification de ce qu'on reçoit en représentation json.

Je verrais bien un test qui fasse le roundtrip export/import et assure que c'est ok. Là c'est un peu trop éparpillé avec juste dans tests/api/test_formdata.py::test_formdata une rapide vérification de ce qu'on reçoit en représentation json.
Review

Yes, ajouté

Yes, ajouté

View File

@ -15,6 +15,7 @@
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import itertools
import types
import uuid
import xml.etree.ElementTree as ET
from contextlib import contextmanager
@ -24,6 +25,7 @@ from quixote.html import htmltag, htmltext
from . import data_sources, fields
from .categories import BlockCategory
from .formdata import FormData
from .qommon import _, misc
from .qommon.form import CompositeWidget, WidgetList
from .qommon.storage import StorableObject
@ -246,6 +248,12 @@ class BlockDef(StorableObject):
for field in self.fields or []:
yield from field.i18n_scan(base_location=location)
def get_all_fields(self):
return self.fields
def data_class(self):
return types.ClassType('fake_formdata', (FormData,), {'_formdef': self})
class BlockSubWidget(CompositeWidget):
template_name = 'qommon/forms/widgets/block_sub.html'

View File

@ -3986,13 +3986,19 @@ class BlockField(WidgetField):
self.block.fields,
formdata=kwargs.get('formdata'),
include_files=kwargs.get('include_file_content'),
include_unnamed_fields=True,
)
)
return result
def from_json_value(self, value):
# TODO: add support for blockfield in json import
return None
from wcs.api import posted_json_data_to_formdata_data
result = []
for subvalue_data in value or []:
result.append(posted_json_data_to_formdata_data(self.block, subvalue_data))
return {'data': result, 'schema': {x.id: x.key for x in self.block.fields}}
def get_opendocument_node_value(self, value, formdata=None, **kwargs):
node = ET.Element('{%s}span' % OD_NS['text'])