From f5ed34aaa57aa1f098baf85f0f01c37dc7e88024 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 23 Feb 2021 15:58:27 +0100
Subject: [PATCH] workflows: handle "add block" buttons in display form action
 (#47898)

---
 tests/backoffice_pages/test_all.py | 58 ++++++++++++++++++++++++++++++
 wcs/forms/common.py                | 15 +++++---
 wcs/wf/form.py                     |  9 ++++-
 wcs/workflows.py                   |  4 +++
 4 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index e3d3be52f..3cd6eddff 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -4232,6 +4232,64 @@ def test_backoffice_workflow_form_with_live_data_source(pub):
         ]
 
 
+def test_backoffice_workflow_display_form_with_block_add(pub):
+    user = create_user(pub)
+    create_environment(pub)
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+    ]
+    block.store()
+
+    wf = Workflow.get_default_workflow()
+    wf.id = '2'
+    wf.store()
+    wf = Workflow.get(wf.id)
+    status = wf.get_status('new')
+    status.items = []
+    display_form = FormWorkflowStatusItem()
+    display_form.id = '_display_form'
+    display_form.by = [user.roles[0]]
+    display_form.varname = 'blah'
+    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
+    display_form.formdef.fields = [
+        fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
+        fields.BlockField(id='2', label='Blocks', type='block:foobar', varname='data', max_items=3),
+    ]
+    status.items.append(display_form)
+    display_form.parent = status
+
+    jump = JumpOnSubmitWorkflowStatusItem()
+    jump.id = '_jump'
+    jump.status = 'accepted'
+    status.items.append(jump)
+    jump.parent = status
+
+    wf.store()
+    formdef = FormDef.get_by_urlname('form-title')
+    formdef.workflow_id = wf.id
+    formdef.store()
+
+    for formdata in formdef.data_class().select():
+        if formdata.status == 'wf-new':
+            break
+    app = login(get_app(pub))
+    resp = app.get(formdata.get_url(backoffice=True))
+    resp.form['fblah_1'] = 'blah'
+    resp.form['fblah_2$element0$f123'] = 'foo'
+    resp = resp.form.submit('fblah_2$add_element')
+    resp.form['fblah_2$element1$f123'] = 'bar'
+    resp = resp.form.submit('submit')
+
+    assert formdef.data_class().get(formdata.id).workflow_data == {
+        'blah_var_data': 'foobar, foobar',
+        'blah_var_data_raw': {'data': [{'123': 'foo'}, {'123': 'bar'}], 'schema': {'123': 'string'}},
+        'blah_var_str': 'blah',
+    }
+
+
 def test_backoffice_criticality_in_formdef_listing(pub):
     if not pub.is_using_postgresql():
         pytest.skip('this requires SQL')
diff --git a/wcs/forms/common.py b/wcs/forms/common.py
index e4d836925..5e8a75136 100644
--- a/wcs/forms/common.py
+++ b/wcs/forms/common.py
@@ -26,7 +26,7 @@ from quixote.util import randbytes
 from wcs import data_sources
 from wcs.api_utils import get_user_from_api_query_string, is_url_signed, sign_url_auto_orig
 from wcs.fields import WidgetField, FileField
-from wcs.workflows import EditableWorkflowStatusItem
+from wcs.workflows import EditableWorkflowStatusItem, RedisplayFormException
 
 from ..qommon import _
 from ..qommon import misc
@@ -547,10 +547,15 @@ class FormStatusPage(Directory, FormTemplateMixin):
 
         user = self.check_receiver()
         form = self.get_workflow_form(user)
-        response = self.check_submitted_form(form)
-        if response:
-            get_session().unmark_visited_object(self.filled)
-            return response
+        try:
+            response = self.check_submitted_form(form)
+        except RedisplayFormException:
+            # don't display errors after "add block" button has been clicked.
+            form.clear_errors()
+        else:
+            if response:
+                get_session().unmark_visited_object(self.filled)
+                return response
 
         get_logger().info('form %s - id: %s - view status' % (self.formdef.name, self.filled.id))
         get_response().add_javascript(['jquery.js', 'qommon.forms.js'])
diff --git a/wcs/wf/form.py b/wcs/wf/form.py
index b4a1534a0..df881aad9 100644
--- a/wcs/wf/form.py
+++ b/wcs/wf/form.py
@@ -19,7 +19,7 @@ import xml.etree.ElementTree as ET
 from ..qommon import _, N_
 from ..qommon.form import *
 
-from wcs.workflows import WorkflowStatusItem, register_item_class
+from wcs.workflows import WorkflowStatusItem, register_item_class, RedisplayFormException
 from wcs.formdef import FormDef, lax_int
 
 from wcs.admin.fields import FieldDefPage, FieldsDirectory
@@ -244,6 +244,13 @@ class FormWorkflowStatusItem(WorkflowStatusItem):
     def submit_form(self, form, formdata, user, evo):
         if not self.formdef:
             return
+        if form.get_submit() is True:
+            # non-submit button, maybe a "add block" button, look for them.
+            for widget in form.widgets:
+                if isinstance(widget, WidgetList):  # BlockWidget
+                    add_element_widget = widget.get_widget('add_element')
+                    if add_element_widget and add_element_widget.parse():
+                        raise RedisplayFormException()
         if form.get_submit() == 'submit' and not form.has_errors():
             self.evaluate_live_form(form, formdata, user)
             formdata.store()
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 69ff7739e..ad2ce80b0 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -104,6 +104,10 @@ class AbortActionException(Exception):
     pass
 
 
+class RedisplayFormException(Exception):
+    pass
+
+
 class AttachmentSubstitutionProxy(object):
     def __init__(self, formdata, attachment_evolution_part):
         self.formdata = formdata
-- 
2.30.1

