Projet

Général

Profil

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

Serghei Mihai, 16 avril 2020 09:28

Télécharger (28,9 ko)

Voir les différences:

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

 tests/test_admin_pages.py     | 100 +++++++++++++-
 tests/test_workflow_import.py |  44 +++++-
 tests/test_workflows.py       | 251 +++++++++++++++++++++++++++++++++-
 wcs/admin/workflows.py        |   2 +
 wcs/carddef.py                |   2 +-
 wcs/wf/external_workflow.py   | 201 +++++++++++++++++++++++++++
 wcs/workflows.py              |  26 ++++
 7 files changed, 622 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_triggers(pub):
3506
    create_superuser(pub)
3507

  
3508
    Workflow.wipe()
3509
    workflow = Workflow(name='foo')
3510
    workflow.store()
3511

  
3512
    app = login(get_app(pub))
3513
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
3514
    resp = resp.click('add global action')
3515
    resp.forms[0]['name'] = 'External workflow trigger'
3516

  
3517
    resp = resp.forms[0].submit('submit')
3518
    resp = resp.follow()
3519
    assert 'External workflow' not in [o[0] for o in resp.forms[1]['type'].options]
3520

  
3521
    resp = resp.click('Delete')
3522
    resp.forms[0].submit('delete')
3523

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

  
3531
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
3532
    resp = resp.click('add global action')
3533
    resp.forms[0]['name'] = 'Global Action with External workflow trigger'
3534
    resp = resp.forms[0].submit('submit')
3535
    resp = resp.follow()
3536

  
3537
    # adding an external trigger
3538
    resp.forms[1]['type'] = 'External workflow'
3539
    resp = resp.forms[1].submit()
3540
    resp = resp.follow()
3541

  
3542
    assert 'External workflow (not configured)' in resp.text
3543
    resp = resp.click(href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[1].id, index=0)
3544
    resp.form['name'] = ''
3545
    resp = resp.form.submit()
3546
    assert 'required field' in resp.text
3547
    resp.form['name'] = 'Test'
3548
    resp = resp.form.submit('submit').follow()
3549
    assert "External workflow (by name « Test »)" in resp.text
3550
    assert Workflow.get(workflow.id).global_actions[0].triggers[1].name == 'Test'
3551

  
3552

  
3553
def test_workflows_global_actions_external_workflow_action(pub):
3554
    create_superuser(pub)
3555
    Workflow.wipe()
3556

  
3557
    wf = Workflow(name='external')
3558
    action = wf.add_global_action('Global action')
3559
    trigger = action.append_trigger('external_workflow')
3560
    trigger.name = 'test'
3561
    item = action.append_item('remove')
3562
    wf.store()
3563

  
3564
    formdef = FormDef()
3565
    formdef.name = 'external'
3566
    formdef.workflow = wf
3567
    formdef.store()
3568
    workflow = Workflow(name='foo')
3569
    st = workflow.add_status('New')
3570
    workflow.store()
3571

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

  
3576
    # activate option
3577
    if not pub.site_options.has_section('options'):
3578
        pub.site_options.add_section('options')
3579
    pub.site_options.set('options', 'external-workflow', 'true')
3580
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
3581
        pub.site_options.write(fd)
3582

  
3583
    resp = app.get('/backoffice/workflows/%s/status/%s/' % (workflow.id, st.id))
3584
    resp.forms[0]['action-formdata-action'] = 'External workflow'
3585
    resp = resp.forms[0].submit().follow()
3586
    assert 'External workflow (not completed)' in resp.text
3587

  
3588
    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (workflow.id, st.id))
3589
    resp.forms[0]['slug'] = 'formdef:%s' % formdef.url_name
3590
    resp = resp.forms[0].submit('submit')
3591
    assert "value must be selected" in resp.text
3592
    assert resp.forms[0]['target$type'].value == 'auto'
3593
    resp.forms[0]['target$type'] = 'expression'
3594
    resp = resp.forms[0].submit('submit')
3595
    assert "Expression must be filled" in resp
3596
    assert "value must be selected" in resp.text
3597
    resp.forms[0]['target$type'] = 'auto'
3598
    resp.forms[0]['trigger_id'] = trigger.id
3599
    resp = resp.forms[0].submit('submit').follow().follow()
