cubicweb/wfutils.py
changeset 11963 64ecd4d96ac7
child 12355 c703dc95c82e
equal deleted inserted replaced
11962:36851c8b6763 11963:64ecd4d96ac7
       
     1 # copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """Workflow setup utilities.
       
    19 
       
    20 These functions work with a declarative workflow definition:
       
    21 
       
    22 .. code-block:: python
       
    23 
       
    24         {
       
    25             'etypes': 'CWGroup',
       
    26             'default': True,
       
    27             'initial_state': u'draft',
       
    28             'states': [u'draft', u'published'],
       
    29             'transitions': {
       
    30                 u'publish': {
       
    31                     'fromstates': u'draft',
       
    32                     'tostate': u'published',
       
    33                     'requiredgroups': u'managers'
       
    34                     'conditions': (
       
    35                         'U in_group X',
       
    36                         'X owned_by U'
       
    37                     )
       
    38                 }
       
    39             }
       
    40         }
       
    41 
       
    42 .. autofunction:: setup_workflow
       
    43 .. autofunction:: cleanupworkflow
       
    44 """
       
    45 
       
    46 import collections
       
    47 
       
    48 from six import text_type
       
    49 
       
    50 from cubicweb import NoResultError
       
    51 
       
    52 
       
    53 def get_tuple_or_list(value):
       
    54     if value is None:
       
    55         return None
       
    56     if not isinstance(value, (tuple, list)):
       
    57         value = (value,)
       
    58     return value
       
    59 
       
    60 
       
    61 def cleanupworkflow(cnx, wf, wfdef):
       
    62     """Cleanup an existing workflow by removing the states and transitions that
       
    63     do not exist in the given definition.
       
    64 
       
    65     :param cnx: A connexion with enough permissions to define a workflow
       
    66     :param wf: A `Workflow` entity
       
    67     :param wfdef: A workflow definition
       
    68     """
       
    69     cnx.execute(
       
    70         'DELETE State S WHERE S state_of WF, WF eid %%(wf)s, '
       
    71         'NOT S name IN (%s)' % (
       
    72             ', '.join('"%s"' % s for s in wfdef['states'])),
       
    73         {'wf': wf.eid})
       
    74 
       
    75     cnx.execute(
       
    76         'DELETE Transition T WHERE T transition_of WF, WF eid %%(wf)s, '
       
    77         'NOT T name IN (%s)' % (
       
    78             ', '.join('"%s"' % s for s in wfdef['transitions'])),
       
    79         {'wf': wf.eid})
       
    80 
       
    81 
       
    82 def setup_workflow(cnx, name, wfdef, cleanup=True):
       
    83     """Create or update a workflow definition so it matches the given
       
    84     definition.
       
    85 
       
    86     :param cnx: A connexion with enough permissions to define a workflow
       
    87     :param name: The workflow name. Used to create the `Workflow` entity, or
       
    88                  to find an existing one.
       
    89     :param wfdef: A workflow definition.
       
    90     :param cleanup: Remove extra states and transitions. Can be done separatly
       
    91                     by calling :func:`cleanupworkflow`.
       
    92     :return: The created/updated workflow entity
       
    93     """
       
    94     name = text_type(name)
       
    95     try:
       
    96         wf = cnx.find('Workflow', name=name).one()
       
    97     except NoResultError:
       
    98         wf = cnx.create_entity('Workflow', name=name)
       
    99 
       
   100     etypes = get_tuple_or_list(wfdef['etypes'])
       
   101     cnx.execute('DELETE WF workflow_of ETYPE WHERE WF eid %%(wf)s, '
       
   102                 'NOT ETYPE name IN (%s)' % ','.join('"%s"' for e in etypes),
       
   103                 {'wf': wf.eid})
       
   104     cnx.execute('SET WF workflow_of ETYPE WHERE'
       
   105                 ' NOT WF workflow_of ETYPE, WF eid %%(wf)s, ETYPE name IN (%s)'
       
   106                 % ','.join('"%s"' % e for e in etypes),
       
   107                 {'wf': wf.eid})
       
   108     if wfdef['default']:
       
   109         cnx.execute(
       
   110             'SET ETYPE default_workflow X '
       
   111             'WHERE '
       
   112             'NOT ETYPE default_workflow X, '
       
   113             'X eid %%(x)s, ETYPE name IN (%s)' % ','.join(
       
   114                 '"%s"' % e for e in etypes),
       
   115             {'x': wf.eid})
       
   116 
       
   117     states = {}
       
   118     states_transitions = collections.defaultdict(list)
       
   119     for state in wfdef['states']:
       
   120         st = wf.state_by_name(state) or wf.add_state(state)
       
   121         states[state] = st
       
   122 
       
   123     if 'initial_state' in wfdef:
       
   124         wf.cw_set(initial_state=states[wfdef['initial_state']])
       
   125 
       
   126     for trname, trdef in wfdef['transitions'].items():
       
   127         tr = (wf.transition_by_name(trname) or
       
   128               cnx.create_entity('Transition', name=trname))
       
   129         tr.cw_set(transition_of=wf)
       
   130         if trdef.get('tostate'):
       
   131             tr.cw_set(destination_state=states[trdef['tostate']])
       
   132         fromstates = get_tuple_or_list(trdef.get('fromstates', ()))
       
   133         for stname in fromstates:
       
   134             states_transitions[stname].append(tr)
       
   135 
       
   136         requiredgroups = get_tuple_or_list(trdef.get('requiredgroups', ()))
       
   137         conditions = get_tuple_or_list(trdef.get('conditions', ()))
       
   138 
       
   139         tr.set_permissions(requiredgroups, conditions, reset=True)
       
   140 
       
   141     for stname, transitions in states_transitions.items():
       
   142         state = states[stname]
       
   143         state.cw_set(allowed_transition=None)
       
   144         state.cw_set(allowed_transition=transitions)
       
   145 
       
   146     if cleanup:
       
   147         cleanupworkflow(cnx, wf, wfdef)
       
   148 
       
   149     return wf