From 7777d65a12e88b2aa6cea53501c851a566b77b47 Mon Sep 17 00:00:00 2001
From: Serghei Mihai <smihai@entrouvert.com>
Date: Tue, 19 May 2020 11:34:08 +0200
Subject: [PATCH] wf: add edit card data action (#41829)

---
 tests/test_workflows.py     | 153 ++++++++++++++++++++++++++++++++++++
 wcs/wf/edit_carddata.py     |  53 +++++++++++++
 wcs/wf/external_workflow.py |  12 ++-
 wcs/workflows.py            |   1 +
 4 files changed, 215 insertions(+), 4 deletions(-)
 create mode 100644 wcs/wf/edit_carddata.py

diff --git a/tests/test_workflows.py b/tests/test_workflows.py
index 5a1b62f9..df8478d9 100644
--- a/tests/test_workflows.py
+++ b/tests/test_workflows.py
@@ -62,6 +62,7 @@ from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
 from wcs.wf.notification import SendNotificationWorkflowStatusItem
 from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, Mapping
 from wcs.wf.create_carddata import CreateCarddataWorkflowStatusItem
+from wcs.wf.edit_carddata import EditCarddataWorkflowStatusItem
 from wcs.wf.external_workflow import ExternalWorkflowGlobalAction
 
 
@@ -4934,3 +4935,155 @@ def test_call_external_workflow_with_data_sourced_object(pub):
     logged_error = LoggedError.select()[0]
     assert logged_error.summary == 'Could not find linked "Data" object by id %s' % carddata.id
     assert logged_error.exception_class == 'KeyError'
+
+
+def test_edit_carddata_with_data_sourced_object(pub):
+    FormDef.wipe()
+    CardDef.wipe()
+    LoggedError.wipe()
+
+    datasource = {'type': 'formula',
+                  'value': repr([{'id': 'b', 'text': 'baker'},
+                                 {'id': 'c', 'text': 'cook'},
+                                 {'id': 'l', 'text': 'lawyer'}])}
+    carddef = CardDef()
+    carddef.name = 'Person'
+    carddef.fields = [
+        StringField(id='0', label='First Name', varname='first_name'),
+        StringField(id='1', label='Last Name', varname='last_name'),
+        ItemField(id='2', label='Profession', type='item',
+                  varname='profession', data_source=datasource),
+    ]
+    carddef.digest_template = '{{ form_var_first_name }} {{ form_var_last_name }}'
+    carddef.store()
+
+    carddata = carddef.data_class()()
+    carddata.data = {
+        '0': 'Foo',
+        '1': 'Bar',
+        '2': 'l',
+        '2_display': 'lawyer'}
+    carddata.store()
+
+    wf = Workflow(name='Card update')
+    st1 = wf.add_status('Update card', 'st1')
+
+    edit = EditCarddataWorkflowStatusItem()
+    edit.formdef_slug = carddef.url_name
+    edit.mappings = [
+        Mapping(field_id='2', expression='{{ form_var_new_profession }}'),
+    ]
+    edit.id = 'edit'
+    st1.items.append(edit)
+    edit.parent = st1
+    wf.store()
+
+    datasource = {'type': 'carddef:%s' % carddef.url_name}
+    formdef = FormDef()
+    formdef.name = 'Persons'
+    formdef.fields = [
+        ItemField(id='0', label='Person',
+            type='item', varname='person',
+            data_source=datasource),
+        StringField(id='1', label='New profession',
+                    varname='new_profession')
+    ]
+    formdef.workflow = wf
+    formdef.store()
+
+    formdata = formdef.data_class()()
+    formdata.data = {
+        '0': '1',
+        '1': 'c'
+    }
+    formdata.store()
+    formdata.just_created()
+    formdata.perform_workflow()
+
+    data = carddef.data_class().select()[0]
+    assert data.data['2'] == 'c'
+    assert data.data['2_display'] == 'cook'
+
+    formdata = formdef.data_class()()
+    formdata.data = {
+        '0': '1',
+        '1': 'b'
+    }
+    formdata.store()
+    formdata.just_created()
+    formdata.perform_workflow()
+
+    data = carddef.data_class().select()[0]
+    assert data.data['2'] == 'b'
+    assert data.data['2_display'] == 'baker'
+
+
+def test_edit_carddata_with_linked_object(pub):
+    FormDef.wipe()
+    CardDef.wipe()
+    LoggedError.wipe()
+
+    carddef = CardDef()
+    carddef.name = 'Parent'
+    carddef.fields = [
+        StringField(id='0', label='First Name', varname='first_name'),
+        StringField(id='1', label='Last Name', varname='last_name'),
+        StringField(id='2', label='Kids number', varname='kids_number'),
+    ]
+    carddef.store()
+
+    wf = Workflow(name='Card create and update')
+    st1 = wf.add_status('Create card', 'st1')
+    edit = CreateCarddataWorkflowStatusItem()
+    edit.formdef_slug = carddef.url_name
+    edit.mappings = [
+        Mapping(field_id='0', expression='{{ form_var_first_name }}'),
+        Mapping(field_id='1', expression='{{ form_var_last_name }}'),
+        Mapping(field_id='2', expression='{{ form_var_kids_number|default:"0" }}'),
+    ]
+    st1.items.append(edit)
+    edit.parent = st1
+    jump = JumpWorkflowStatusItem()
+    jump.id = '_jump'
+    jump.by = ['_submitter', '_receiver']
+    jump.status = 'st2'
+    st1.items.append(jump)
+    jump.parent = st1
+
+    st2 = wf.add_status('Update card', 'st2')
+    edit = EditCarddataWorkflowStatusItem()
+    edit.formdef_slug = carddef.url_name
+    edit.mappings = [
+        Mapping(field_id='2', expression='{{ form_var_kids_number|add:"1" }}'),
+    ]
+    edit.id = 'edit'
+    st2.items.append(edit)
+    edit.parent = st2
+    wf.store()
+
+    formdef = FormDef()
+    formdef.name = 'Parents'
+    formdef.fields = [
+        StringField(id='0', label='First Name',
+                    varname='first_name'),
+        StringField(id='1', label='Last Name',
+                    varname='last_name'),
+        StringField(id='2', label='Number of kids',
+                    varname='kids_number')
+    ]
+    formdef.workflow = wf
+    formdef.store()
+
+    formdata = formdef.data_class()()
+    formdata.data = {
+        '0': 'Parent',
+        '1': 'Foo',
+        '2': '2'
+    }
+    formdata.store()
+    formdata.just_created()
+    formdata.perform_workflow()
+
+    assert carddef.data_class().count() == 1
+    card_data = carddef.data_class().select()[0]
+    assert card_data.data['2'] == '3'
diff --git a/wcs/wf/edit_carddata.py b/wcs/wf/edit_carddata.py
new file mode 100644
index 00000000..a83c988e
--- /dev/null
+++ b/wcs/wf/edit_carddata.py
@@ -0,0 +1,53 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2020  Entr'ouvert
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from wcs.qommon import _
+from quixote import get_publisher
+
+from wcs.workflows import register_item_class
+from wcs.carddef import CardDef
+
+from wcs.wf.create_carddata import CreateCarddataWorkflowStatusItem
+from wcs.wf.external_workflow import ExternalWorkflowGlobalAction
+
+
+class EditCarddataWorkflowStatusItem(CreateCarddataWorkflowStatusItem, ExternalWorkflowGlobalAction):
+    description = _('Edit Card Data')
+    key = 'edit_carddata'
+    mappings_label = _('Mappings to card fields')
+
+    def get_parameters(self):
+        return ('formdef_slug', 'mappings', 'condition')
+
+    def get_datasourced_objects_ids(self, formdata):
+        ids = []
+        for field in formdata.get_formdef().get_all_fields():
+            if getattr(field, 'data_source', None) and field.data_source['type'] == 'carddef:%s' % self.formdef_slug:
+                ids.append(formdata.data.get(field.id))
+        return ids
+
+    def perform(self, formdata):
+        carddef = CardDef.get_by_urlname(self.formdef_slug)
+        if not carddef:
+            return
+
+        for target_data in self.iter_target_datas(formdata, carddef):
+            self.apply_mappings(dest=target_data, src=formdata)
+            with get_publisher().substitutions.freeze():
+                target_data.store()
+
+
+register_item_class(EditCarddataWorkflowStatusItem)
diff --git a/wcs/wf/external_workflow.py b/wcs/wf/external_workflow.py
index f0a37cf3..c3082aa1 100644
--- a/wcs/wf/external_workflow.py
+++ b/wcs/wf/external_workflow.py
@@ -111,12 +111,16 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
                          'object_name': objectdef.name}
         return _('not completed')
 
-    def iter_target_datas(self, formdata, objectdef):
-        data_ids = []
-        # search linked objects in data sources
+    def get_datasourced_objects_ids(self, formdata):
+        ids = []
         for field in formdata.get_formdef().get_all_fields():
             if getattr(field, 'data_source', None) and field.data_source['type'] == self.slug:
-                data_ids.append(formdata.data.get(field.id))
+                ids.append(formdata.data.get(field.id))
+        return ids
+
+    def iter_target_datas(self, formdata, objectdef):
+        # get linked objects from data sources
+        data_ids = self.get_datasourced_objects_ids(formdata)
 
         # search in evolution
         for part in formdata.iter_evolution_parts():
diff --git a/wcs/workflows.py b/wcs/workflows.py
index d8f24834..4333d7d2 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -3030,5 +3030,6 @@ def load_extra():
     from .wf import create_formdata
     from .wf import create_carddata
     from .wf import external_workflow
+    from .wf import edit_carddata
 
 from .wf.export_to_model import ExportToModel
-- 
2.27.0.rc0