3600
    assert 'External workflow (trigger « test » on external)' in resp.text
3601

  
3602

  
3505 3603
def test_workflows_criticality_levels(pub):
3506 3604
    create_superuser(pub)
3507 3605
    create_role()
tests/test_workflow_import.py
14 14
    Workflow, CommentableWorkflowStatusItem, WorkflowCriticalityLevel,
15 15
    WorkflowBackofficeFieldsFormDef, SendmailWorkflowStatusItem,
16 16
    SendSMSWorkflowStatusItem, WorkflowImportError, ChoiceWorkflowStatusItem,
17
    JumpOnSubmitWorkflowStatusItem)
17
    JumpOnSubmitWorkflowStatusItem, WorkflowGlobalActionExternalWorkflowTrigger)
18 18
from wcs.wf.wscall import WebserviceCallStatusItem
19 19
from wcs.wf.dispatch import DispatchWorkflowStatusItem
20 20
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
......
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('external_workflow')
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.target = {
789
        'type': 'expression',
790
        'expression': '{{ form_var_id }}'
791
    }
792
    external_workflow.parent = st2
793
    st2.items.append(external_workflow)
794

  
795
    wf.store()
796

  
797
    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_object_expression(pub):
4640
    FormDef.wipe()
4641
    LoggedError.wipe()
4642

  
4643
    external_formdef = FormDef()
4644
    external_formdef.name = 'External Form'
4645
    external_formdef.fields = [
4646
        StringField(id='0', label='string', varname='foo_string'),
4647
    ]
4648
    external_wf = Workflow(name='External Workflow')
4649
    action = external_wf.add_global_action('Delete', 'delete')
4650
    action.append_item('remove')
4651
    trigger = action.append_trigger('external_workflow')
4652
    trigger.name = 'delete'
4653
    external_wf.store()
4654

  
4655
    external_formdef.workflow = external_wf
4656
    external_formdef.store()
4657

  
4658
    wf = Workflow(name='External call')
4659
    st1 = wf.add_status('Call external workflow')
4660
    external_wf_action = ExternalWorkflowGlobalAction()
4661
    external_wf_action.slug = 'formdef:%s' % external_formdef.url_name
4662
    external_wf_action.trigger_id = 'unknown'
4663
    external_wf_action.target  = {
4664
        'type': 'expression',
4665
        'expression': '{{ form_var_target_id }}'
4666
    }
4667
    external_wf_action.parent = st1
4668
    st1.items.append(external_wf_action)
4669
    wf.store()
4670

  
4671
    formdef = FormDef()
4672
    formdef.name = 'External call form'
4673
    formdef.fields = [
4674
        StringField(id='0', label='string', varname='target_id'),
4675
    ]
4676
    formdef.workflow = wf
4677
    formdef.store()
4678

  
4679
    # unknown trigger
4680
    formdata = formdef.data_class()()
4681
    formdata.data = {'0': 'whatever'}
4682
    formdata.store()
4683
    formdata.just_created()
4684
    formdata.perform_workflow()
4685

  
4686
    assert LoggedError.count() == 1
4687
    logged_error = LoggedError.select()[0]
4688
    assert logged_error.summary == 'Could not find trigger "unknown"'
4689

  
4690
    # unknown object id
4691
    LoggedError.wipe()
4692

  
4693
    new_wf = Workflow(name='New external call')
4694
    st1 = new_wf.add_status('Call external workflow')
4695
    external_wf_action.trigger_id = trigger.id
4696
    st1.items.append(external_wf_action)
4697
    new_wf.store()
4698

  
4699
    formdef.workflow = new_wf
4700
    formdef.store()
4701

  
4702
    formdata = formdef.data_class()()
4703
    formdata.data = {'0': '42'}
4704
    formdata.store()
4705
    formdata.just_created()
4706
    formdata.perform_workflow()
4707

  
4708
    assert LoggedError.count() == 1
4709
    logged_error = LoggedError.select()[0]
4710
    assert logged_error.summary == 'Could not find object "External Form" by id 42'
4711
    assert logged_error.exception_class == 'KeyError'
4712

  
4713
    # no errors
4714
    LoggedError.wipe()
4715
    external_formdata = external_formdef.data_class()()
