From 9bab7c98eb169fe73b988bc3483c89fd00a0ae6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 31 May 2022 09:51:56 +0200
Subject: [PATCH] workflows: accept empty mappings in (create/edit) (form/card)
 actions (#58632)

---
 tests/admin_pages/test_workflow.py | 32 ++++++++++++++++
 tests/backoffice_pages/test_all.py | 60 ++++++++++++++++++++++++++++++
 wcs/wf/create_formdata.py          | 13 +------
 wcs/wf/edit_carddata.py            |  1 -
 4 files changed, 94 insertions(+), 12 deletions(-)

diff --git a/tests/admin_pages/test_workflow.py b/tests/admin_pages/test_workflow.py
index 58c7b0858..db04bcf08 100644
--- a/tests/admin_pages/test_workflow.py
+++ b/tests/admin_pages/test_workflow.py
@@ -2656,6 +2656,38 @@ def test_workflows_create_formdata_action_config(pub):
     assert "syntax error: Could not parse the remainder: '{{' from '{{'" in resp
 
 
+def test_workflows_create_formdata_config_with_empty_values(pub):
+    create_superuser(pub)
+
+    FormDef.wipe()
+    target_formdef = FormDef()
+    target_formdef.name = 'target form'
+    target_formdef.fields = [
+        fields.StringField(id='0', label='string1', varname='foo_string'),
+        fields.StringField(id='1', label='string2', varname='bar_string'),
+    ]
+    target_formdef.store()
+
+    Workflow.wipe()
+    wf = Workflow(name='create-formdata')
+    st = wf.add_status('New')
+    st.add_action('create_formdata')
+    wf.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
+    # only mapping error: custom error message
+    resp.forms[0]['formdef_slug'] = 'target-form'
+    resp = resp.forms[0].submit('submit')
+    assert 'Please define new mappings' in resp
+    resp.form['map_fields_by_varname'].checked = True
+    resp.form['mappings$element0$field_id'] = '1'
+    resp = resp.forms[0].submit('submit')
+    wf.refresh_from_storage()
+    assert wf.possible_status[0].items[0].mappings[0].field_id == '1'
+    assert wf.possible_status[0].items[0].mappings[0].expression is None
+
+
 def test_workflows_create_formdata_deleted_field(pub):
     create_superuser(pub)
 
diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index 50a7719c6..376aa7b47 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -4219,6 +4219,66 @@ def test_backoffice_create_formdata_map_fields_by_varname(pub, create_formdata):
         assert not pq('.wf-links')
 
 
+def test_backoffice_create_formdata_map_fields_by_varname_plus_empty(pub, create_formdata):
+    create_formdata['create_formdata'].map_fields_by_varname = True
+    create_formdata['create_formdata'].mappings = [
+        Mapping(field_id='0', expression=None),
+    ]
+    create_formdata['wf'].store()
+    create_formdata['source_formdef'].fields = [
+        fields.StringField(id='0', label='string', varname='string0'),
+        fields.StringField(id='2', label='string', varname='string2', required=False),
+    ]
+    create_formdata['source_formdef'].store()
+    create_formdata['target_formdef'].fields = [
+        fields.StringField(id='0', label='string', varname='string0'),
+        fields.StringField(id='2', label='string', varname='string2', required=False),
+    ]
+    create_formdata['target_formdef'].store()
+
+    # create submitting user
+    user = create_formdata['pub'].user_class()
+    user.name = 'Jean Darmette'
+    user.email = 'jean.darmette@triffouilis.fr'
+    user.store()
+
+    # create source formdata
+    formdata = create_formdata['source_formdef'].data_class()()
+    create_formdata['formdata'] = formdata
+    formdata.data = {
+        '0': 'foo',
+        '2': 'bar',
+    }
+    formdata.user = user
+    formdata.just_created()
+    formdata.store()
+    formdata.perform_workflow()
+
+    # agent login and go to backoffice management pages
+    app = get_app(create_formdata['pub'])
+    app = login(app)
+    resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
+
+    # click on first available formdata
+    resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
+    target_data_class = create_formdata['target_formdef'].data_class()
+    assert target_data_class.count() == 0
+    # resubmit it through backoffice submission
+    resp = resp.form.submit(name='button_resubmit')
+    assert target_data_class.count() == 1
+    target_formdata = target_data_class.select()[0]
+
+    assert target_formdata.submission_context == {
+        'orig_object_type': 'formdef',
+        'orig_formdata_id': '1',
+        'orig_formdef_id': '1',
+    }
+    assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
+    assert target_formdata.user.id == user.id
+    assert target_formdata.status == 'draft'
+    assert target_formdata.data == {'0': None, '2': 'bar'}
+
+
 def test_backoffice_create_carddata_from_formdata(pub):
     CardDef.wipe()
     FormDef.wipe()
diff --git a/wcs/wf/create_formdata.py b/wcs/wf/create_formdata.py
index a5943f645..cde4730d3 100644
--- a/wcs/wf/create_formdata.py
+++ b/wcs/wf/create_formdata.py
@@ -50,7 +50,6 @@ class Mapping:
 class MappingWidget(CompositeWidget):
     def __init__(self, name, value=None, to_formdef=None, cached_field_labels=None, **kwargs):
         value = value or Mapping(None, '')
-        self.accept_empty_value = kwargs.pop('accept_empty_value', False)
         super().__init__(name, value, **kwargs)
 
         to_fields = self._fields_to_options(to_formdef)
@@ -63,16 +62,12 @@ class MappingWidget(CompositeWidget):
             SingleSelectWidget, name='field_id', title=_('Field'), value=value.field_id, options=to_fields
         )
 
