[MERGE] workflows: added some documentation and some tests.
bzr revid: vmt@openerp.com-20130731151636-05lgd5i08rie6yqp
This commit is contained in:
commit
a507a9ea05
|
@ -14,6 +14,7 @@ OpenERP Server
|
|||
02_architecture
|
||||
03_module_dev
|
||||
04_security
|
||||
workflows
|
||||
05_test_framework
|
||||
06_misc
|
||||
deployment-gunicorn
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
.. _workflows:
|
||||
|
||||
Workflows
|
||||
=========
|
||||
|
||||
In OpenERP, a workflow is a technical artefact to manage a set of "things to do"
|
||||
associated to the records of some data model. The workflow provides a higher-
|
||||
level way to organize the things to do on a record.
|
||||
|
||||
More specifically, a workflow is a directed graph where the nodes are called
|
||||
"activities" and the arcs are called "transitions".
|
||||
|
||||
- Activities define work that should be done within the OpenERP server, such as
|
||||
changing the state of some records, or sending emails.
|
||||
|
||||
- Transitions control how the workflow progresses from activity to activity.
|
||||
|
||||
In the definition of a workflow, one can attach conditions, signals, and
|
||||
triggers to transitions, so that the behavior of the workflow depends on user
|
||||
actions (such as clicking on a button), changes to records, or arbitrary Python
|
||||
code.
|
||||
|
||||
Basics
|
||||
------
|
||||
|
||||
Defining a workflow with data files is straightforward: a record "workflow" is
|
||||
given together with records for the activities and the transitions. For
|
||||
instance, here is a simple sequence of two activities defined in XML::
|
||||
|
||||
<record id="test_workflow" model="workflow">
|
||||
<field name="name">test.workflow</field>
|
||||
<field name="osv">test.workflow.model</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">print_a()</field>
|
||||
</record>
|
||||
<record id="activity_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">print_b()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_a"/>
|
||||
<field name="act_to" ref="activity_b"/>
|
||||
</record>
|
||||
|
||||
A worfklow is always defined with respect to a particular model (the model is
|
||||
given by the attribute ``osv`` on the model ``workflow``). Methods specified in
|
||||
the activities or transitions will be called on that model.
|
||||
|
||||
In the example code above, a workflow called "test_workflow" is created. It is
|
||||
made up of two activies, named "a" and "b", and one transition, going from "a"
|
||||
to "b".
|
||||
|
||||
The first activity has its attribute ``flow_start`` set to ``True`` so that
|
||||
OpenERP knows where to start the workflow traversal after it is instanciated.
|
||||
Because ``on_create`` is set to True on the workflow record, the workflow is
|
||||
instanciated for each newly created record. (Otherwise, the workflow should be
|
||||
instanciated by other means, such as from some module Python code.)
|
||||
|
||||
When the workflow is instanciated, it begins with activity "a". That activity
|
||||
is of kind ``function``, which means that the action ``print_a()`` is a method
|
||||
call on the model ``test.workflow`` (the usual ``cr, uid, ids, context``
|
||||
arguments are passed for you).
|
||||
|
||||
The transition between "a" and "b" does not specify any condition. This means
|
||||
that the workflow instance immediately goes from "a" to "b" after "a" has been
|
||||
processed, and thus also processes activity "b".
|
||||
|
||||
Transitions
|
||||
-----------
|
||||
|
||||
Transitions provide the control structures to orchestrate a workflow. When an
|
||||
activity is completed, the workflow engine tries to get across transitions
|
||||
departing from the completed activity, towards the next activities. In their
|
||||
simplest form (as in the example above), they link activities sequentially:
|
||||
activities are processed as soon as the activities preceding them are completed.
|
||||
|
||||
Instead of running all activities in one fell swoop, it is also possible to wait
|
||||
on transitions, going through them only when some criteria are met. The criteria
|
||||
are the conditions, the signals, and the triggers. They are detailed in the
|
||||
following sections.
|
||||
|
||||
Conditions
|
||||
''''''''''
|
||||
|
||||
When an activity has been completed, its outgoing transitions are inspected to
|
||||
determine whether it is possible for the workflow instance to proceed through
|
||||
them and reach the next activities. When only a condition is defined (i.e., no
|
||||
signal or trigger is defined), the condition is evaluated by OpenERP, and if it
|
||||
evaluates to ``True``, the worklfow instance progresses through the transition.
|
||||
If the condition is not met, it will be reevaluated every time the associated
|
||||
record is modified, or by an explicit method call to do it.
|
||||
|
||||
By default, the attribute ``condition`` (i.e., the expression to be evaluated)
|
||||
is just "True", which trivially evaluates to ``True``. Note that the condition
|
||||
may be several lines long; in that case, the value of the last one determines
|
||||
whether the transition can be taken.
|
||||
|
||||
In the condition evaluation environment, several symbols are conveniently
|
||||
defined (in addition to the OpenERP ``safe_eval`` environment):
|
||||
|
||||
- all the model column names, and
|
||||
- all the browse record's attributes.
|
||||
|
||||
Signals
|
||||
'''''''
|
||||
|
||||
In addition to a condition, a transition can specify a signal name. When such
|
||||
a signal name is present, the transition is not taken directly, even if the
|
||||
condition evaluates to ``True``. Instead the transition blocks, waiting to be
|
||||
woken up.
|
||||
|
||||
In order to wake up a transition with a defined signal name, the signal must be
|
||||
sent to the workflow instance. A common way to send a signal is to use a button
|
||||
in the user interface, using the element ``<button/>`` with the signal name as
|
||||
the attribute ``name`` of the button. Once the button is clicked, the signal is
|
||||
sent to the workflow instance of the current record.
|
||||
|
||||
.. note:: The condition is still evaluated when the signal is sent to the
|
||||
workflow instance.
|
||||
|
||||
Triggers
|
||||
''''''''
|
||||
|
||||
With conditions that evaluate to ``False``, transitions are not taken (and thus
|
||||
the activity it leads to is not processed immediately). Still, the workflow
|
||||
instance can get new chances to progress across that transition by providing
|
||||
so-called triggers. The idea is that when the condition is not satisfied,
|
||||
triggers are recorded in database. Later, it is possible to wake up
|
||||
specifically the workflow instances that installed those triggers, offering
|
||||
them to reevaluate their transition conditions. This mechanism makes it cheaper
|
||||
to wake up workflow instances by targetting just a few of them (those that have
|
||||
installed the triggers) instead of all of them.
|
||||
|
||||
Triggers are recorded in database as record IDs (together with the model name)
|
||||
and refer to the workflow instance waiting for those records. The transition
|
||||
definition provides a model name (attribute ``trigger_model``) and a Python
|
||||
expression (attribute ``trigger_expression``) that evaluates to a list of record
|
||||
IDs in the given model. Any of those records can wake up the workflow instance
|
||||
they are associated with.
|
||||
|
||||
.. note:: Note that triggers are not re-installed whenever the transition is
|
||||
re-tried.
|
||||
|
||||
Splitting and joining transitions
|
||||
'''''''''''''''''''''''''''''''''
|
||||
|
||||
When multiple transitions leave the same activity, or lead to the same activity,
|
||||
OpenERP provides some control over which transitions are actually taken, or how
|
||||
the reached activity will be processed. The attributes ``split_mode`` and
|
||||
``join_mode`` on the activity are used for such control. The possible values of
|
||||
those attributes are explained below.
|
||||
|
||||
Activities
|
||||
----------
|
||||
|
||||
While the transitions can be seen as the control structures of the workflows,
|
||||
activities are the places where everything happens, from changing record states
|
||||
to sending email.
|
||||
|
||||
Different kinds of activities exist: ``Dummy``, ``Function``, ``Subflow``, and
|
||||
``Stop all``, each doing different things when the activity is processed. In
|
||||
addition to their kind, activies have other properties, detailed in the next
|
||||
sections.
|
||||
|
||||
Flow start and flow stop
|
||||
''''''''''''''''''''''''
|
||||
|
||||
The attribute ``flow_start`` is a boolean value specifying whether the activity
|
||||
is processed when the workflow is instanciated. Multiple activities can have
|
||||
their attribute ``flow_start`` set to ``True``. When instanciating a workflow
|
||||
for a record, OpenERP simply processes all of them, and evaluate all their
|
||||
outgoing transitions afterwards.
|
||||
|
||||
The attribute ``flow_stop`` is a boolean value specifying whether the activity
|
||||
stops the workflow instance. A workflow instance is considered completed when
|
||||
all its activities with the attribute ``flow_stop`` set to ``True`` are
|
||||
completed.
|
||||
|
||||
It is important for OpenERP to know when a workflow instance is completed. A
|
||||
workflow can have an activity that is actually another workflow (called a
|
||||
subflow); that activity is completed when the subflow is completed.
|
||||
|
||||
Subflow
|
||||
'''''''
|
||||
|
||||
An activity can embed a complete workflow, called a subflow (the embedding
|
||||
workflow is called the parent workflow). The workflow to instanciate is
|
||||
specified by attribute ``subflow_id``.
|
||||
|
||||
.. note:: In the GUI, that attribute can not be set unless the kind of the
|
||||
activity is ``Subflow``.
|
||||
|
||||
The activity is considered completed (and its outgoing transitions ready to be
|
||||
evaluated) when the subflow is completed (see attribute ``flow_stop`` above).
|
||||
|
||||
Sending a signal from a subflow
|
||||
'''''''''''''''''''''''''''''''
|
||||
|
||||
When a workflow is embedded in an activity (as a subflow) of a workflow, the
|
||||
sublow can send a signal from its own activities to the parent workflow by
|
||||
giving a signal name in the attribute ``signal_send``. OpenERP processes those
|
||||
activities by sending the value of ``signal_send`` prefixed by "subflow." to
|
||||
the parent workflow instance.
|
||||
|
||||
In other words, it is possible to react and get transitions in the parent
|
||||
workflow as activities are executed in the sublow.
|
||||
|
||||
Server actions
|
||||
''''''''''''''
|
||||
|
||||
An activity can run a "Server Action" by specifying its ID in the attribute
|
||||
``action_id``.
|
||||
|
||||
Python action
|
||||
'''''''''''''
|
||||
|
||||
An activity can execute some Python code, given by the attribute ``action``.
|
||||
The evaluation environment is the same as the one explained in the section
|
||||
`Conditions`_.
|
||||
|
||||
Split mode
|
||||
''''''''''
|
||||
|
||||
After an activity has been processed, its outgoing transitions are evaluated.
|
||||
Normally, if a transition can be taken, OpenERP traverses it and proceed to the
|
||||
activity the transition leads to.
|
||||
|
||||
Actually, when more than a single transition is leaving an activity, OpenERP may
|
||||
proceed or not, depending on the other transitions. That is, the conditions on
|
||||
the transitions can be combined together, and the combined result instructs
|
||||
OpenERP to traverse zero, one, or all the transitions. The way they are combined
|
||||
is controlled by the attribute ``split_mode``.
|
||||
|
||||
There are three possible split modes: ``XOR``, ``OR`` and ``AND``.
|
||||
|
||||
``XOR``
|
||||
When the transitions are combined with a ``XOR`` split mode, as soon as a
|
||||
transition has a satisfied condition, the transition is traversed and the
|
||||
others are skipped.
|
||||
|
||||
``OR``
|
||||
With the ``OR`` mode, all the transitions with a satisfied condition are
|
||||
traversed. The remaining transitions will not be evaluated later.
|
||||
|
||||
``AND``
|
||||
With the ``AND`` mode, OpenERP will wait for all outgoing transition
|
||||
conditions to be satisfied, then traverse all of them at once.
|
||||
|
||||
Join mode
|
||||
'''''''''
|
||||
|
||||
Just like outgoing transition conditions can be combined together to decide
|
||||
whether they can be traversed or not, incoming transitions can be combined
|
||||
together to decide if and when an activity may be processed. The attribute
|
||||
``join_mode`` controls that behavior.
|
||||
|
||||
There are two possible join modes: ``XOR`` and ``AND``.
|
||||
|
||||
``XOR``
|
||||
With the ``XOR`` mode, an incoming transition with a satisfied condition is
|
||||
traversed immediately, and enables the processing of the activity.
|
||||
|
||||
``AND``
|
||||
With the ``AND`` mode, OpenERP will wait until all incoming transitions have
|
||||
been traversed before enabling the processing of the activity.
|
||||
|
||||
Kinds
|
||||
'''''
|
||||
|
||||
Activities can be of different kinds: ``dummy``, ``function``, ``subflow``, or
|
||||
``stopall``. The kind defines what type of work an activity can do.
|
||||
|
||||
Dummy
|
||||
The ``dummy`` kind is for activities that do nothing, or for activities that
|
||||
only call a server action. Activities that do nothing can be used as hubs to
|
||||
gather/dispatch transitions.
|
||||
|
||||
Function
|
||||
The ``function`` kind is for activities that only need to run some Python
|
||||
code, and possibly a server action.
|
||||
|
||||
Stop all
|
||||
The ``stopall`` kind is for activities that will completely stop the
|
||||
workflow instance and mark it as completed. In addition they can also run
|
||||
some Python code.
|
||||
|
||||
Subflow
|
||||
When the kind of the activity is ``subflow``, the activity embeds another
|
||||
workflow instance. When the subflow is completed, the activity is also
|
||||
considered completed.
|
||||
|
||||
By default, the subflow is instanciated for the same record as the parent
|
||||
workflow. It is possible to change that behavior by providing Python code
|
||||
that returns a record ID (of the same data model as the subflow). The
|
||||
embedded subflow instance is then the one of the given record.
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import models
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'test-workflow',
|
||||
'version': '0.1',
|
||||
'category': 'Tests',
|
||||
'description': """A module to play with workflows.""",
|
||||
'author': 'OpenERP SA',
|
||||
'maintainer': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base'],
|
||||
'data': ['data.xml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,517 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_test_workflow_model" model="ir.ui.view">
|
||||
<field name="name">Test workflow</field>
|
||||
<field name="model">test.workflow.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Test workflow">
|
||||
<button name="a-b" string="a-b" type="workflow" icon="gtk-ok" colspan="1"/>
|
||||
<label string="a-b"/>
|
||||
<button name="trigger" string="trigger" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="trigger"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_test_workflow" model="ir.actions.act_window">
|
||||
<field name="name">Test workflow</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">test.workflow.model</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem icon="STOCK_PREFERENCES" id="base.menu_tests" name="Tests" sequence="1000000"/>
|
||||
|
||||
<menuitem id="menu_test_workflow" parent="base.menu_tests" name="Test workflow"/>
|
||||
|
||||
<menuitem id="menu_test_workflow_leaf"
|
||||
name="Test workflow"
|
||||
action="action_test_workflow"
|
||||
parent="menu_test_workflow"/>
|
||||
|
||||
|
||||
<record id="test_workflow_trigger_1" model="test.workflow.trigger">
|
||||
<!-- A single trigger record, with known ID 1 -->
|
||||
</record>
|
||||
|
||||
<!-- A simple workflow:
|
||||
|
||||
a -signal-> b -trigger-> c
|
||||
|
||||
-->
|
||||
<record id="test_workflow" model="workflow">
|
||||
<field name="name">test.workflow</field>
|
||||
<field name="osv">test.workflow.model</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">print_a()</field>
|
||||
</record>
|
||||
<record id="activity_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow"/>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">print_b()</field>
|
||||
</record>
|
||||
<record id="activity_c" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">c</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">print_c()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_a"/>
|
||||
<field name="act_to" ref="activity_b"/>
|
||||
<field name="signal">a-b</field>
|
||||
</record>
|
||||
<record id="trans_b_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_b"/>
|
||||
<field name="act_to" ref="activity_c"/>
|
||||
<field name="condition">condition()</field>
|
||||
<field name="trigger_model">test.workflow.trigger</field>
|
||||
<field name="trigger_expr_id">[1]</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow A (a single activity):
|
||||
|
||||
a
|
||||
|
||||
-->
|
||||
<record id="test_workflow_a" model="workflow">
|
||||
<field name="name">test.workflow.a</field>
|
||||
<field name="osv">test.workflow.model.a</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_a_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_a"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">dummy</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow B (a single activity):
|
||||
|
||||
a
|
||||
|
||||
The function is run when the record is created.
|
||||
|
||||
-->
|
||||
<record id="test_workflow_b" model="workflow">
|
||||
<field name="name">test.workflow.b</field>
|
||||
<field name="osv">test.workflow.model.b</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_b_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_b"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow C (a single activity):
|
||||
|
||||
a
|
||||
|
||||
The function is not run when the kind is dummy and no action_id is provided.
|
||||
-->
|
||||
<record id="test_workflow_c" model="workflow">
|
||||
<field name="name">test.workflow.c</field>
|
||||
<field name="osv">test.workflow.model.c</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_c_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_c"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">dummy</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow D (a single activity):
|
||||
|
||||
a
|
||||
|
||||
The function is run when the kind is stopall and no action_id is provided.
|
||||
-->
|
||||
<record id="test_workflow_d" model="workflow">
|
||||
<field name="name">test.workflow.d</field>
|
||||
<field name="osv">test.workflow.model.d</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_d_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_d"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">stopall</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow E:
|
||||
|
||||
a -True-> b
|
||||
|
||||
Both activities are run when the workflow is instanciated.
|
||||
-->
|
||||
<record id="test_workflow_e" model="workflow">
|
||||
<field name="name">test.workflow.e</field>
|
||||
<field name="osv">test.workflow.model.e</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_e_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_e"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
</record>
|
||||
<record id="activity_e_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_e"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_e_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_e_a"/>
|
||||
<field name="act_to" ref="activity_e_b"/>
|
||||
</record>
|
||||
|
||||
<!-- Workflow F:
|
||||
|
||||
a -signal-> b
|
||||
|
||||
Same as E but with a signal on the transition.
|
||||
-->
|
||||
<record id="test_workflow_f" model="workflow">
|
||||
<field name="name">test.workflow.f</field>
|
||||
<field name="osv">test.workflow.model.f</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_f_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_f"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
</record>
|
||||
<record id="activity_f_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_f"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_f_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_f_a"/>
|
||||
<field name="act_to" ref="activity_f_b"/>
|
||||
<field name="signal">a-b</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow G:
|
||||
|
||||
a -False-> b
|
||||
|
||||
-->
|
||||
<record id="test_workflow_g" model="workflow">
|
||||
<field name="name">test.workflow.g</field>
|
||||
<field name="osv">test.workflow.model.g</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_g_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_g"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
</record>
|
||||
<record id="activity_g_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_g"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_g_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_g_a"/>
|
||||
<field name="act_to" ref="activity_g_b"/>
|
||||
<field name="condition">False</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow H:
|
||||
|
||||
a or -> b { value: 2 }
|
||||
`-> c { value: 2 }
|
||||
|
||||
Whether the action of b or c is exectued last is non-deterministic.
|
||||
|
||||
-->
|
||||
<record id="test_workflow_h" model="workflow">
|
||||
<field name="name">test.workflow.h</field>
|
||||
<field name="osv">test.workflow.model.h</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_h_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_h"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
<field name="split_mode">OR</field>
|
||||
</record>
|
||||
<record id="activity_h_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_h"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
<record id="activity_h_c" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_h"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">c</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_h_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_h_a"/>
|
||||
<field name="act_to" ref="activity_h_b"/>
|
||||
</record>
|
||||
<record id="trans_h_a_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_h_a"/>
|
||||
<field name="act_to" ref="activity_h_c"/>
|
||||
</record>
|
||||
|
||||
<!-- Workflow I:
|
||||
|
||||
a or -> b { value: 2 }
|
||||
`false> c { value: 3 }
|
||||
|
||||
-->
|
||||
<record id="test_workflow_i" model="workflow">
|
||||
<field name="name">test.workflow.i</field>
|
||||
<field name="osv">test.workflow.model.i</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_i_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_i"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
<field name="split_mode">OR</field>
|
||||
</record>
|
||||
<record id="activity_i_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_i"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
<record id="activity_i_c" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_i"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">c</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 3})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_i_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_i_a"/>
|
||||
<field name="act_to" ref="activity_i_b"/>
|
||||
</record>
|
||||
<record id="trans_i_a_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_i_a"/>
|
||||
<field name="act_to" ref="activity_i_c"/>
|
||||
<field name="condition">False</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow J:
|
||||
|
||||
a and -> b { value: 2 }
|
||||
`False> c { value: 3 }
|
||||
|
||||
This will stay in a because all transitions should be True at the same time.
|
||||
|
||||
-->
|
||||
<record id="test_workflow_j" model="workflow">
|
||||
<field name="name">test.workflow.j</field>
|
||||
<field name="osv">test.workflow.model.j</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_j_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_j"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
<field name="split_mode">AND</field>
|
||||
</record>
|
||||
<record id="activity_j_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_j"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
<record id="activity_j_c" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_j"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">c</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 3})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_j_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_j_a"/>
|
||||
<field name="act_to" ref="activity_j_b"/>
|
||||
</record>
|
||||
<record id="trans_j_a_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_j_a"/>
|
||||
<field name="act_to" ref="activity_j_c"/>
|
||||
<field name="condition">False</field>
|
||||
</record>
|
||||
|
||||
<!-- Workflow K:
|
||||
|
||||
a xor -> b { value: 2 }
|
||||
`> c { value: 2 }
|
||||
|
||||
Only one (truish) transition is taken with a XOR.
|
||||
|
||||
-->
|
||||
<record id="test_workflow_k" model="workflow">
|
||||
<field name="name">test.workflow.k</field>
|
||||
<field name="osv">test.workflow.model.k</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_k_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_k"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
<field name="split_mode">XOR</field>
|
||||
</record>
|
||||
<record id="activity_k_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_k"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
<record id="activity_k_c" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_k"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">c</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_k_a_b" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_k_a"/>
|
||||
<field name="act_to" ref="activity_k_b"/>
|
||||
</record>
|
||||
<record id="trans_k_a_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_k_a"/>
|
||||
<field name="act_to" ref="activity_k_c"/>
|
||||
</record>
|
||||
|
||||
<!-- Workflow L:
|
||||
|
||||
a -> xor c { value: 3 }
|
||||
b ´
|
||||
a -> and d { value: 3 }
|
||||
b ´
|
||||
|
||||
c is run for each incoming (and taken) transition.
|
||||
d is run once when all its incoming transitions are taken at the same time.
|
||||
|
||||
-->
|
||||
<record id="test_workflow_l" model="workflow">
|
||||
<field name="name">test.workflow.l</field>
|
||||
<field name="osv">test.workflow.model.l</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="activity_l_a" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_l"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">a</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 1})</field>
|
||||
<field name="split_mode">OR</field>
|
||||
</record>
|
||||
<record id="activity_l_b" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_l"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">b</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 2})</field>
|
||||
<field name="split_mode">OR</field>
|
||||
</record>
|
||||
<record id="activity_l_c" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_l"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">c</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 3})</field>
|
||||
<field name="join_mode">XOR</field>
|
||||
</record>
|
||||
<record id="activity_l_d" model="workflow.activity">
|
||||
<field name="wkf_id" ref="test_workflow_l"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">d</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'value': 3})</field>
|
||||
<field name="join_mode">AND</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_l_a_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_l_a"/>
|
||||
<field name="act_to" ref="activity_l_c"/>
|
||||
</record>
|
||||
<record id="trans_l_b_c" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_l_b"/>
|
||||
<field name="act_to" ref="activity_l_c"/>
|
||||
</record>
|
||||
|
||||
<record id="trans_l_a_d" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_l_a"/>
|
||||
<field name="act_to" ref="activity_l_d"/>
|
||||
</record>
|
||||
<record id="trans_l_b_d" model="workflow.transition">
|
||||
<field name="act_from" ref="activity_l_b"/>
|
||||
<field name="act_to" ref="activity_l_d"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import openerp
|
||||
|
||||
class m(openerp.osv.orm.Model):
|
||||
""" A model for which we will define a workflow (see data.xml). """
|
||||
_name = 'test.workflow.model'
|
||||
|
||||
def print_(self, cr, uid, ids, s, context=None):
|
||||
print ' Running activity `%s` for record %s' % (s, ids)
|
||||
return True
|
||||
|
||||
def print_a(self, cr, uid, ids, context=None):
|
||||
return self.print_(cr, uid, ids, 'a', context)
|
||||
|
||||
def print_b(self, cr, uid, ids, context=None):
|
||||
return self.print_(cr, uid, ids, 'b', context)
|
||||
|
||||
def print_c(self, cr, uid, ids, context=None):
|
||||
return self.print_(cr, uid, ids, 'c', context)
|
||||
|
||||
def condition(self, cr, uid, ids, context=None):
|
||||
m = self.pool['test.workflow.trigger']
|
||||
for r in m.browse(cr, uid, [1], context=context):
|
||||
if not r.value:
|
||||
return False
|
||||
return True
|
||||
|
||||
def trigger(self, cr, uid, context=None):
|
||||
return openerp.workflow.trg_trigger(uid, 'test.workflow.trigger', 1, cr)
|
||||
|
||||
class n(openerp.osv.orm.Model):
|
||||
""" A model used for the trigger feature. """
|
||||
_name = 'test.workflow.trigger'
|
||||
_columns = { 'value': openerp.osv.fields.boolean('Value') }
|
||||
_defaults = { 'value': False }
|
||||
|
||||
class a(openerp.osv.orm.Model):
|
||||
_name = 'test.workflow.model.a'
|
||||
_columns = { 'value': openerp.osv.fields.integer('Value') }
|
||||
_defaults = { 'value': 0 }
|
||||
|
||||
class b(openerp.osv.orm.Model):
|
||||
_name = 'test.workflow.model.b'
|
||||
_inherit = 'test.workflow.model.a'
|
||||
|
||||
class c(openerp.osv.orm.Model):
|
||||
_name = 'test.workflow.model.c'
|
||||
_inherit = 'test.workflow.model.a'
|
||||
|
||||
class d(openerp.osv.orm.Model):
|
||||
_name = 'test.workflow.model.d'
|
||||
_inherit = 'test.workflow.model.a'
|
||||
|
||||
class e(openerp.osv.orm.Model):
|
||||
_name = 'test.workflow.model.e'
|
||||
_inherit = 'test.workflow.model.a'
|
||||
|
||||
for name in 'bcdefghijkl':
|
||||
type(
|
||||
name,
|
||||
(openerp.osv.orm.Model,),
|
||||
{
|
||||
'_name': 'test.workflow.model.%s' % name,
|
||||
'_inherit': 'test.workflow.model.a',
|
||||
})
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_workflow
|
||||
|
||||
fast_suite = [
|
||||
]
|
||||
|
||||
checks = [
|
||||
test_workflow,
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,183 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tests import common
|
||||
|
||||
|
||||
class test_workflows(common.TransactionCase):
|
||||
|
||||
def check_activities(self, model_name, i, names):
|
||||
""" Check that the record i has workitems in the given activity names.
|
||||
"""
|
||||
instance = self.registry('workflow.instance')
|
||||
workitem = self.registry('workflow.workitem')
|
||||
|
||||
# Given the workflow instance associated to the record ...
|
||||
instance_id = instance.search(
|
||||
self.cr, SUPERUSER_ID,
|
||||
[('res_type', '=', model_name), ('res_id', '=', i)])
|
||||
self.assertTrue( instance_id, 'A workflow instance is expected.')
|
||||
|
||||
# ... get all its workitems ...
|
||||
workitem_ids = workitem.search(
|
||||
self.cr, SUPERUSER_ID,
|
||||
[('inst_id', '=', instance_id[0])])
|
||||
self.assertTrue(
|
||||
workitem_ids,
|
||||
'The workflow instance should have workitems.')
|
||||
|
||||
# ... and check the activity the are in against the provided names.
|
||||
workitem_records = workitem.browse(
|
||||
self.cr, SUPERUSER_ID, workitem_ids)
|
||||
self.assertEqual(
|
||||
sorted([item.act_id.name for item in workitem_records]),
|
||||
sorted(names))
|
||||
|
||||
def check_value(self, model_name, i, value):
|
||||
""" Check that the record i has the given value.
|
||||
"""
|
||||
model = self.registry(model_name)
|
||||
record = model.read(self.cr, SUPERUSER_ID, [i], ['value'])[0]
|
||||
self.assertEqual(record['value'], value)
|
||||
|
||||
def test_workflow(self):
|
||||
model = self.registry('test.workflow.model')
|
||||
trigger = self.registry('test.workflow.trigger')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
|
||||
# a -> b is just a signal.
|
||||
model.signal_workflow(self.cr, SUPERUSER_ID, [i], 'a-b')
|
||||
self.check_activities(model._name, i, ['b'])
|
||||
|
||||
# b -> c is a trigger (which is False),
|
||||
# so we remain in the b activity.
|
||||
model.trigger(self.cr, SUPERUSER_ID, [i])
|
||||
self.check_activities(model._name, i, ['b'])
|
||||
|
||||
# b -> c is a trigger (which is set to True).
|
||||
# so we go in c when the trigger is called.
|
||||
trigger.write(self.cr, SUPERUSER_ID, [1], {'value': True})
|
||||
model.trigger(self.cr, SUPERUSER_ID)
|
||||
self.check_activities(model._name, i, ['c'])
|
||||
|
||||
self.assertEqual(
|
||||
True,
|
||||
True)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_a(self):
|
||||
model = self.registry('test.workflow.model.a')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 0)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_b(self):
|
||||
model = self.registry('test.workflow.model.b')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 1)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_c(self):
|
||||
model = self.registry('test.workflow.model.c')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 0)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_d(self):
|
||||
model = self.registry('test.workflow.model.d')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 1)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_e(self):
|
||||
model = self.registry('test.workflow.model.e')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['b'])
|
||||
self.check_value(model._name, i, 2)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_f(self):
|
||||
model = self.registry('test.workflow.model.f')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 1)
|
||||
|
||||
model.signal_workflow(self.cr, SUPERUSER_ID, [i], 'a-b')
|
||||
self.check_activities(model._name, i, ['b'])
|
||||
self.check_value(model._name, i, 2)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_g(self):
|
||||
model = self.registry('test.workflow.model.g')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 1)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_h(self):
|
||||
model = self.registry('test.workflow.model.h')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['b', 'c'])
|
||||
self.check_value(model._name, i, 2)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_i(self):
|
||||
model = self.registry('test.workflow.model.i')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['b'])
|
||||
self.check_value(model._name, i, 2)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_j(self):
|
||||
model = self.registry('test.workflow.model.j')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['a'])
|
||||
self.check_value(model._name, i, 1)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_k(self):
|
||||
model = self.registry('test.workflow.model.k')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
# Non-determinisitic: can be b or c
|
||||
# self.check_activities(model._name, i, ['b'])
|
||||
# self.check_activities(model._name, i, ['c'])
|
||||
self.check_value(model._name, i, 2)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
||||
|
||||
def test_workflow_l(self):
|
||||
model = self.registry('test.workflow.model.l')
|
||||
|
||||
i = model.create(self.cr, SUPERUSER_ID, {})
|
||||
self.check_activities(model._name, i, ['c', 'c', 'd'])
|
||||
self.check_value(model._name, i, 3)
|
||||
|
||||
model.unlink(self.cr, SUPERUSER_ID, [i])
|
|
@ -19,62 +19,92 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Evaluate workflow code found in activity actions and transition conditions.
|
||||
"""
|
||||
|
||||
import openerp
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
|
||||
class Env(dict):
|
||||
def __init__(self, cr, uid, model, ids):
|
||||
"""
|
||||
Dictionary class used as an environment to evaluate workflow code (such as
|
||||
the condition on transitions).
|
||||
|
||||
This environment provides sybmols for cr, uid, id, model name, model
|
||||
instance, column names, and all the record (the one obtained by browsing
|
||||
the provided ID) attributes.
|
||||
"""
|
||||
def __init__(self, cr, uid, model, id):
|
||||
self.cr = cr
|
||||
self.uid = uid
|
||||
self.model = model
|
||||
self.ids = ids
|
||||
self.id = id
|
||||
self.ids = [id]
|
||||
self.obj = openerp.registry(cr.dbname)[model]
|
||||
self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if (key in self.columns) or (key in dir(self.obj)):
|
||||
res = self.obj.browse(self.cr, self.uid, self.ids[0])
|
||||
res = self.obj.browse(self.cr, self.uid, self.id)
|
||||
return res[key]
|
||||
else:
|
||||
return super(Env, self).__getitem__(key)
|
||||
|
||||
def _eval_expr(cr, ident, workitem, action):
|
||||
ret=False
|
||||
assert action, 'You used a NULL action in a workflow, use dummy node instead.'
|
||||
for line in action.split('\n'):
|
||||
def _eval_expr(cr, ident, workitem, lines):
|
||||
"""
|
||||
Evaluate each line of ``lines`` with the ``Env`` environment, returning
|
||||
the value of the last line.
|
||||
"""
|
||||
assert lines, 'You used a NULL action in a workflow, use dummy node instead.'
|
||||
uid, model, id = ident
|
||||
result = False
|
||||
for line in lines.split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
uid=ident[0]
|
||||
model=ident[1]
|
||||
ids=[ident[2]]
|
||||
if line =='True':
|
||||
ret=True
|
||||
elif line =='False':
|
||||
ret=False
|
||||
if line == 'True':
|
||||
result = True
|
||||
elif line == 'False':
|
||||
result = False
|
||||
else:
|
||||
env = Env(cr, uid, model, ids)
|
||||
ret = eval(line, env, nocopy=True)
|
||||
return ret
|
||||
env = Env(cr, uid, model, id)
|
||||
result = eval(line, env, nocopy=True)
|
||||
return result
|
||||
|
||||
def execute_action(cr, ident, workitem, activity):
|
||||
obj = openerp.registry(cr.dbname)['ir.actions.server']
|
||||
ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]}
|
||||
result = obj.run(cr, ident[0], [activity['action_id']], ctx)
|
||||
"""
|
||||
Evaluate the ir.actions.server action specified in the activity.
|
||||
"""
|
||||
uid, model, id = ident
|
||||
ir_actions_server = openerp.registry(cr.dbname)['ir.actions.server']
|
||||
context = { 'active_model': model, 'active_id': id, 'active_ids': [id] }
|
||||
result = ir_actions_server.run(cr, uid, [activity['action_id']], context)
|
||||
return result
|
||||
|
||||
def execute(cr, ident, workitem, activity):
|
||||
"""
|
||||
Evaluate the action specified in the activity.
|
||||
"""
|
||||
return _eval_expr(cr, ident, workitem, activity['action'])
|
||||
|
||||
def check(cr, workitem, ident, transition, signal):
|
||||
"""
|
||||
Test if a transition can be taken. The transition can be taken if:
|
||||
|
||||
- the signal name matches,
|
||||
- the uid is SUPERUSER_ID or the user groups contains the transition's
|
||||
group,
|
||||
- the condition evaluates to a truish value.
|
||||
"""
|
||||
if transition['signal'] and signal != transition['signal']:
|
||||
return False
|
||||
|
||||
uid = ident[0]
|
||||
if transition['group_id'] and uid != 1:
|
||||
if uid != openerp.SUPERUSER_ID and transition['groups_id']:
|
||||
registry = openerp.registry(cr.dbname)
|
||||
user_groups = registry['res.users'].read(cr, uid, [uid], ['groups_id'])[0]['groups_id']
|
||||
if not transition['group_id'] in user_groups:
|
||||
if transition['group_id'] not in user_groups:
|
||||
return False
|
||||
|
||||
return _eval_expr(cr, ident, workitem, transition['condition'])
|
||||
|
|
Loading…
Reference in New Issue