From bd056fd50e92a987e8f81a3441a6129273e13f56 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 26 Oct 2021 15:35:46 +0200 Subject: [PATCH] formdef: do not load data from item's widgets with errors (#58207) --- tests/form_pages/test_all.py | 71 ++++++++++++++++++++++++++++++++++++ wcs/formdef.py | 9 ++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py index b32990e5..db6604c7 100644 --- a/tests/form_pages/test_all.py +++ b/tests/form_pages/test_all.py @@ -7987,3 +7987,74 @@ def test_file_prefill_on_edit(pub, http_requests): # and persist after being saved again resp = resp.form.submit('submit').follow() assert 'test.txt' in resp.text + + +def test_autosave_and_datasource_failure(mocker, pub, settings): + values = [ + {'id': '1', 'text': 'item 1', 'foo': 'bar'}, + {'id': '2', 'text': 'item 2', 'bar': 'foo'}, + ] + + _get_structured_items_mock = mocker.patch('wcs.data_sources._get_structured_items') + _get_structured_items_mock.return_value = values + get_value_by_id_mock = mocker.patch('wcs.data_sources.NamedDataSource.get_value_by_id') + get_value_by_id_mock.return_value = values[0] + + create_user(pub) + + NamedDataSource.wipe() + data_source = NamedDataSource(name='foobar') + data_source.data_source = {'type': 'json', 'value': 'http://remote.example.net/json'} + data_source.query_parameter = 'q' + data_source.id_parameter = 'id' + data_source.store() + + formdef = create_formdef() + formdef.data_class().wipe() + + formdef.fields = [ + fields.PageField(id='0', label='1st page', type='page'), + fields.ItemField( + id='1', + label='string', + type='item', + data_source={'type': 'foobar'}, + ), + fields.StringField(id='2', label='string', required=False), + fields.PageField(id='3', label='2nd page', type='page'), + fields.StringField(id='4', label='string', required=False), + ] + formdef.store() + + app = get_app(pub) + login(app, username='foo', password='foo') + + resp = app.get('/test/') + # select item 1 + resp.form['f1'].force_value('1') + # prevent nothing to save during autosave + resp.form['f2'].force_value('foobar') + # save data for asynchronous autosave + autosave_data = resp.form.submit_fields() + # submit first page + resp = resp.form.submit('submit') # -> next page + + # simulate a still valid _ajax_form_token + valid_ajax_form_token = resp.form['_ajax_form_token'].value + autosave_data = [ + (k, v) if k != '_ajax_form_token' else (k, valid_ajax_form_token) for k, v in autosave_data + ] + + # simulate data source failure + _get_structured_items_mock.return_value = [] + resp_autosave = app.post('/test/autosave', params=autosave_data) + _get_structured_items_mock.return_value = values + assert resp_autosave.json == {'result': 'success'} + + # submit second page + resp = resp.form.submit('submit') # -> validation page + # validate + resp = resp.form.submit('submit') # -> submit + + data = formdef.data_class().select()[0].data + assert '1' not in data or (data['1'] is not None and data['1_display'] is not None) diff --git a/wcs/formdef.py b/wcs/formdef.py index 2997c836..4f3a5f24 100644 --- a/wcs/formdef.py +++ b/wcs/formdef.py @@ -39,7 +39,7 @@ from .formdata import FormData from .qommon import PICKLE_KWARGS, _, force_str, get_cfg from .qommon.admin.emails import EmailsDirectory from .qommon.cron import CronJob -from .qommon.form import Form, HtmlWidget, UploadedFile +from .qommon.form import Form, HtmlWidget, UploadedFile, Widget from .qommon.misc import JSONEncoder, get_as_datetime, simplify, xml_node_text from .qommon.publisher import get_publisher_class from .qommon.storage import Equal, NotEqual, StorableObject, fix_key @@ -808,7 +808,12 @@ class FormDef(StorableObject): @classmethod def get_field_data(cls, field, widget): d = {} - d[field.id] = widget.parse() + value = widget.parse() + # ignore temporarily invalid value from item(s) fields + if field.key in ['item', 'items'] and not value and widget.has_error() and Widget.parse(widget): + return {} + + d[field.id] = value if d.get(field.id) is not None and field.convert_value_from_str: d[field.id] = field.convert_value_from_str(d[field.id]) field.set_value(d, d[field.id]) -- 2.33.0