From 66b26edf5b586dff1df3c231fd8436e606776556 Mon Sep 17 00:00:00 2001
From: Serghei Mihai <smihai@entrouvert.com>
Date: Tue, 26 Nov 2019 11:23:50 +0100
Subject: [PATCH] wf: add action to create card data (#39997)

---
 tests/test_backoffice_pages.py | 79 ++++++++++++++++++++++++++++++++++
 tests/test_workflows.py        | 77 +++++++++++++++++++++++++++++++++
 wcs/wf/create_carddata.py      | 52 ++++++++++++++++++++++
 wcs/wf/create_formdata.py      | 15 ++++---
 wcs/workflows.py               |  1 +
 5 files changed, 219 insertions(+), 5 deletions(-)
 create mode 100644 wcs/wf/create_carddata.py

diff --git a/tests/test_backoffice_pages.py b/tests/test_backoffice_pages.py
index a44abe9a..22eb4d93 100644
--- a/tests/test_backoffice_pages.py
+++ b/tests/test_backoffice_pages.py
@@ -48,6 +48,7 @@ from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
 from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
 from wcs.wf.resubmit import ResubmitWorkflowStatusItem
 from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, Mapping
+from wcs.wf.create_carddata import CreateCarddataWorkflowStatusItem
 from wcs.carddef import CardDef
 from wcs.categories import Category
 from wcs.formdef import FormDef
@@ -6065,3 +6066,81 @@ def test_backoffice_create_formdata_map_fields_by_varname(create_formdata):
         assert pq('.wf-links')
     else:
         assert not pq('.wf-links')
+
+
+def test_backoffice_create_carddata_from_formdata(pub, studio):
+    CardDef.wipe()
+    FormDef.wipe()
+
+    user = create_user(pub, is_admin=True)
+    user.name = 'Foo Bar'
+    user.email = 'foo@example.com'
+    user.store()
+
+    carddef = CardDef()
+    carddef.name = 'My card'
+    carddef.fields = [
+        fields.StringField(id='1', label='string'),
+        fields.ItemField(id='2', label='List', items=['item1', 'item2']),
+        fields.DateField(id='3', label='Date')
+    ]
+    carddef.store()
+
+    wf = Workflow(name='create-carddata')
+    st1 = wf.add_status('New')
+    st2 = wf.add_status('Create card')
+
+    jump = ChoiceWorkflowStatusItem()
+    jump.id = '_createcard'
+    jump.label = 'Create card'
+    jump.by = ['_receiver']
+    jump.status = st2.id
+    jump.parent = st1
+    st1.items.append(jump)
+
+    create_card = CreateCarddataWorkflowStatusItem()
+    create_card.label = 'Create Card Data'
+    create_card.varname = 'mycard'
+    create_card.id = '_create'
+    create_card.formdef_slug = carddef.url_name
+    create_card.mappings = [
+        Mapping(field_id='1', expression='Simple String'),
+        Mapping(field_id='2', expression='{{ form_var_list_raw }}'),
+        Mapping(field_id='3', expression='{{ form_var_date }}'),
+    ]
+    create_card.parent = st2
+    st2.items.append(create_card)
+
+    display_message = DisplayMessageWorkflowStatusItem()
+    display_message.message = 'Card nr. {{ form_links_mycard_form_number }} created'
+    display_message.parent = st2
+    st2.items.append(display_message)
+    wf.store()
+
+    formdef = FormDef()
+    formdef.name = 'Source form'
+    formdef.workflow_roles = {'_receiver': 1}
+    formdef.fields = [
+        fields.ItemField(id='1', label='List', items=['item1', 'item2'],
+                         varname='list'),
+        fields.DateField(id='2', label='Date', varname='date')
+    ]
+    formdef.workflow_id = wf.id
+    formdef.store()
+
+    formdata = formdef.data_class()()
+    today = time.strptime('2020-01-01', '%Y-%m-%d')
+    formdata.data = {
+        '1': 'item2',
+        '2': today
+    }
+
+    formdata.user = user
+    formdata.just_created()
+    formdata.store()
+    formdata.perform_workflow()
+
+    app = login(get_app(pub))
+    resp = app.get(formdata.get_url(backoffice=True))
+    resp = resp.form.submit(name='button_createcard').follow()
+    assert 'Card nr. 1-1 created' in resp
diff --git a/tests/test_workflows.py b/tests/test_workflows.py
index 7f9c4e01..4c637da4 100644
--- a/tests/test_workflows.py
+++ b/tests/test_workflows.py
@@ -21,6 +21,7 @@ from wcs.qommon.http_request import HTTPRequest
 from wcs.qommon.form import *
 
 from wcs.formdef import FormDef
+from wcs.carddef import CardDef
 from wcs import sessions
 from wcs.fields import (StringField, DateField, MapField, FileField, ItemField,
         ItemsField, CommentField)
@@ -52,6 +53,7 @@ from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
 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 utilities import (create_temporary_pub, MockSubstitutionVariables,
@@ -4407,3 +4409,78 @@ def test_create_formdata(pub):
     assert len(errors) == 2
     assert any('form_var_toto_string' in (error.exception_message or '') for error in errors)
     assert any('Missing field' in error.summary for error in errors)
+
+
+def test_create_carddata(pub):
+    CardDef.wipe()
+    FormDef.wipe()
+    LoggedError.wipe()
+
+    carddef = CardDef()
+    carddef.name = 'My card'
+    carddef.fields = [
+        StringField(id='1', label='string'),
+        ItemField(id='2', label='List', items=['item1', 'item2'],
+                  varname='clist'),
+        DateField(id='3', label='Date', varname='cdate')
+    ]
+    carddef.store()
+
+    wf = Workflow(name='create-carddata')
+    wf.possible_status = Workflow.get_default_workflow().possible_status[:]
+    create = CreateCarddataWorkflowStatusItem()
+    create.label = 'Create CardDef'
+    create.varname = 'mycard'
+    create.id = '_create'
+    create.formdef_slug = carddef.url_name
+    create.mappings = [
+        Mapping(field_id='1', expression='=form_var_undefined'),
+        Mapping(field_id='2', expression='{{ form_var_list }}'),
+        Mapping(field_id='3', expression='{{ form_var_date }}'),
+    ]
+    create.parent = wf.possible_status[1]
+    wf.possible_status[1].items.insert(0, create)
+    wf.store()
+
+    formdef = FormDef()
+    formdef.name = 'source form'
+    formdef.fields = [
+        ItemField(id='1', label='List', items=['item1', 'item2'],
+                  varname='list'),
+        DateField(id='2', label='Date', varname='date')
+    ]
+    formdef.workflow_id = wf.id
+    formdef.store()
+
+    formdata = formdef.data_class()()
+    formdata.data = {}
+    formdata.just_created()
+
+    assert carddef.data_class().count() == 0
+
+    formdata = formdef.data_class()()
+    formdata.data = {}
+
+    formdata.just_created()
+    formdata.perform_workflow()
+
+    assert carddef.data_class().count() == 1
+
+    errors = LoggedError.select()
+    assert len(errors) == 2
+    assert any('form_var_undefined' in (error.exception_message or '') for error in errors)
+    assert any('invalid date value' in (error.exception_message or '') for error in errors)
+
+    formdata = formdef.data_class()()
+    today = datetime.date.today()
+
+    formdata.data = {'1': 'item1',
+                     '1_display': 'item1',
+                     '2': today}
+    formdata.just_created()
+    formdata.perform_workflow()
+
+    assert formdata.get_substitution_variables()['form_links_mycard_form_number'] == '1-2'
+    carddata = carddef.data_class().get(id=2)
+    assert carddata.get_substitution_variables()['form_var_clist'] == 'item1'
+    assert carddata.get_substitution_variables()['form_var_cdate'] == today
diff --git a/wcs/wf/create_carddata.py b/wcs/wf/create_carddata.py
new file mode 100644
index 00000000..e0d11861
--- /dev/null
+++ b/wcs/wf/create_carddata.py
@@ -0,0 +1,52 @@
+# 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 N_
+from quixote import get_publisher
+
+from wcs.workflows import register_item_class
+from wcs.carddef import CardDef
+
+from wcs.wf.create_formdata import (CreateFormdataWorkflowStatusItem,
+                                    LinkedFormdataEvolutionPart)
+
+
+class LinkedCardDataEvolutionPart(LinkedFormdataEvolutionPart):
+    formdef_class = CardDef
+
+
+class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem):
+    description = N_('Create Card Data')
+    key = 'create_carddata'
+    category = 'formdata-action'
+    ok_in_global_action = True
+
+    formdef_class = CardDef
+    evolution_part_class = LinkedCardDataEvolutionPart
+
+    formdef_label = N_('Card')
+    mappings_label = N_('Mappings to new card fields')
+    varname_hint = N_('This is used to get linked card in expressions.')
+
+    @classmethod
+    def is_available(cls, workflow=None):
+        return get_publisher().has_site_option('studio')
+
+    def get_parameters(self):
+        return ('formdef_slug', 'mappings', 'keep_user', 'varname')
+
+
+register_item_class(CreateCarddataWorkflowStatusItem)
diff --git a/wcs/wf/create_formdata.py b/wcs/wf/create_formdata.py
index 9c1e6d7f..b08ce97d 100644
--- a/wcs/wf/create_formdata.py
+++ b/wcs/wf/create_formdata.py
@@ -141,13 +141,14 @@ class LinkedFormdataEvolutionPart(object):
         return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
 
     def __repr__(self):
-        return '<Link "%s-%s">' % (self.formdef_id, self.formdata_id)
+        return '<Linked %s "%s-%s">' % (self.formdef_class.__name__,
+                                        self.formdef_id, self.formdata_id)
 
     @classmethod
     def get_substitution_variables(cls, formdata):
         d = {}
         for part in formdata.iter_evolution_parts():
-            if not isinstance(part, LinkedFormdataEvolutionPart):
+            if not isinstance(part, cls):
                 continue
             if part.formdata:
                 d['form_links_%s' % (part.varname or '*')] = part
@@ -193,6 +194,10 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
     evolution_part_class = LinkedFormdataEvolutionPart
 
     formdef_slug = None
+    formdef_label = N_('Form')
+    mappings_label = N_('Mappings to new form fields')
+    varname_hint = N_('This is used to get linked forms in expressions.')
+
     draft = False
     backoffice_submission = False
     keep_user = True
@@ -221,7 +226,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
             list_forms = [(None, '---', '')]
             list_forms += [(x.url_name, x.name, x.url_name) for x in self.formdef_class.select(order_by='name')]
             form.add(SingleSelectWidget, 'formdef_slug',
-                     title=_('Form'),
+                     title=_(self.formdef_label),
                      value=self.formdef_slug,
                      options=list_forms)
         if 'draft' in parameters:
@@ -247,13 +252,13 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
         formdef = self._resolve_formdef_slug(form.get('formdef_slug'))
         if 'mappings' in parameters and formdef:
             form.add(MappingsWidget, '%smappings' % prefix,
-                     title=_('Mappings to new form fields'),
+                     title=_(self.mappings_label),
                      to_formdef=formdef,
                      value=self.mappings)
         if 'varname' in parameters:
             form.add(VarnameWidget, '%svarname' % prefix,
                      title=_('Identifier'), value=self.varname,
-                     hint=_('This is used to get linked forms in expressions.'),
+                     hint=_(self.varname_hint),
                      advanced=not(bool(self.varname)))
         if 'map_fields_by_varname' in parameters and self.formdef:
             form.add(CheckboxWidget, '%smap_fields_by_varname' % prefix,
diff --git a/wcs/workflows.py b/wcs/workflows.py
index a4997f50..7671fe35 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -2924,5 +2924,6 @@ def load_extra():
     from .wf import redirect_to_url
     from .wf import notification
     from .wf import create_formdata
+    from .wf import create_carddata
 
 from .wf.export_to_model import ExportToModel
-- 
2.25.1

