InteractionWorkflow.py 11.4 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
##############################################################################
#
# Copyright (c) 2003 Nexedi SARL and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
# 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.
#
##############################################################################

import Globals
import App
Jean-Paul Smets's avatar
Jean-Paul Smets committed
21
from AccessControl import getSecurityManager, ClassSecurityInfo
Jean-Paul Smets's avatar
Jean-Paul Smets committed
22 23 24 25
from Products.CMFCore.utils import getToolByName, _getAuthenticatedUser
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
26 27 28 29
from Products.DCWorkflow.Transitions import TRIGGER_AUTOMATIC, TRIGGER_WORKFLOW_METHOD
from Products.CMFCore.WorkflowCore import WorkflowException, \
     ObjectDeleted, ObjectMoved
from Products.DCWorkflow.Expression import StateChangeInfo, createExprContext
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30
from Products.CMFCore.WorkflowTool import addWorkflowFactory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31
from Products.CMFActivity.ActiveObject import ActiveObject
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33 34

from zLOG import LOG

Jean-Paul Smets's avatar
Jean-Paul Smets committed
35
class InteractionWorkflowDefinition (DCWorkflowDefinition, ActiveObject):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    """
    The InteractionTool implements portal object
    interaction policies.

    An interaction is defined by
    a domain and a behaviour:

    The domain is defined as:

    - the meta_type it applies to

    - the portal_type it applies to

    - the conditions of application (category membership, value range,
      security, function, etc.)

    The transformation template is defined as:

    - pre method executed before

    - pre async executed anyway

    - post method executed after success before return

    - post method executed after success anyway

    This is similar to signals and slots except is applies to classes
    rather than instances. Similar to
    stateless workflow methods with more options. Similar to ZSQL scipts
    but in more cases.

    Examples of applications:

    - when movement is updated, apply transformation rules to movement

    - when stock is 0, post an event of stock empty

    - when birthday is called, call the happy birthday script
    
    ERP5 main application: specialize behaviour of classes "on the fly".
    Make the architecture as modular as possible. Implement connections
     la Qt.

    Try to mimic: Workflow...

    Question: should be use it for values ? or use a global value model ?

    Status : OK


    Implementation:

    A new kind of workflow (stateless). Follow the DCWorkflow class.
    Provide filters (per portal_type, etc.). Allow inspection of objects ?
    """
91
    meta_type = 'Workflow'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    title = 'Interaction Workflow Definition'

    interactions = None

    security = ClassSecurityInfo()

    manage_options = (
        {'label': 'Properties', 'action': 'manage_properties'},
        {'label': 'Interactions', 'action': 'interactions/manage_main'},
        {'label': 'Variables', 'action': 'variables/manage_main'},
        {'label': 'Scripts', 'action': 'scripts/manage_main'},
        ) + App.Undo.UndoSupport.manage_options

    def __init__(self, id):
        self.id = id
        from Interaction import Interaction
        self._addObject(Interaction('interactions'))
        from Products.DCWorkflow.Variables import Variables
        self._addObject(Variables('variables'))
        from Products.DCWorkflow.Worklists import Worklists
        self._addObject(Worklists('worklists'))
        from Products.DCWorkflow.Scripts import Scripts
        self._addObject(Scripts('scripts'))

Jean-Paul Smets's avatar
Jean-Paul Smets committed
116 117 118 119
    security.declarePrivate('listObjectActions')
    def listObjectActions(self, info):
        return []

120 121 122 123 124 125 126
    security.declarePrivate('_changeStateOf')
    def _changeStateOf(self, ob, tdef=None, kwargs=None) :
      """
      InteractionWorkflow is stateless. Thus, this function should do nothing.
      """
      return

Jean-Paul Smets's avatar
Jean-Paul Smets committed
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    security.declarePrivate('isInfoSupported')
    def isInfoSupported(self, ob, name):
        '''
        Returns a true value if the given info name is supported.
        '''
        vdef = self.variables.get(name, None)
        if vdef is None:
            return 0
        return 1
    
    security.declarePrivate('getInfoFor')
    def getInfoFor(self, ob, name, default):
        '''
        Allows the user to request information provided by the
        workflow.  This method must perform its own security checks.
        '''
        vdef = self.variables[name]
        if vdef.info_guard is not None and not vdef.info_guard.check(
            getSecurityManager(), self, ob):
            return default
        status = self._getStatusOf(ob)
        if status is not None and status.has_key(name):
            value = status[name]
        # Not set yet.  Use a default.
        elif vdef.default_expr is not None:
            ec = createExprContext(StateChangeInfo(ob, self, status))
            value = vdef.default_expr(ec)
        else:
            value = vdef.default_value

        return value
    
    security.declarePrivate('isWorkflowMethodSupported')
    def isWorkflowMethodSupported(self, ob, method_id):
        '''
        Returns a true value if the given workflow is 
        automatic with the propper method_id
        '''
Sebastien Robin's avatar
Sebastien Robin committed
165
        #return 0 # Why this line ??? # I guess it should be used
Jean-Paul Smets's avatar
Jean-Paul Smets committed
166 167
        for t in self.interactions.values():
            if t.trigger_type == TRIGGER_WORKFLOW_METHOD:
168
                if method_id in t.method_id:
