From 51b2282b48afe6875faaed4cd8e5cc27682b784c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 25 Jan 2021 19:41:04 +0100
Subject: [PATCH] forms: keep block fields as parsed when prefilling (#50530)

---
 tests/form_pages/test_block.py | 143 +++++++++++++++++++++++++--------
 wcs/forms/root.py              |   5 ++
 2 files changed, 116 insertions(+), 32 deletions(-)

diff --git a/tests/form_pages/test_block.py b/tests/form_pages/test_block.py
index 26ddb6800..376500db7 100644
--- a/tests/form_pages/test_block.py
+++ b/tests/form_pages/test_block.py
@@ -397,6 +397,14 @@ def test_block_string_prefill(pub, blocks_feature):
     ]
     block.store()
 
+    block2 = BlockDef()
+    block2.name = 'foobar2'
+    block2.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test', type='string'),
+    ]
+    block2.store()
+
     formdef = FormDef()
     formdef.name = 'form title'
     formdef.fields = [
@@ -404,49 +412,120 @@ def test_block_string_prefill(pub, blocks_feature):
         fields.StringField(id='1', label='string', varname='foo'),
         fields.PageField(id='2', label='2nd page', type='page'),
         fields.BlockField(id='3', label='test', type='block:foobar'),
+        fields.BlockField(id='4', label='test', type='block:foobar2'),
+    ]
+    formdef.store()
+
+    for i in range(2):
+        if i == 1:
+            # second pass, add another prefilled field in second block
+            block2.fields.append(
+                fields.StringField(
+                    id='124', required=True, label='Test', type='string',
+                    prefill={'type': 'string', 'value': '{{ form_var_foo }} World'})
+            )
+            block2.store()
+
+        formdef.data_class().wipe()
+
+        app = get_app(pub)
+        resp = app.get(formdef.get_url())
+        resp.form['f1'] = 'Hello'
+        resp = resp.form.submit('submit')  # -> 2nd page
+        assert not resp.pyquery('#form_error_f3')  # not marked as error
+        assert not resp.pyquery('#form_error_f4')  # ...
+        assert '>required field<' not in resp
+        assert resp.form['f3$element0$f123'].value == 'Hello World'
+        assert resp.form['f4$element0$f123'].value == ''
+        resp.form['f4$element0$f123'] = 'plop'
+        resp = resp.form.submit('submit')  # -> validation page
+        resp = resp.form.submit('submit')  # -> end page
+        resp = resp.follow()
+
+        formdata = formdef.data_class().select()[0]
+        assert formdata.data['3']['data'][0]['123'] == 'Hello World'
+        if i == 1:
+            assert formdata.data['4']['data'][0]['124'] == 'Hello World'
+
+        # check unmodified prefilled field
+        app = get_app(pub)
+        resp = app.get(formdef.get_url())
+        resp.form['f1'] = 'Hello'
+        resp = resp.form.submit('submit')  # -> 2nd page
+        assert resp.form['f3$element0$f123'].value == 'Hello World'
+        resp.form['f4$element0$f123'] = 'plop'
+        resp = resp.form.submit('submit')  # -> validation page
+        resp = resp.form.submit('previous')  # -> 2nd page
+        resp = resp.form.submit('previous')  # -> 1st page
+        resp.form['f1'] = 'Test'
+        resp = resp.form.submit('submit')  # -> 2nd page
+        assert resp.form['f3$element0$f123'].value == 'Test World'
+
+        # check modified prefilled field
+        app = get_app(pub)
+        resp = app.get(formdef.get_url())
+        resp.form['f1'] = 'Hello'
+        resp = resp.form.submit('submit')  # -> 2nd page
+        assert resp.form['f3$element0$f123'].value == 'Hello World'
+        resp.form['f3$element0$f123'] = 'Foobar'
+        resp.form['f4$element0$f123'] = 'plop'
+        resp = resp.form.submit('submit')  # -> validation page
+        resp = resp.form.submit('previous')  # -> 2nd page
+        resp = resp.form.submit('previous')  # -> 1st page
+        resp.form['f1'] = 'Test'
+        resp = resp.form.submit('submit')  # -> 2nd page
+        assert resp.form['f3$element0$f123'].value == 'Foobar'
+
+
+def test_block_prefill_and_required(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test', type='string',
+            prefill={'type': 'string', 'value': 'World'}),
+    ]
+    block.store()
+
+    block2 = BlockDef()
+    block2.name = 'foobar2'
+    block2.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.BoolField(id='234', required=True, label='Test2', type='bool'),
+    ]
+    block2.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.ItemField(
+            id='1', required=True, label='Test2', type='item', items=['Foo', 'Bar']),
+        fields.BlockField(id='2', label='test', type='block:foobar'),
+        fields.BlockField(id='3', label='test', type='block:foobar2'),
     ]
     formdef.store()
+
     formdef.data_class().wipe()
 
     app = get_app(pub)
     resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert not resp.pyquery('#form_error_f3')  # not marked as error
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
+    assert '>required field<' not in resp
+    assert resp.form['f2$element0$f123'].value == 'World'
+    resp.form['f3$element0$f123'] = 'Hello'
+    resp = resp.form.submit('submit')  # -> same page, error displyed
+    assert resp.text.count('>required field<') == 1
+    resp.form['f3$element0$f234'].checked = True
     resp = resp.form.submit('submit')  # -> validation page
     resp = resp.form.submit('submit')  # -> end page
     resp = resp.follow()
 
     formdata = formdef.data_class().select()[0]
-    assert formdata.data['3']['data'][0]['123'] == 'Hello World'
-
-    # check unmodified prefilled field
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    resp.form['f1'] = 'Test'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Test World'
-
-    # check modified prefilled field
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
-    resp.form['f3$element0$f123'] = 'Foobar'
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    resp.form['f1'] = 'Test'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Foobar'
+    assert formdata.data['2']['data'][0]['123'] == 'World'
+    assert formdata.data['3']['data'][0]['123'] == 'Hello'
+    assert formdata.data['3']['data'][0]['234'] is True
 
 
 def test_block_locked_prefill(pub, blocks_feature):
diff --git a/wcs/forms/root.py b/wcs/forms/root.py
index 99c3874d6..07fbbef09 100644
--- a/wcs/forms/root.py
+++ b/wcs/forms/root.py
@@ -372,6 +372,11 @@ class FormPage(Directory, FormTemplateMixin):
             if not prefilled and widget:
                 widget.clear_error()
                 widget._parsed = False
+                if block or field.key == 'block':
+                    # keep block outer & inner widgets as _parsed to avoid
+                    # later display of "required value" message that should
+                    # only happen when pages are submitted.
+                    widget._parsed = True
 
             if v is not None:
                 # store computed value, it will be used to compare with
-- 
2.30.0