4716
    external_formdata.data = {'0': 'test'}
4717
    external_formdata.store()
4718
    assert external_formdef.data_class().count() == 1
4719

  
4720
    formdata = formdef.data_class()()
4721
    formdata.data = {'0': external_formdata.id}
4722
    formdata.store()
4723
    formdata.just_created()
4724
    formdata.perform_workflow()
4725

  
4726
    assert LoggedError.count() == 0
4727
    assert external_formdef.data_class().count() == 0
4728

  
4729

  
4730
def test_call_external_workflow_with_evolution_linked_object(pub):
4731
    FormDef.wipe()
4732
    CardDef.wipe()
4733
    LoggedError.wipe()
4734

  
4735
    external_wf = Workflow(name='External Workflow')
4736
    st1 = external_wf.add_status(name='New')
4737
    action = external_wf.add_global_action('Delete', 'delete')
4738
    action.append_item('remove')
4739
    trigger = action.append_trigger('external_workflow')
4740
    trigger.name = 'delete'
4741
    external_wf.store()
4742

  
4743
    external_formdef = FormDef()
4744
    external_formdef.name = 'External Form'
4745
    external_formdef.fields = [
4746
        StringField(id='0', label='string', varname='form_string'),
4747
    ]
4748
    external_formdef.workflow = external_wf
4749
    external_formdef.store()
4750

  
4751
    external_carddef = CardDef()
4752
    external_carddef.name = 'External Card'
4753
    external_carddef.fields = [
4754
        StringField(id='0', label='string', varname='card_string'),
4755
    ]
4756
    external_carddef.workflow = external_wf
4757
    external_carddef.store()
4758

  
4759
    wf = Workflow(name='External actions')
4760
    st1 = wf.add_status('Create external formdata')
4761
    create_formdata = CreateFormdataWorkflowStatusItem()
4762
    create_formdata.label = 'create linked form'
4763
    create_formdata.formdef_slug = external_formdef.url_name
4764
    create_formdata.varname = 'created_form'
4765
    create_formdata.id = '_create_form'
4766
    mappings = [
4767
        Mapping(field_id='0', expression='{{ form_var_string }}')
4768
    ]
4769
    create_formdata.mappings = mappings
4770
    create_formdata.parent = st1
4771

  
4772
    create_carddata = CreateCarddataWorkflowStatusItem()
4773
    create_carddata.label = 'create linked card'
4774
    create_carddata.formdef_slug = external_carddef.url_name
4775
    create_carddata.varname = 'created_card'
4776
    create_carddata.id = '_create_card'
4777
    create_carddata.mappings = mappings
4778
    create_carddata.parent = st1
4779

  
4780
    st1.items.append(create_formdata)
4781
    st1.items.append(create_carddata)
4782

  
4783
    global_action = wf.add_global_action('Delete external linked object', 'delete')
4784
    action = global_action.append_item('external_workflow_global_action')
4785
    action.slug = 'formdef:%s' % external_formdef.url_name
4786
    action.trigger_id = trigger.id
4787
    wf.store()
4788

  
4789
    formdef = FormDef()
4790
    formdef.name = 'External action form'
4791
    formdef.fields = [
4792
        StringField(id='0', label='string', varname='string'),
4793
    ]
4794
    formdef.workflow = wf
4795
    formdef.store()
4796

  
4797
    assert external_formdef.data_class().count() == 0
4798
    assert external_carddef.data_class().count() == 0
4799

  
4800
    formdata = formdef.data_class()()
4801
    formdata.data = {'0': 'test form'}
4802
    formdata.store()
4803
    formdata.just_created()
4804
    formdata.perform_workflow()
4805

  
4806
    assert external_formdef.data_class().count() == 1
4807
    assert external_carddef.data_class().count() == 1
4808
    perform_items([action], formdata)
4809
    assert LoggedError.count() == 0
4810
    assert external_formdef.data_class().count() == 0
4811
    assert external_carddef.data_class().count() == 1
4812

  
4813
    perform_items([action], formdata)
4814
    assert LoggedError.count() == 1
4815
    logged_error = LoggedError.select()[0]
4816
    assert logged_error.summary == 'Could not find linked "External Form" object'
