Projet

Général

Profil

0001-wf-add-external-workflow-action-40204.patch

Serghei Mihai, 26 avril 2020 19:24

Télécharger (19,8 ko)

Voir les différences:

Subject: [PATCH] wf: add external workflow action (#40204)

 tests/test_admin_pages.py     |  48 +++++++++-
 tests/test_workflow_import.py |  37 ++++++++
 tests/test_workflows.py       | 160 +++++++++++++++++++++++++++++++++-
 wcs/carddef.py                |   4 +-
 wcs/wf/external_workflow.py   | 153 ++++++++++++++++++++++++++++++++
 wcs/workflows.py              |   5 ++
 6 files changed, 403 insertions(+), 4 deletions(-)
 create mode 100644 wcs/wf/external_workflow.py
tests/test_admin_pages.py
1210 1210
    resp = resp.forms[0].submit()
1211 1211
    resp = resp.follow()
1212 1212
    assert len(FormDef.get(1).fields) == 0
1213
 
1213

  
1214 1214
def test_form_duplicate_field(pub):
1215 1215
    user = create_superuser(pub)
1216 1216
    create_role()
......
3502 3502
    assert Workflow.get(workflow.id).global_actions[0].triggers[0].timeout == '-2'
3503 3503

  
3504 3504

  
3505
def test_workflows_global_actions_external_workflow_action(pub):
3506
    create_superuser(pub)
3507
    Workflow.wipe()
3508

  
3509
    wf = Workflow(name='external')
3510
    action = wf.add_global_action('Global action')
3511
    trigger = action.append_trigger('webservice')
3512
    trigger.identifier = 'test'
3513
    item = action.append_item('remove')
3514
    wf.store()
3515

  
3516
    formdef = FormDef()
3517
    formdef.name = 'external'
3518
    formdef.workflow = wf
3519
    formdef.store()
3520
    workflow = Workflow(name='foo')
3521
    st = workflow.add_status('New')
3522
    workflow.store()
3523

  
3524
    app = login(get_app(pub))
3525
    resp = app.get('/backoffice/workflows/%s/status/%s/' % (workflow.id, st.id))
3526
    assert 'External workflow' not in [o[0] for o in resp.forms[0]['action-formdata-action'].options]
3527

  
3528
    # activate option
3529
    if not pub.site_options.has_section('options'):
3530
        pub.site_options.add_section('options')
3531
    pub.site_options.set('options', 'external-workflow', 'true')
3532
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
3533
        pub.site_options.write(fd)
3534

  
3535
    resp = app.get('/backoffice/workflows/%s/status/%s/' % (workflow.id, st.id))
3536
    resp.forms[0]['action-formdata-action'] = 'External workflow'
3537
    resp = resp.forms[0].submit().follow()
3538
    assert 'External workflow (not completed)' in resp.text
3539

  
3540
    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (workflow.id, st.id))
3541
    resp = resp.forms[0].submit('submit')
3542
    assert "required field" in resp.text
3543
    resp.forms[0]['slug'] = 'formdef:%s' % formdef.url_name
3544
    resp = resp.forms[0].submit('submit')
3545
    assert "required field" in resp.text
3546
    resp = resp.forms[0].submit('submit')
3547
    resp.forms[0]['trigger_id'] = 'action:%s' % trigger.identifier
3548
    resp = resp.forms[0].submit('submit').follow().follow()
3549

  
3550

  
3505 3551
def test_workflows_criticality_levels(pub):
3506 3552
    create_superuser(pub)
3507 3553
    create_role()
tests/test_workflow_import.py
22 22
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
23 23
from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
24 24
from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, Mapping
25
from wcs.wf.external_workflow import ExternalWorkflowGlobalAction
25 26
from wcs.roles import Role
26 27
from wcs.fields import StringField, FileField
27 28

  
......
753 754
    wf.store()
754 755

  
755 756
    assert_import_export_works(wf, include_id=True)
757

  
758

  
759
def test_external_workflow(pub):
760
    target_wf = Workflow(name='External global action')
761
    action = target_wf.add_global_action('Delete', 'delete')