169 170 171
                    if ((t.portal_type_filter is None or ob.getPortalType() in t.portal_type_filter)
                      and self._checkTransitionGuard(t, ob)):
                        return 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
172 173 174 175 176 177 178 179 180
        return 0                


    security.declarePrivate('wrapWorkflowMethod')
    def wrapWorkflowMethod(self, ob, method_id, func, args, kw):
        '''
        Allows the user to request a workflow action.  This method
        must perform its own security checks.
        '''
Sebastien Robin's avatar
Sebastien Robin committed
181
        return
Jean-Paul Smets's avatar
Jean-Paul Smets committed
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198

    security.declarePrivate('notifyBefore')
    def notifyBefore(self, ob, action, args=None, kw=None):
        '''
        Notifies this workflow of an action before it happens,
        allowing veto by exception.  Unless an exception is thrown, either
        a notifySuccess() or notifyException() can be expected later on.
        The action usually corresponds to a method name.
        '''
        for t in self.interactions.values():
            tdef = None
            if t.trigger_type == TRIGGER_AUTOMATIC:
                if t.portal_type_filter is None:
                  tdef = t
                elif ob.getPortalType() in t.portal_type_filter:
                  tdef = t
            elif t.trigger_type == TRIGGER_WORKFLOW_METHOD:
199
                if action in t.method_id:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
200 201 202 203 204 205 206 207 208 209 210
                    if t.portal_type_filter is None:
                      tdef = t
                    elif ob.getPortalType() in t.portal_type_filter:
                      tdef = t
            if tdef is not None:
                former_status = self._getStatusOf(ob)
                # Execute the "before" script.
                for script_name in tdef.script_name:
                    script = self.scripts[script_name]
                    # Pass lots of info to the script in a single parameter.
                    sci = StateChangeInfo(
211
                        ob, self, former_status, tdef, None, None, kwargs=kw)
212
                    script(sci)  # May throw an exception.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
213 214 215 216 217 218

    security.declarePrivate('notifySuccess')
    def notifySuccess(self, ob, action, result, args=None, kw=None):
        '''
        Notifies this workflow that an action has taken place.
        '''
219 220 221
        # initialize variables
        econtext = None
        sci = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
222 223 224 225 226 227 228 229
        for t in self.interactions.values():
            tdef = None
            if t.trigger_type == TRIGGER_AUTOMATIC:
                if t.portal_type_filter is None:
                  tdef = t
                elif ob.getPortalType() in t.portal_type_filter:
                  tdef = t
            elif t.trigger_type == TRIGGER_WORKFLOW_METHOD:
230
                if action in t.method_id:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
231 232 233
                    if t.portal_type_filter is None:
                      tdef = t
                    elif ob.getPortalType() in t.portal_type_filter:
234
                      tdef = t
Jean-Paul Smets's avatar
Jean-Paul Smets committed
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
            if tdef is not None:
                # Update variables.
                former_status = self._getStatusOf(ob)
                tdef_exprs = tdef.var_exprs
                if tdef_exprs is None: tdef_exprs = {}
                status = {}
                for id, vdef in self.variables.items():
                    if not vdef.for_status:
                        continue
                    expr = None
                    if tdef_exprs.has_key(id):
                        expr = tdef_exprs[id]
                    elif not vdef.update_always and former_status.has_key(id):
                        # Preserve former value
                        value = former_status[id]
                    else:
                        if vdef.default_expr is not None:
                            expr = vdef.default_expr
                        else:
                            value = vdef.default_value
                    if expr is not None:
                        # Evaluate an expression.
                        if econtext is None:
                            # Lazily create the expression context.
                            if sci is None:
                                sci = StateChangeInfo(
                                    ob, self, former_status, tdef,
                                    None, None, None)
                            econtext = createExprContext(sci)
                        value = expr(econtext)
                    status[id] = value
        
267
                # Execute "after" scripts
Jean-Paul Smets's avatar
Jean-Paul Smets committed
268 269 270 271
                for script_name in tdef.after_script_name:
                    script = self.scripts[script_name]
                    # Pass lots of info to the script in a single parameter.
                    sci = StateChangeInfo(
272
                        ob, self, status, tdef, None, None, kwargs=kw)
273
                    script(sci)  # May throw an exception.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
274

275
                # Execute "activity" scripts
Jean-Paul Smets's avatar
Jean-Paul Smets committed
276
                for script_name in tdef.activate_script_name:
277 278 279
                    self.activate(activity='SQLQueue')\
                        .activeScript(script_name, ob.getRelativeUrl(), status, tdef.id)
                
Jean-Paul Smets's avatar
Jean-Paul Smets committed
280 281 282 283 284 285 286
    
    security.declarePrivate('activeScript')
    def activeScript(self, script_name, ob_url, status, tdef_id):
          script = self.scripts[script_name]
          ob = self.restrictedTraverse(ob_url)
          tdef = self.interactions.get(tdef_id)
          sci = StateChangeInfo(
287 288
                        ob, self, status, tdef, None, None, None)
          script(sci)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
289
  
290 291
    def _getWorkflowStateOf(self, ob, id_only=0):
          return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
292
          
Jean-Paul Smets's avatar
Jean-Paul Smets committed
293 294 295 296
Globals.InitializeClass(InteractionWorkflowDefinition)

addWorkflowFactory(InteractionWorkflowDefinition, id='interaction_workflow',
                                     title='Web-configurable interaction workflow')