4817
    assert logged_error.exception_class == 'KeyError'
4818

  
4819

  
4820
def test_call_external_workflow_with_data_sourced_object(pub):
4821
    FormDef.wipe()
4822
    CardDef.wipe()
4823
    LoggedError.wipe()
4824

  
4825
    carddef_wf = Workflow(name='Carddef Workflow')
4826
    st1 = carddef_wf.add_status(name='New')
4827
    action = carddef_wf.add_global_action('Delete', 'delete')
4828
    action.append_item('remove')
4829
    trigger = action.append_trigger('external_workflow')
4830
    trigger.name = 'delete'
4831
    carddef_wf.store()
4832

  
4833
    carddef = CardDef()
4834
    carddef.name = 'Data'
4835
    carddef.fields = [
4836
        StringField(id='0', label='string', varname='card_string'),
4837
    ]
4838
    carddef.digest_template = '{{ form_var_card_string }}'
4839
    carddef.workflow = carddef_wf
4840
    carddef.store()
4841

  
4842
    carddata = carddef.data_class()()
4843
    carddata.data = {'0': 'Text'}
4844
    carddata.store()
4845

  
4846
    wf = Workflow(name='External actions')
4847
    st1 = wf.add_status('Action')
4848

  
4849
    global_action = wf.add_global_action('Delete external linked object', 'delete')
4850
    action = global_action.append_item('external_workflow_global_action')
4851
    action.slug = 'carddef:%s' % carddef.url_name
4852
    action.trigger_id = trigger.id
4853
    wf.store()
4854

  
4855
    datasource = {'type': 'carddef:%s' % carddef.url_name}
4856
    formdef = FormDef()
4857
    formdef.name = 'External action form'
4858
    formdef.fields = [
4859
        ItemField(id='0', label='Card',
4860
            type='item', varname='card',
4861
            data_source=datasource)
4862
    ]
4863
    formdef.workflow = wf
4864
    formdef.store()
4865

  
4866
    assert LoggedError.count() == 0
4867
    assert carddef.data_class().count() == 1
4868

  
4869
    formdata = formdef.data_class()()
4870
    formdata.data = {'0': '1'}
4871
    formdata.store()
4872
    formdata.just_created()
4873
    formdata.perform_workflow()
4874

  
4875
    perform_items([action], formdata)
4876
    assert LoggedError.count() == 0
4877
    assert carddef.data_class().count() == 0
4878

  
4879
    perform_items([action], formdata)
4880
    assert LoggedError.count() == 1
4881
    logged_error = LoggedError.select()[0]
4882
    assert logged_error.summary == 'Could not find linked "Data" object'
4883
    assert logged_error.exception_class == 'KeyError'
wcs/admin/workflows.py
1302 1302
            ('manual', _('Manual')),
1303 1303
            ('webservice', _('Webservice')),
1304 1304
        ]
1305
        if get_publisher().has_site_option('external-workflow'):
1306
            available_triggers.append(('external_workflow', _('External workflow')))
1305 1307
        form.add(SingleSelectWidget, 'type', title=_('Type'),
1306 1308
                required=True, options=available_triggers)
1307 1309
        form.add_submit('submit', _('Add'))
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
......
81 80
    @classmethod
82 81
    def get_default_workflow(cls):
83 82
        from wcs.workflows import EditableWorkflowStatusItem, ChoiceWorkflowStatusItem
83
        from wcs.workflows import 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-2016  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
import time
18
import xml.etree.ElementTree as ET
19

  
20
from quixote import get_request, get_session, get_publisher
21
from quixote.html import htmltext
22

  
23
from django.utils.functional import cached_property
24

  
25
from wcs.qommon import _
26
from wcs.qommon.form import (SingleSelectWidget, ComputedExpressionWidget,
27
                             CompositeWidget)
28

  
29
from wcs.logged_errors import LoggedError
30
from wcs.workflows import WorkflowStatusItem, perform_items, register_item_class
31
from wcs.workflows import WorkflowGlobalActionExternalWorkflowTrigger, Workflow
32
from wcs.wf.create_formdata import LinkedFormdataEvolutionPart
33
from wcs.carddef import CardDef
34
from wcs.formdef import FormDef
35

  
36

  
37
class TargetWidget(CompositeWidget):
38
    target_types = [
39
        ('auto', _('Automatic'), 'auto'),
40
        ('expression', _('Expression'), 'expression')
41
    ]