-        placeholder = ''
-        if self.accept_empty_value:
-            placeholder = _('Leaving the field blank will empty the value.')
-
         self.add(
             ComputedExpressionWidget,
             name='expression',
             title=_('Expression'),
             value=value.expression,
-            value_placeholder=placeholder,
+            value_placeholder=_('Leaving the field blank will empty the value.'),
         )
 
     def _fields_to_options(self, formdef):
@@ -82,7 +77,7 @@ class MappingWidget(CompositeWidget):
 
     def _parse(self, request):
         super()._parse(request)
-        if self.get('field_id') is not None and self.accept_empty_value or self.get('expression') is not None:
+        if self.get('field_id') is not None:
             self.value = Mapping(field_id=self.get('field_id'), expression=self.get('expression'))
         else:
             self.value = None
@@ -104,14 +99,12 @@ class MappingsWidget(WidgetListAsTable):
             # reorder mappings based on to_formdef fields order
             value.sort(key=lambda mapping: self.ranks.get(str(mapping.field_id), 9999))
 
-        accept_empty_value = kwargs.pop('accept_empty_value', False)
         cached_field_labels = kwargs.pop('cached_field_labels', None)
         super().__init__(
             name,
             element_type=MappingWidget,
             element_kwargs={
                 'to_formdef': to_formdef,
-                'accept_empty_value': accept_empty_value,
                 'cached_field_labels': cached_field_labels,
             },
             **kwargs,
@@ -305,7 +298,6 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
     formdef_slug = None
     formdef_label = _('Form')
     mappings_label = _('Mappings to new form fields')
-    accept_empty_value = False
     varname_hint = _('This is used to get linked forms in expressions.')
     user_association_option_label = _('User to associate to form')
 
@@ -411,7 +403,6 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
                 MappingsWidget,
                 '%smappings' % prefix,
                 title=self.mappings_label,
-                accept_empty_value=self.accept_empty_value,
                 to_formdef=formdef,
                 value=self.mappings,
                 cached_field_labels=self.cached_field_labels,
diff --git a/wcs/wf/edit_carddata.py b/wcs/wf/edit_carddata.py
index e222f7c3a..9152d3033 100644
--- a/wcs/wf/edit_carddata.py
+++ b/wcs/wf/edit_carddata.py
@@ -26,7 +26,6 @@ class EditCarddataWorkflowStatusItem(CreateCarddataWorkflowStatusItem, ExternalW
     description = _('Edit Card Data')
     key = 'edit_carddata'
     mappings_label = _('Mappings to card fields')
-    accept_empty_value = True
     automatic_targetting = _('Action on cards linked to this form/card')
     manual_targetting = _('Specify the list of cards on which the action will be applied')
 
-- 
2.36.1