762
    trigger = action.append_trigger('webservice')
763
    trigger.trigger_id = 'Cleanup'
764
    target_wf.store()
765

  
766
    target_formdef = FormDef()
767
    target_formdef.name = 'target form'
768
    target_formdef.workflow = target_wf
769
    target_formdef.store()
770

  
771
    wf = Workflow(name='External workflow call')
772
    st1 = wf.add_status('New')
773
    st2 = wf.add_status('Call external workflow')
774

  
775
    jump = ChoiceWorkflowStatusItem()
776
    jump.id = '_external'
777
    jump.label = 'Cleanup'
778
    jump.by = ['_submitter']
779
    jump.status = st2.id
780
    jump.parent = st1
781
    st1.items.append(jump)
782

  
783
    external_workflow = ExternalWorkflowGlobalAction()
784
    external_workflow.id = '_external_workflow'
785
    external_workflow.slug = 'formdef:%s' % target_formdef.url_name
786
    external_workflow.event = trigger.id
787

  
788
    external_workflow.parent = st2
789
    st2.items.append(external_workflow)
790

  
791
    wf.store()
792
    assert_import_export_works(wf, include_id=True)
tests/test_workflows.py
34 34
        CommentableWorkflowStatusItem, ChoiceWorkflowStatusItem,
35 35
        DisplayMessageWorkflowStatusItem,
36 36
        AbortActionException, WorkflowCriticalityLevel,
37
        AttachmentEvolutionPart, WorkflowBackofficeFieldsFormDef)
37
        AttachmentEvolutionPart, WorkflowBackofficeFieldsFormDef,
38
        perform_items)
38 39
from wcs.wf.aggregation_email import (AggregationEmailWorkflowStatusItem,
39 40
        AggregationEmail, send_aggregation_emails)
