From 037148c564d96b3a2576d3c6d21cb22d198bfe08 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 10 Jan 2023 17:01:38 +0100 Subject: [PATCH] fields: implement block field json serialization (#68964) --- tests/api/test_formdef.py | 145 +++++++++++++++++++++++- tests/backoffice_pages/test_carddata.py | 20 ++++ wcs/blocks.py | 8 ++ wcs/fields.py | 10 +- 4 files changed, 180 insertions(+), 3 deletions(-) diff --git a/tests/api/test_formdef.py b/tests/api/test_formdef.py index 1bd786b69..8fdbc9470 100644 --- a/tests/api/test_formdef.py +++ b/tests/api/test_formdef.py @@ -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 diff --git a/tests/backoffice_pages/test_carddata.py b/tests/backoffice_pages/test_carddata.py index d7d0d23b4..63a2b4539 100644 --- a/tests/backoffice_pages/test_carddata.py +++ b/tests/backoffice_pages/test_carddata.py @@ -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', + } + ], } diff --git a/wcs/blocks.py b/wcs/blocks.py index 275a60ce9..a6253082e 100644 --- a/wcs/blocks.py +++ b/wcs/blocks.py @@ -15,6 +15,7 @@ # along with this program; if not, see . 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' diff --git a/wcs/fields.py b/wcs/fields.py index c4052e057..6ba4a94f6 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -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']) -- 2.39.2