42

  
43
    def __init__(self, name, value={}, **kwargs):
44
        super(TargetWidget, self).__init__(name, value, **kwargs)
45
        self.add(SingleSelectWidget, 'type',
46
                 value=self.value.get('type') or 'auto',
47
                 options=self.target_types,
48
                 render_br=False,
49
                 hint=_('Automatic from related data source and history, or expression'),
50
                 attrs={'data-dynamic-display-parent': 'true'}
51
        )
52
        self.add(ComputedExpressionWidget, 'expression',
53
                 value=value['expression'],
54
                 extra_css_class='grid-3-4',
55
                 render_br=False,
56
                 attrs={
57
                     'data-dynamic-display-child-of': '%s$type' % name,
58
                     'data-dynamic-display-value': 'expression'
59
                 }
60
        )
61
        # put widgets in grid
62
        self.widgets[0].extra_css_class = 'grid-1-4'
63
        self.widgets[1].extra_css_class = 'grid-3-4'
64

  
65
    def _parse(self, request):
66
        super(TargetWidget, self)._parse(request)
67
        if self.get('type') == 'expression' and not self.get('expression'):
68
            self.error = _('Expression must be filled')
69
            return
70
        self.value = {
71
            'type': self.get('type'),
72
            'expression': self.get('expression')
73
        }
74

  
75

  
76
class ExternalWorkflowGlobalAction(WorkflowStatusItem):
77

  
78
    description = _('External workflow')
79
    key = 'external_workflow_global_action'
80
    category = 'formdata-action'
81

  
82
    slug = None
83
    target = {'type': 'auto', 'expression': ''}
84
    trigger_id = None
85

  
86
    @classmethod
87
    def is_available(cls, workflow=None):
88
        return get_publisher().has_site_option('external-workflow')
89

  
90
    def get_workflow_external_triggers(self, workflow):
91
        triggers = []
92
        for action in workflow.global_actions or []:
93
            for trigger in action.triggers or []:
94
                if isinstance(trigger, WorkflowGlobalActionExternalWorkflowTrigger):
95
                    triggers.append(trigger)
96
        return triggers
97

  
98
    def get_object_def(self, object_slug=None):
99
        slug = object_slug or self.slug
100
        object_type, slug = slug.split(':')
101
        if object_type == 'formdef':
102
            object_class = FormDef
103
        elif object_type == 'carddef':
104
            object_class = CardDef
105
        try:
106
            return object_class.get_by_urlname(slug)
107
        except Exception as e:
108
            pass
109

  
110
    def get_trigger(self, workflow):
111
        for trigger in self.get_workflow_external_triggers(workflow):
112
            if trigger.id == self.trigger_id:
113
                return trigger
114

  
115
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
116
        super(ExternalWorkflowGlobalAction, self).add_parameters_widgets(
117
            form, parameters, prefix=prefix, formdef=formdef)
118

  
119
        if 'slug' in parameters:
120
            objects = [(None, '---', '')]
121
            for wf in Workflow.select():
122
                if self.get_workflow_external_triggers(wf):
123
                    for objectdef in wf.formdefs() + wf.carddefs():
124
                        object_slug = '%s:%s' % (objectdef.__class__.__name__.lower(),
125
                                                 objectdef.url_name)
126
                        objects += [(object_slug, objectdef.name, object_slug)]
127
            objects.sort(key=lambda x: x[1])
128
            form.add(SingleSelectWidget, '%sslug' % prefix,
129
                     title=_('Object type'),
130
                     value=self.slug,
131
                     options=objects)
132
        if 'target' in parameters and form.get('%sslug' % prefix):
133
            form.add(TargetWidget, '%starget' % prefix,
134
                     title=_('Target object'),
135
                     value=self.target
136
            )
137

  
138
        if 'trigger_id' in parameters and form.get('%sslug' % prefix):
139
            object_def = self.get_object_def(form.get('%sslug' % prefix))
140
            triggers_names = [(None, '---', '')]
141
            for trigger in self.get_workflow_external_triggers(object_def.workflow):
142
                if trigger.name:
143
                    triggers_names += [(trigger.id, trigger.name, trigger.id)]
144
            form.add(SingleSelectWidget, '%strigger_id' % prefix,
145
                     title=_('Trigger'),
146
                     value=self.trigger_id,
147
                     options=triggers_names)
148

  
149
            if form.is_submitted() and not form.get('%strigger_id' % prefix):
150
                form.get_widget('%strigger_id' % prefix).set_error(_('value must be selected'))
151

  
152
    def get_line_details(self):
153
        if self.slug and self.trigger_id:
154
            objectdef = self.get_object_def()
155
            trigger = self.get_trigger(objectdef.workflow)
156
            return _('trigger « %s » on %s' % (trigger.name, objectdef.name))
157
        return _('not completed')
158

  
159
    def iter_target_datas(self, formdata, objectdef):
160
        data_ids = []
161
        if self.target['type'] == 'expression':
162
            data_id = self.compute(self.target['expression'], formdata=formdata)
163
            data_ids.append(data_id)
164
            error_msg = 'Could not find object "%s" by id %s' % (objectdef.name, data_id)
165
        else:
166
            # search linked objects in data sources
167
            for field in formdata.get_formdef().fields:
168
                if field.data_source and field.data_source['type'] == self.slug:
169
                    data_ids.append(formdata.data.get(field.id))
170
            # search in evolution
171
            for part in formdata.iter_evolution_parts():
172
                if isinstance(part, LinkedFormdataEvolutionPart) and part.formdef_class == objectdef.__class__:
173
                    data_ids.append(part.formdata_id)
174
            error_msg = 'Could not find linked "%s" object' % objectdef.name
175

  
176
        for target_id in data_ids:
177
            try:
178
                yield objectdef.data_class().get(target_id)
179
            except KeyError as e:
180
                # use custom error message depending on target type
181
                LoggedError.record(error_msg, formdata=formdata, exception=e)
182

  
183
    def get_parameters(self):
184
        return ('slug',  'target', 'trigger_id', 'condition')
185

  
186
    def perform(self, formdata):
187
        objectdef = self.get_object_def()
188
        if not objectdef:
189
            return
190

  
191
        trigger = self.get_trigger(objectdef.workflow)
192
        if not trigger:
193
            LoggedError.record('Could not find trigger "%s"' % self.trigger_id,
194
                               formdata=formdata)
195
            return
196

  
197
        for target_data in self.iter_target_datas(formdata, objectdef):
198
            perform_items(trigger.parent.items, target_data)
199

  
200

  
201
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
......
1307 1311
        return [('hooks', WorkflowGlobalActionWebserviceHooksDirectory(formdata))]
1308 1312

  
1309 1313

  
1314
class WorkflowGlobalActionExternalWorkflowTrigger(WorkflowGlobalActionManualTrigger):
1315
    key = 'external_workflow'
1316
    name = None
1317

  
1318
    def get_parameters(self):
1319
        return ('name',)
1320

  
1321
    def render_as_line(self):
1322
        if self.name:
1323
            return _('External workflow (by name « %s »)') % self.name
1324
        else:
1325
            return _('External workflow (not configured)')
1326

  
1327
    def form(self, workflow):
1328
        form = Form(enctype='multipart/form-data')
1329
        form.add(StringWidget, 'name', title=_('Name'),
1330
                 required=True, value=self.name)
1331
        return form
1332

  
1333

  
1310 1334
class WorkflowGlobalAction(object):
1311 1335
    id = None
1312 1336
    name = None
......
1336 1360
            'manual': WorkflowGlobalActionManualTrigger,
1337 1361
            'timeout': WorkflowGlobalActionTimeoutTrigger,
1338 1362
            'webservice': WorkflowGlobalActionWebserviceTrigger,
1363
            'external_workflow': WorkflowGlobalActionExternalWorkflowTrigger
1339 1364
        }
1340 1365
        o = trigger_types.get(type)()
1341 1366
        if not self.triggers:
......
2927 2952
    from .wf import notification
2928 2953
    from .wf import create_formdata
2929 2954
    from .wf import create_carddata
2955
    from .wf import external_workflow
2930 2956

  
2931 2957
from .wf.export_to_model import ExportToModel
2932
-