40 41
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
......
55 56
from wcs.wf.notification import SendNotificationWorkflowStatusItem
56 57
from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, Mapping
57 58
from wcs.wf.create_carddata import CreateCarddataWorkflowStatusItem
59
from wcs.wf.external_workflow import ExternalWorkflowGlobalAction
58 60

  
59 61

  
60 62
from utilities import (create_temporary_pub, MockSubstitutionVariables,
......
4632 4634
    assert carddef.data_class().count() == 0
4633 4635
    formdata.perform_workflow()
4634 4636
    assert carddef.data_class().count() == 0
4637

  
4638

  
4639
def test_call_external_workflow_with_evolution_linked_object(pub):
4640
    FormDef.wipe()
4641
    CardDef.wipe()
4642
    LoggedError.wipe()
4643

  
4644
    external_wf = Workflow(name='External Workflow')
4645
    st1 = external_wf.add_status(name='New')
4646
    action = external_wf.add_global_action('Delete', 'delete')
4647
    action.append_item('remove')
4648
    trigger = action.append_trigger('webservice')
4649
    trigger.identifier = 'delete'
4650
    external_wf.store()
4651

  
4652
    external_formdef = FormDef()
4653
    external_formdef.name = 'External Form'
4654
    external_formdef.fields = [
4655
        StringField(id='0', label='string', varname='form_string'),
4656
    ]
4657
    external_formdef.workflow = external_wf
4658
    external_formdef.store()
4659

  
4660
    external_carddef = CardDef()
4661
    external_carddef.name = 'External Card'
4662
    external_carddef.fields = [
4663
        StringField(id='0', label='string', varname='card_string'),
4664
    ]
4665
    external_carddef.workflow = external_wf
4666
    external_carddef.store()
4667

  
4668
    wf = Workflow(name='External actions')
4669
    st1 = wf.add_status('Create external formdata')
4670
    create_formdata = CreateFormdataWorkflowStatusItem()
4671
    create_formdata.label = 'create linked form'
4672
    create_formdata.formdef_slug = external_formdef.url_name
4673
    create_formdata.varname = 'created_form'
4674
    create_formdata.id = '_create_form'
4675
    mappings = [
4676
        Mapping(field_id='0', expression='{{ form_var_string }}')
4677
    ]
4678
    create_formdata.mappings = mappings
4679
    create_formdata.parent = st1
4680

  
4681
    create_carddata = CreateCarddataWorkflowStatusItem()
4682
    create_carddata.label = 'create linked card'
4683
    create_carddata.formdef_slug = external_carddef.url_name
4684
    create_carddata.varname = 'created_card'
4685
    create_carddata.id = '_create_card'
4686
    create_carddata.mappings = mappings
4687
    create_carddata.parent = st1
4688

  
4689
    st1.items.append(create_formdata)
4690
    st1.items.append(create_carddata)
4691

  
4692
    global_action = wf.add_global_action('Delete external linked object', 'delete')
4693
    action = global_action.append_item('external_workflow_global_action')
4694
    action.slug = 'formdef:%s' % external_formdef.url_name
4695
    action.trigger_id = 'action:%s' % trigger.identifier
4696
    wf.store()
4697

  
4698
    formdef = FormDef()
4699
    formdef.name = 'External action form'
4700
    formdef.fields = [
4701
        StringField(id='0', label='string', varname='string'),
4702
    ]
4703
    formdef.workflow = wf
4704
    formdef.store()
4705

  
4706
    assert external_formdef.data_class().count() == 0
4707
    assert external_carddef.data_class().count() == 0
4708

  
4709
    formdata = formdef.data_class()()
4710
    formdata.data = {'0': 'test form'}
4711
    formdata.store()
4712
    formdata.just_created()
4713
    formdata.perform_workflow()
4714

  
4715
    assert external_formdef.data_class().count() == 1
4716
    assert external_carddef.data_class().count() == 1
4717
    perform_items([action], formdata)
4718
    assert LoggedError.count() == 0
4719
    assert external_formdef.data_class().count() == 0
4720
    assert external_carddef.data_class().count() == 1
4721

  
4722
    perform_items([action], formdata)
4723
    assert LoggedError.count() == 1
4724
    logged_error = LoggedError.select()[0]
4725
    assert logged_error.summary == 'Could not find linked "External Form" object'
4726
    assert logged_error.exception_class == 'KeyError'
4727

  
4728

  
4729
def test_call_external_workflow_with_data_sourced_object(pub):
4730
    FormDef.wipe()
4731
    CardDef.wipe()
4732
    LoggedError.wipe()
4733

  
4734
    carddef_wf = Workflow(name='Carddef Workflow')
4735
    st1 = carddef_wf.add_status(name='New')
4736
    action = carddef_wf.add_global_action('Delete', 'delete')
4737
    action.append_item('remove')
4738
    trigger = action.append_trigger('webservice')
4739
    trigger.identifier = 'delete'
4740
    carddef_wf.store()
4741

  
4742
    carddef = CardDef()
4743
    carddef.name = 'Data'
4744
    carddef.fields = [
4745
        StringField(id='0', label='string', varname='card_string'),
4746
    ]
4747
    carddef.digest_template = '{{ form_var_card_string }}'
4748
    carddef.workflow = carddef_wf
4749
    carddef.store()
4750

  
4751
    carddata = carddef.data_class()()
4752
    carddata.data = {'0': 'Text'}
4753
    carddata.store()
4754

  
4755
    wf = Workflow(name='External actions')
4756
    st1 = wf.add_status('Action')
4757

  
4758
    global_action = wf.add_global_action('Delete external linked object', 'delete')
4759
    action = global_action.append_item('external_workflow_global_action')
4760
    action.slug = 'carddef:%s' % carddef.url_name
4761
    action.trigger_id = 'action:%s' % trigger.identifier
4762
    wf.store()
4763

  
4764
    datasource = {'type': 'carddef:%s' % carddef.url_name}
4765
    formdef = FormDef()
4766
    formdef.name = 'External action form'
4767
    formdef.fields = [
4768
        ItemField(id='0', label='Card',
4769
            type='item', varname='card',
4770
            data_source=datasource)
4771
    ]
4772
    formdef.workflow = wf
4773
    formdef.store()
4774

  
4775
    assert LoggedError.count() == 0
4776
    assert carddef.data_class().count() == 1
4777

  
4778
    formdata = formdef.data_class()()
4779
    formdata.data = {'0': '1'}
4780
    formdata.store()
4781
    formdata.just_created()
4782
    formdata.perform_workflow()
4783

  
4784
    perform_items([action], formdata)
4785
    assert LoggedError.count() == 0
4786
    assert carddef.data_class().count() == 0
4787

  
4788
    perform_items([action], formdata)
4789
    assert LoggedError.count() == 1
4790
    logged_error = LoggedError.select()[0]
4791
    assert logged_error.summary == 'Could not find linked "Data" object'
4792
    assert logged_error.exception_class == 'KeyError'
wcs/carddef.py
22 22

  
23 23
from wcs.carddata import CardData
24 24
from wcs.formdef import FormDef
25
from wcs.workflows import Workflow
26 25

  
27 26
if not hasattr(types, 'ClassType'):
28 27
    types.ClassType = type
......
80 79

  
81 80
    @classmethod
82 81
    def get_default_workflow(cls):
83
        from wcs.workflows import EditableWorkflowStatusItem, ChoiceWorkflowStatusItem
82
        from wcs.workflows import (EditableWorkflowStatusItem,
83
                                 ChoiceWorkflowStatusItem, Workflow)
84 84
        from wcs.wf.remove import RemoveWorkflowStatusItem
85 85
        workflow = Workflow(name=_('Default (cards)'))
86 86
        workflow.id = '_carddef_default'
wcs/wf/external_workflow.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2020  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
from quixote import get_publisher
18

  
19
from wcs.qommon import _
20
from wcs.qommon.form import SingleSelectWidget
21

  
22
from wcs.logged_errors import LoggedError
23
from wcs.workflows import WorkflowStatusItem, perform_items, register_item_class
24
from wcs.workflows import WorkflowGlobalActionWebserviceTrigger, Workflow
25
from wcs.wf.create_formdata import LinkedFormdataEvolutionPart
26
from wcs.carddef import CardDef
27
from wcs.formdef import FormDef
28

  
29

  
30
class ExternalWorkflowGlobalAction(WorkflowStatusItem):
31

  
32
    description = _('External workflow')
33
    key = 'external_workflow_global_action'
34
    category = 'formdata-action'
35

  
36
    slug = None
37
    trigger_id = None
38

  
39
    @classmethod
40
    def is_available(cls, workflow=None):
41
        return get_publisher().has_site_option('external-workflow')
42

  
43
    def get_workflow_webservice_triggers(self, workflow):
44
        for action in workflow.global_actions or []:
45
            for trigger in action.triggers or []:
46
                if isinstance(trigger, WorkflowGlobalActionWebserviceTrigger):
47
                    yield trigger
48

  
49
    def get_object_def(self, object_slug=None):
50
        slug = object_slug or self.slug
51
        object_type, slug = slug.split(':')
52
        if object_type == 'formdef':
53
            object_class = FormDef
54
        elif object_type == 'carddef':
55
            object_class = CardDef
56
        try:
57
            return object_class.get_by_urlname(slug)
58
        except KeyError:
59
            pass
60

  
61
    def get_trigger(self, workflow):
62
        try:
63
            trigger_type, trigger_id = self.trigger_id.split(':', 1)
64
        except ValueError:
65
            return
66
        for trigger in self.get_workflow_webservice_triggers(workflow):
67
            if trigger.identifier == trigger_id:
68
                return trigger
69

  
70
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
71
        super(ExternalWorkflowGlobalAction, self).add_parameters_widgets(
72
            form, parameters, prefix=prefix, formdef=formdef)
73

  
74
        if 'slug' in parameters:
75
            objects = [(None, '---', '')]
76
            for wf in Workflow.select():
77
                if any(self.get_workflow_webservice_triggers(wf)):
78
                    for objectdef in wf.formdefs(lightweight=True) + wf.carddefs(lightweight=True):
79
                        object_slug = '%s:%s' % (objectdef.__class__.__name__.lower(),
80
                                                 objectdef.url_name)
81
                        objects.append((object_slug, objectdef.name, object_slug))
82
            if len(objects) == 1:
83
                form.add_global_errors([_('No workflow with external triggerable global action ')])
84
                return
85
            objects.sort(key=lambda x: x[1])
86
            form.add(SingleSelectWidget, '%sslug' % prefix,
87
                     title=_('Form/Card'),
88
                     value=self.slug,
89
                     required=True,
90
                     options=objects)
91

  
92
        if 'trigger_id' in parameters and form.get('%sslug' % prefix):
93
            object_def = self.get_object_def(form.get('%sslug' % prefix))
94
            triggers_names = [(None, '---', '')]
95
            for trigger in self.get_workflow_webservice_triggers(object_def.workflow):
96
                if trigger.identifier:
97
                    trigger_id = 'action:%s' % trigger.identifier
98
                    triggers_names.append((trigger_id, trigger.parent.name, trigger_id))
99
            form.add(SingleSelectWidget, '%strigger_id' % prefix,
100
                     title=_('Action'),
101
                     value=self.trigger_id,
102
                     required=True,
103
                     options=triggers_names)
104

  
105
    def get_line_details(self):
106
        if self.slug and self.trigger_id:
107
            objectdef = self.get_object_def()
108
            trigger = self.get_trigger(objectdef.workflow)
109
            return _('action "%(trigger_name)s" on %(form_name)s' % {
110
                'trigger_name': trigger.parent.name,
111
                'form_name': objectdef.name})
112
        return _('not completed')
113

  
114
    def iter_target_datas(self, formdata, objectdef):
115
        data_ids = []
116
        # search linked objects in data sources
117
        for field in formdata.get_formdef().get_all_fields():
118
            if field.data_source and field.data_source['type'] == self.slug:
119
                data_ids.append(formdata.data.get(field.id))
120

  
121
        # search in evolution
122
        for part in formdata.iter_evolution_parts():
123
            if isinstance(part, LinkedFormdataEvolutionPart) and part.formdef_class == objectdef.__class__:
124
                data_ids.append(part.formdata_id)
125
        error_msg = _('Could not find linked "%(object_name)s" object' % {
126
            'object_name': objectdef.name})
127

  
128
        for target_id in data_ids:
129
            try:
130
                yield objectdef.data_class().get(target_id)
131
            except KeyError as e:
132
                # use custom error message depending on target type
133
                LoggedError.record(error_msg, formdata=formdata, exception=e)
134

  
135
    def get_parameters(self):
136
        return ('slug', 'trigger_id', 'condition')
137

  
138
    def perform(self, formdata):
139
        objectdef = self.get_object_def()
140
        if not objectdef:
141
            return
142

  
143
        trigger = self.get_trigger(objectdef.workflow)
144
        if not trigger:
145
            LoggedError.record(_('Could not find trigger "%(trigger_name)s"' % {
146
                'trigger_name': self.trigger_id}), formdata=formdata)
147
            return
148

  
149
        for target_data in self.iter_target_datas(formdata, objectdef):
150
            perform_items(trigger.parent.items, target_data)
151

  
152

  
153
register_item_class(ExternalWorkflowGlobalAction)
wcs/workflows.py
44 44
from .roles import Role, logged_users_role, get_user_roles
45 45
from .fields import FileField
46 46
from .formdef import FormDef
47
from .carddef import CardDef
47 48
from .formdata import Evolution
48 49

  
49 50
if not __name__.startswith('wcs.') and not __name__ == "__main__":
......
834 835
    def formdefs(self, **kwargs):
835 836
        return list(FormDef.select(lambda x: x.workflow_id == self.id, **kwargs))
836 837

  
838
    def carddefs(self, **kwargs):
839
        return list(CardDef.select(lambda x: x.workflow_id == self.id, **kwargs))
840

  
837 841

  
838 842
class XmlSerialisable(object):
839 843
    node_name = None
......
2927 2931
    from .wf import notification
2928 2932
    from .wf import create_formdata
2929 2933
    from .wf import create_carddata
2934
    from .wf import external_workflow
2930 2935

  
2931 2936
from .wf.export_to_model import ExportToModel
2932
-