Merge lp:~openerp-community/openobject-server/context-in-workflows into lp:openobject-server

Proposed by Raphaël Valyi - http://www.akretion.com
Status: Work in progress
Proposed branch: lp:~openerp-community/openobject-server/context-in-workflows
Merge into: lp:openobject-server
Diff against target: 392 lines (+61/-53)
5 files modified
openerp/osv/osv.py (+4/-4)
openerp/workflow/instance.py (+10/-10)
openerp/workflow/wkf_expr.py (+13/-10)
openerp/workflow/wkf_service.py (+13/-8)
openerp/workflow/workitem.py (+21/-21)
To merge this branch: bzr merge lp:~openerp-community/openobject-server/context-in-workflows
Reviewer Review Type Date Requested Status
Stefan Rijnhart (Opener) (community) Needs Fixing
Christophe CHAUVET (community) Needs Information
Raphaël Valyi - http://www.akretion.com (community) Needs Fixing
OpenERP Core Team Pending
Review via email: mp+85518@code.launchpad.net

Description of the change

Work in progress, seem my merge message.
A way to test the current limitation where a bug is caused because the workflow engine would pass some unexpected context param is just to create an order with manual invoicing. Validate it and then create the invoice from the sale order.

A bug like that will result:
[..]
  File "/home/rvalyi/DEV/openerp/openerp6.1/addons/sale/sale.py", line 457, in manual_invoice
[..]
  File "/home/rvalyi/DEV/openerp/openerp6.1/server/openerp/workflow/wkf_expr.py", line 85, in check
    return _eval_expr(cr, ident, workitem, transition['condition'], context)
  File "/home/rvalyi/DEV/openerp/openerp6.1/server/openerp/workflow/wkf_expr.py", line 59, in _eval_expr
    ret = eval(line, env, nocopy=True)
  File "/home/rvalyi/DEV/openerp/openerp6.1/server/openerp/tools/safe_eval.py", line 294, in safe_eval
    return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
  File "", line 1, in <module>
  File "/home/rvalyi/DEV/openerp/openerp6.1/server/openerp/osv/orm.py", line 396, in function_proxy
    return attr(self._cr, self._uid, [self._id], *args, **kwargs)
TypeError: test_state() got an unexpected keyword argument 'context'

Probably, to make it compatible we should inspect the method signature a bit like Albert did in it former "browse_executer" here:
https://code.launchpad.net/~albert-nan/openobject-server/workflow-context/+merge/12256
(in orm.py)

To post a comment you must log in.
Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) :
review: Needs Fixing
Revision history for this message
Christophe CHAUVET (christophe-chauvet) wrote :

+1 to have context on workflow

It does also transmit context to the workflow, if it define on button element as

<button name="wkf_test" context="{'test': 'ok'}"/>

Christophe.

review: Needs Information
Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

FWIW, you can have a look at the branch where I tested merging the same thing[1].

As I explain on my merge prop there, I think we need more work on the addons side in order to get any benefit from this work, and that will be much more tricky. I started a wip of changing addons in this manner[2] but it is far from complete or working yet, and perhaps it's too much at this point.

[1] lp:~openerp-dev/openobject-server/trunk-workflow-context-odo
[2] lp:~openerp-dev/openobject-addons/trunk-workflow-context-odo

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

Hi Raphaël,

thanks for the effort! A tiny detail in openerp/workflow/wkf_expr.py, line 133: looks like you lose the context here.

I see the problem with the API change and existing methods that Olivier also points out. However, it is one thing to make all models pass the context when calling the workflow service API, but for 6.1 it might minimally suffice to just make the methods mentioned in wkf_activity.action accept the context keyword argument. That should be doable.

Cheers,
Stefan.

review: Needs Fixing
Revision history for this message
Fabien (Open ERP) (fp-tinyerp) wrote :

Olivier,

Are you sure it's a good idea to allow context in workflows ?

Workflows are not used for user interface interactions, but for
controlling the
evolution of the object. It has no sense to put context on constraints
and, for
the same reason, I don't think we should allow context in workflows.

An activity should not depend on a context but on data stored on the object.
The goal of the workflow is to easily modify it, add/remove some
transitions, etc.
If we depend on contexts, we may loose this modularity.

I am still not sure about this, but I would not allow the context to be
used in
workflows, for the same reason that it has no sense to put context on
constraints.

On 13/12/11 19:05, Olivier Dony (OpenERP) wrote:
> FWIW, you can have a look at the branch where I tested merging the same thing[1].
>
> As I explain on my merge prop there, I think we need more work on the addons side in order to get any benefit from this work, and that will be much more tricky. I started a wip of changing addons in this manner[2] but it is far from complete or working yet, and perhaps it's too much at this point.
>
>
> [1] lp:~openerp-dev/openobject-server/trunk-workflow-context-odo
> [2] lp:~openerp-dev/openobject-addons/trunk-workflow-context-odo
>

Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :
Download full text (4.5 KiB)

Hello Fabien,

1) If I'm not wrong about it, on issue is that if you don't pass the
context you'll for instance miss important things like translations. So for
instance if I create an invoice upon the order (through the workflow) but
if if that invoice fails to create for some reason, the error message it
will display will not be localized properly (you'll eventually find out the
sale_order.user_id lang or something like that but won't be very precise.
Or am I wrong? Also I believe you were using some Python frame inspection
for that but is was extremely slow, am I correct, was it related?

2) we also have an issue with the methods the workflow call: those methods
are easily called from different paths and easily they need the standard
context system to be modular.
Look at such methods:
http://bazaar.launchpad.net/~akretion-team/openobject-addons/sale-modular-picking-better-context/revision/5761
or
http://bazaar.launchpad.net/~akretion-team/openobject-addons/sale-modular-picking-better-context/revision/5760

Actually for a modular API, we really have the case were Sebastien Beau
would need to override the beginning of the method chain to inject some
custom context key.
Later in the call chain, the will override again and hope he will get his
context back so he can take some custom action. Usually it's done in the
same transaction and it seems overkill to write some data in the database
to just re-read it a few method calls later while it could come from memory.

The issue is that is seems pointless then to have all those clean methods
correctly propagating the context and using it normally being usually
called by some piggy workflow method that wouldn't pass them the context
and would instead use a useless meaningless arg* signature.

Wouldn't it be better to just propagate the context all the way through?

IMHO it was right to say constraints should not depend on context. But for
the workflow I'm really not sure. I see many modularity benefit of having
that. A system like Christophe gives with things like <button
name="wkf_test" context="{'test': 'ok'}"/> could be extremely powerful with
context being propagated.

So in any case I'm not 100% what is best here. But probably if we decide
workflows don't propagate the context, we should still refactor the methods
it call to support the standard context arguments so it can be used in a
more modular way, no?

So at the end what would we decide:
1) workflow propagate the context (it doesn't mean it should use it itself;
this can be checked) ?
2) the methods called by workflow don't propagate the context at all either
(sounds it sucks to me, cause would force overkill database accesses when
we are already not that fast in transactions)
3) the methods called by the workflow engine, pass the context if they have
some passed. However the workflow engine don't give them the context, so
it's useful only in overrides or when those methods are called outside from
the workflow engine.

What do you guys think?

On Tue, Dec 13, 2011 at 5:58 PM, Fabien (Open ERP) <email address hidden> wrote:

> Olivier,
>
> Are you sure it's a good idea to allow context in workflows ?
>
> Workflows are not used for user interfac...

Read more...

Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

On 12/13/2011 08:58 PM, Fabien (Open ERP) wrote:
> Olivier,
>
> Are you sure it's a good idea to allow context in workflows ?
>
> Workflows are not used for user interface interactions, but for
> controlling the
> evolution of the object. It has no sense to put context on constraints
> and, for
> the same reason, I don't think we should allow context in workflows.

Actually, workflows execute business operations requested by users, and
are mostly called in the context of user transactions. When a workflow
button sends a signal, the workflow engine actually produces the result
to send back to the user, by invoking business code.
Granted, the workflow activities themselves don't need the context, but
the business code they are calling does.

> An activity should not depend on a context but on data stored on the object.
> The goal of the workflow is to easily modify it, add/remove some
> transitions, etc.
> If we depend on contexts, we may loose this modularity.

Indeed the goal is not to make the workflow itself depend on the
context, but to have it propagate it to the business code that it calls.
The workflow is a controller that invokes business code in the context
of a user transaction, and business code is in general context-aware, so
why would the workflow engine break this contract?

> I am still not sure about this, but I would not allow the context to
> be used in workflows, for the same reason that it has no sense to
> put context on constraints.

We don't want constraints to be context-aware because it would be a bad
incentive for developers. And they really don't need it, because
constraints should never alter the data in any way (not call mutating
business code) and have their error messages properly localized by the
ORM. I think this is different for the workflow engine, which acts as a
controller.

BTW, we'll have to address this in a more global way for next release if
we change the API to have a thread-local (or similar) handling of the
transaction context. If we can allow that transaction context to be
propagated to business code in all circumstances, and that without
making it available to the actual workflow engine, I'm happy with it.
However if we have to choose, it looks to me that having business code
behave properly in all circumstances (even when called via workflows) is
a more important goal than preventing the design of context-dependent
workflows.

Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :
Download full text (5.1 KiB)

Ok,

So Olivier and Fabien, I'm happy that it seems we totally share the same
opinion: none of us wants workflow behavior to be context driven, this
would be bad. BUT, there is not valid reason usual business methods to be
half broken because today when they are invoked through the workflow they
receive no context.

What I propose is:

1) as a first step: change the signature of those common business methods
such as:
http://bazaar.launchpad.net/~akretion-team/openobject-addons/sale-modular-picking-better-context/revision/5761
or
http://bazaar.launchpad.net/~akretion-team/openobject-addons/sale-modular-picking-better-context/revision/5760
So they accept the standard context argument and properly propagate it.

Honestly there aren't too many such actions, if we quickly review stock,
sale, purchase and account, with probably a change in around 20 methods I'm
pretty sure we would cover 90% of the use cases.

I insist today those methods usually have a nonsense useless *args argument
that limit a lot their capabilities when overridden or when calling other
normal business methods. So I mean this is not like if we were breaking
something already self consistent: it's not consistent today.
Today that *args is not used, so it's quite safe I think to replace it by a
"context" arg, basic testing would ensure we are not breaking anything.

2) Eventually, still up to debate, the workflow engine propagate the
context (I'm not 100% sure but I would go for that while still having the
strong policy that workflow themselves should not be context dependent).
If the workflow engine starts passing the context, we should avoid passing
the context where it's not expected. May be one way is to simply pass it as
the last argument (would not break where *args is expected) rather than a
keyword argument, OR, eventually we do some signature checking before
calling as Albert did (sounds less good to me but not sure).

3) Eventually workflow calls and clients can be modified to properly pass
the context when triggering workflow actions. But once most of the common
business signatures are in place, to me this is no API change so this could
be gradually added during the 6.1 release life cycle.

As for keeping the context and the cursor and uid in thread local, of
course, I think this will need to be done in the future. But this may be
effective may be only in 2 years, so we also need a decent solution for the
next 2 years.

On Wed, Dec 14, 2011 at 8:29 AM, Olivier Dony (OpenERP) <email address hidden>wrote:

> On 12/13/2011 08:58 PM, Fabien (Open ERP) wrote:
> > Olivier,
> >
> > Are you sure it's a good idea to allow context in workflows ?
> >
> > Workflows are not used for user interface interactions, but for
> > controlling the
> > evolution of the object. It has no sense to put context on constraints
> > and, for
> > the same reason, I don't think we should allow context in workflows.
>
> Actually, workflows execute business operations requested by users, and
> are mostly called in the context of user transactions. When a workflow
> button sends a signal, the workflow engine actually produces the result
> to send back to the user, by invoking business code.
> Granted, t...

Read more...

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

> 2) Eventually, still up to debate, the workflow engine propagate the
> context (I'm not 100% sure but I would go for that while still having the
> strong policy that workflow themselves should not be context dependent).
> If the workflow engine starts passing the context, we should avoid passing
> the context where it's not expected. May be one way is to simply pass it as
> the last argument (would not break where *args is expected) rather than a
> keyword argument, OR, eventually we do some signature checking before
> calling as Albert did (sounds less good to me but not sure).

The simplest is just to add the keyword argument. If you install all addons from openobject-addons/trunk (well, n10l_de breaks for me) you can get an idea of the scale of the impact. The query "SELECT wkf.osv, wkf_activity.action FROM wkf, wkf_activity WHERE wkf_id = wkf.id AND kind = 'function' AND action LIKE '%()%'" gives you some 80 methods to convert.

The person to prepare such a merge could temporarily hack in Nan's introspection check to iterate over these methods at loading time to see if all have been covered.

The question is whether this 'last minute'(?) API change is fair towards 3rd party addon developers but AFAIK stranger things have been pulled off in this community ;-) In that respect it would be informative when a proper RC1 is planned.

Cheers,
Stefan.

Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

On 12/14/2011 02:28 PM, Stefan Rijnhart (Therp) wrote:
> The simplest is just to add the keyword argument. If you install all
> addons from openobject-addons/trunk (well, n10l_de breaks for me) you
> can get an idea of the scale of the impact. The query "SELECT
> wkf.osv, wkf_activity.action FROM wkf, wkf_activity WHERE wkf_id =
> wkf.id AND kind = 'function' AND action LIKE '%()%'" gives you some
> 80 methods to convert.

You also need to be careful with a few other fields that are evaluated
during the course of workflow execution, such as 'trigger_expr_id' or
'condition' on workflow transitions.

> The person to prepare such a merge could temporarily hack in Nan's
> introspection check to iterate over these methods at loading time to
> see if all have been covered.

Yup. Similarly, changing browse_record to default to an empty dict
context when no context is passed would make it pass that context
automatically to all workflow-called methods. If this does not trigger
too many false positives in other places, running the full test suite
(as runbot does) with such a server would validate that all relevant
methods have been indeed updated.

> The question is whether this 'last minute'(?) API change is fair
> towards 3rd party addon developers but AFAIK stranger things have
> been pulled off in this community ;-) In that respect it would be
> informative when a proper RC1 is planned.

This seems acceptable before releasing RC1, but we probably won't have
the resources to do it internally. If anyone from the community is ready
to do this (just changing the API of business methods called by
workflows to allow an optional context), we would however be happy to
review and merge it.
RC1 is planned very soon (if possible before the end of the year),
provided we are able to finish stabilizing the new web client and bring
down bugs and merge props to an acceptable level (something like 0
remaining Medium+ bugs, less than 40 pending R&D merge proposals, and 0
pending merge proposals from the community)
This post[1] from Fabien on the forum provides some more details on what
we're still working on.

[1] http://www.openerp.com/forum/post96294.html#p96294

PS: sorry if you receive this msg twice, I'm re-posting to the mp for
the record.

Unmerged revisions

3875. By Raphaël Valyi - http://www.akretion.com

[REF] merged (with changes) v5.0 NaNTic lp:~albert-nan/openobject-server/workflow-context branch to make workflow engine propagate the context arg
warning: will not work properly yet because:
1) clients like GTK don't pass the context when calling a workflow action (button)
2) when called from Python code, it seems this code will propagate the context, but it may provocate extra context param
call errors when calling object methods. Probably we should replicate the signature check NaN put in its former "browse_executer" class.
Warning, includes a few debug print statements at this stage

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openerp/osv/osv.py'
--- openerp/osv/osv.py 2011-10-01 01:22:10 +0000
+++ openerp/osv/osv.py 2011-12-13 17:01:27 +0000
@@ -180,16 +180,16 @@
180 cr.close()180 cr.close()
181 return res181 return res
182182
183 def exec_workflow_cr(self, cr, uid, obj, method, *args):183 def exec_workflow_cr(self, cr, uid, obj, method, id, context=None):
184 wf_service = netsvc.LocalService("workflow")184 wf_service = netsvc.LocalService("workflow")
185 return wf_service.trg_validate(uid, obj, args[0], method, cr)185 return wf_service.trg_validate(uid, obj, id, method, cr, context)
186186
187 @check187 @check
188 def exec_workflow(self, db, uid, obj, method, *args):188 def exec_workflow(self, db, uid, obj, method, id, context=None):
189 cr = pooler.get_db(db).cursor()189 cr = pooler.get_db(db).cursor()
190 try:190 try:
191 try:191 try:
192 res = self.exec_workflow_cr(cr, uid, obj, method, *args)192 res = self.exec_workflow_cr(cr, uid, obj, method, id, context)
193 cr.commit()193 cr.commit()
194 except Exception:194 except Exception:
195 cr.rollback()195 cr.rollback()
196196
=== modified file 'openerp/workflow/instance.py'
--- openerp/workflow/instance.py 2011-02-07 12:57:23 +0000
+++ openerp/workflow/instance.py 2011-12-13 17:01:27 +0000
@@ -25,14 +25,14 @@
25import openerp.netsvc as netsvc25import openerp.netsvc as netsvc
26import openerp.pooler as pooler26import openerp.pooler as pooler
2727
28def create(cr, ident, wkf_id):28def create(cr, ident, wkf_id, context=None):
29 (uid,res_type,res_id) = ident29 (uid,res_type,res_id) = ident
30 cr.execute('insert into wkf_instance (res_type,res_id,uid,wkf_id) values (%s,%s,%s,%s) RETURNING id', (res_type,res_id,uid,wkf_id))30 cr.execute('insert into wkf_instance (res_type,res_id,uid,wkf_id) values (%s,%s,%s,%s) RETURNING id', (res_type,res_id,uid,wkf_id))
31 id_new = cr.fetchone()[0]31 id_new = cr.fetchone()[0]
32 cr.execute('select * from wkf_activity where flow_start=True and wkf_id=%s', (wkf_id,))32 cr.execute('select * from wkf_activity where flow_start=True and wkf_id=%s', (wkf_id,))
33 res = cr.dictfetchall()33 res = cr.dictfetchall()
34 stack = []34 stack = []
35 workitem.create(cr, res, id_new, ident, stack=stack)35 workitem.create(cr, res, id_new, ident, stack=stack, context=context)
36 update(cr, id_new, ident)36 update(cr, id_new, ident)
37 return id_new37 return id_new
3838
@@ -40,24 +40,24 @@
40 (uid,res_type,res_id) = ident40 (uid,res_type,res_id) = ident
41 cr.execute('delete from wkf_instance where res_id=%s and res_type=%s', (res_id,res_type))41 cr.execute('delete from wkf_instance where res_id=%s and res_type=%s', (res_id,res_type))
4242
43def validate(cr, inst_id, ident, signal, force_running=False):43def validate(cr, inst_id, ident, signal, force_running=False, context=None):
44 cr.execute("select * from wkf_workitem where inst_id=%s", (inst_id,))44 cr.execute("select * from wkf_workitem where inst_id=%s", (inst_id,))
45 stack = []45 stack = []
46 for witem in cr.dictfetchall():46 for witem in cr.dictfetchall():
47 stack = []47 stack = []
48 workitem.process(cr, witem, ident, signal, force_running, stack=stack)48 workitem.process(cr, witem, ident, signal, force_running, stack=stack, context=context)
49 # An action is returned49 # An action is returned
50 _update_end(cr, inst_id, ident)50 _update_end(cr, inst_id, ident, context)
51 return stack and stack[0] or False51 return stack and stack[0] or False
5252
53def update(cr, inst_id, ident):53def update(cr, inst_id, ident, context=None):
54 cr.execute("select * from wkf_workitem where inst_id=%s", (inst_id,))54 cr.execute("select * from wkf_workitem where inst_id=%s", (inst_id,))
55 for witem in cr.dictfetchall():55 for witem in cr.dictfetchall():
56 stack = []56 stack = []
57 workitem.process(cr, witem, ident, stack=stack)57 workitem.process(cr, witem, ident, stack=stack, context=context)
58 return _update_end(cr, inst_id, ident)58 return _update_end(cr, inst_id, ident, context)
5959
60def _update_end(cr, inst_id, ident):60def _update_end(cr, inst_id, ident, context=None):
61 cr.execute('select wkf_id from wkf_instance where id=%s', (inst_id,))61 cr.execute('select wkf_id from wkf_instance where id=%s', (inst_id,))
62 wkf_id = cr.fetchone()[0]62 wkf_id = cr.fetchone()[0]
63 cr.execute('select state,flow_stop from wkf_workitem w left join wkf_activity a on (a.id=w.act_id) where w.inst_id=%s', (inst_id,))63 cr.execute('select state,flow_stop from wkf_workitem w left join wkf_activity a on (a.id=w.act_id) where w.inst_id=%s', (inst_id,))
@@ -74,7 +74,7 @@
74 cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id IN (select inst_id from wkf_workitem where subflow_id=%s)", (inst_id,))74 cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id IN (select inst_id from wkf_workitem where subflow_id=%s)", (inst_id,))
75 for i in cr.fetchall():75 for i in cr.fetchall():
76 for act_name in act_names:76 for act_name in act_names:
77 validate(cr, i[0], (ident[0],i[1],i[2]), 'subflow.'+act_name[0])77 validate(cr, i[0], (ident[0],i[1],i[2]), 'subflow.'+act_name[0], context)
78 return ok78 return ok
7979
8080
8181
=== modified file 'openerp/workflow/wkf_expr.py'
--- openerp/workflow/wkf_expr.py 2011-09-14 10:25:05 +0000
+++ openerp/workflow/wkf_expr.py 2011-12-13 17:01:27 +0000
@@ -26,22 +26,23 @@
26from openerp.tools.safe_eval import safe_eval as eval26from openerp.tools.safe_eval import safe_eval as eval
2727
28class Env(dict):28class Env(dict):
29 def __init__(self, cr, uid, model, ids):29 def __init__(self, cr, uid, model, ids, context=None):
30 self.cr = cr30 self.cr = cr
31 self.uid = uid31 self.uid = uid
32 self.model = model32 self.model = model
33 self.ids = ids33 self.ids = ids
34 self.context = context
34 self.obj = pooler.get_pool(cr.dbname).get(model)35 self.obj = pooler.get_pool(cr.dbname).get(model)
35 self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()36 self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()
3637
37 def __getitem__(self, key):38 def __getitem__(self, key):
38 if (key in self.columns) or (key in dir(self.obj)):39 if (key in self.columns) or (key in dir(self.obj)):
39 res = self.obj.browse(self.cr, self.uid, self.ids[0])40 res = self.obj.browse(self.cr, self.uid, self.ids[0], self.context)
40 return res[key]41 return res[key]
41 else:42 else:
42 return super(Env, self).__getitem__(key)43 return super(Env, self).__getitem__(key)
4344
44def _eval_expr(cr, ident, workitem, action):45def _eval_expr(cr, ident, workitem, action, context=None):
45 ret=False46 ret=False
46 assert action, 'You used a NULL action in a workflow, use dummy node instead.'47 assert action, 'You used a NULL action in a workflow, use dummy node instead.'
47 for line in action.split('\n'):48 for line in action.split('\n'):
@@ -54,20 +55,23 @@
54 elif line =='False':55 elif line =='False':
55 ret=False56 ret=False
56 else:57 else:
57 env = Env(cr, uid, model, ids)58 env = Env(cr, uid, model, ids, context)
58 ret = eval(line, env, nocopy=True)59 ret = eval(line, env, nocopy=True)
59 return ret60 return ret
6061
61def execute_action(cr, ident, workitem, activity):62def execute_action(cr, ident, workitem, activity, context=None):
62 obj = pooler.get_pool(cr.dbname).get('ir.actions.server')63 obj = pooler.get_pool(cr.dbname).get('ir.actions.server')
64 ctx = {}
65 if context is not None:
66 ctx.update( context )
63 ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]}67 ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]}
64 result = obj.run(cr, ident[0], [activity['action_id']], ctx)68 result = obj.run(cr, ident[0], [activity['action_id']], ctx)
65 return result69 return result
6670
67def execute(cr, ident, workitem, activity):71def execute(cr, ident, workitem, activity, context=None):
68 return _eval_expr(cr, ident, workitem, activity['action'])72 return _eval_expr(cr, ident, workitem, activity['action'], context)
6973
70def check(cr, workitem, ident, transition, signal):74def check(cr, workitem, ident, transition, signal, context=None):
71 if transition['signal'] and signal != transition['signal']:75 if transition['signal'] and signal != transition['signal']:
72 return False76 return False
7377
@@ -78,8 +82,7 @@
78 if not transition['group_id'] in user_groups:82 if not transition['group_id'] in user_groups:
79 return False83 return False
8084
81 return _eval_expr(cr, ident, workitem, transition['condition'])85 return _eval_expr(cr, ident, workitem, transition['condition'], context)
82
8386
84# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:87# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
8588
8689
=== modified file 'openerp/workflow/wkf_service.py'
--- openerp/workflow/wkf_service.py 2011-09-24 14:52:58 +0000
+++ openerp/workflow/wkf_service.py 2011-12-13 17:01:27 +0000
@@ -44,7 +44,10 @@
44 def clear_cache(self, cr, uid):44 def clear_cache(self, cr, uid):
45 self.wkf_on_create_cache[cr.dbname]={}45 self.wkf_on_create_cache[cr.dbname]={}
4646
47 def trg_write(self, uid, res_type, res_id, cr):47 def trg_write(self, uid, res_type, res_id, cr, context=None):
48 print "WWWWWWWWWWWWW", context
49 import traceback
50 traceback.print_stack()
48 """51 """
49 Reevaluates the specified workflow instance. Thus if any condition for52 Reevaluates the specified workflow instance. Thus if any condition for
50 a transition have been changed in the backend, then running ``trg_write``53 a transition have been changed in the backend, then running ``trg_write``
@@ -57,9 +60,10 @@
57 ident = (uid,res_type,res_id)60 ident = (uid,res_type,res_id)
58 cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (res_id or None,res_type or None, 'active'))61 cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (res_id or None,res_type or None, 'active'))
59 for (id,) in cr.fetchall():62 for (id,) in cr.fetchall():
60 instance.update(cr, id, ident)63 instance.update(cr, id, ident, context)
6164
62 def trg_trigger(self, uid, res_type, res_id, cr):65 def trg_trigger(self, uid, res_type, res_id, cr, context=None):
66 print "TTTTTTTTTTTTTTT", context
63 """67 """
64 Activate a trigger.68 Activate a trigger.
6569
@@ -75,7 +79,7 @@
75 for (instance_id,) in res:79 for (instance_id,) in res:
76 cr.execute('select %s,res_type,res_id from wkf_instance where id=%s', (uid, instance_id,))80 cr.execute('select %s,res_type,res_id from wkf_instance where id=%s', (uid, instance_id,))
77 ident = cr.fetchone()81 ident = cr.fetchone()
78 instance.update(cr, instance_id, ident)82 instance.update(cr, instance_id, ident, context)
7983
80 def trg_delete(self, uid, res_type, res_id, cr):84 def trg_delete(self, uid, res_type, res_id, cr):
81 """85 """
@@ -88,7 +92,7 @@
88 ident = (uid,res_type,res_id)92 ident = (uid,res_type,res_id)
89 instance.delete(cr, ident)93 instance.delete(cr, ident)
9094
91 def trg_create(self, uid, res_type, res_id, cr):95 def trg_create(self, uid, res_type, res_id, cr, context=None):
92 """96 """
93 Create a new workflow instance97 Create a new workflow instance
9498
@@ -105,9 +109,10 @@
105 wkf_ids = cr.fetchall()109 wkf_ids = cr.fetchall()
106 self.wkf_on_create_cache[cr.dbname][res_type] = wkf_ids110 self.wkf_on_create_cache[cr.dbname][res_type] = wkf_ids
107 for (wkf_id,) in wkf_ids:111 for (wkf_id,) in wkf_ids:
108 instance.create(cr, ident, wkf_id)112 instance.create(cr, ident, wkf_id, context)
109113
110 def trg_validate(self, uid, res_type, res_id, signal, cr):114 def trg_validate(self, uid, res_type, res_id, signal, cr, context=None):
115 print "VVVVVVVVVVV", context
111 """116 """
112 Fire a signal on a given workflow instance117 Fire a signal on a given workflow instance
113118
@@ -121,7 +126,7 @@
121 # ids of all active workflow instances for a corresponding resource (id, model_nam)126 # ids of all active workflow instances for a corresponding resource (id, model_nam)
122 cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (res_id, res_type, 'active'))127 cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (res_id, res_type, 'active'))
123 for (id,) in cr.fetchall():128 for (id,) in cr.fetchall():
124 res2 = instance.validate(cr, id, ident, signal)129 res2 = instance.validate(cr, id, ident, signal, context=context)
125 result = result or res2130 result = result or res2
126 return result131 return result
127132
128133
=== modified file 'openerp/workflow/workitem.py'
--- openerp/workflow/workitem.py 2011-12-11 10:21:40 +0000
+++ openerp/workflow/workitem.py 2011-12-13 17:01:27 +0000
@@ -30,7 +30,7 @@
30import wkf_expr30import wkf_expr
31import wkf_logs31import wkf_logs
3232
33def create(cr, act_datas, inst_id, ident, stack):33def create(cr, act_datas, inst_id, ident, stack, context=None):
34 for act in act_datas:34 for act in act_datas:
35 cr.execute("select nextval('wkf_workitem_id_seq')")35 cr.execute("select nextval('wkf_workitem_id_seq')")
36 id_new = cr.fetchone()[0]36 id_new = cr.fetchone()[0]
@@ -38,9 +38,9 @@
38 cr.execute('select * from wkf_workitem where id=%s',(id_new,))38 cr.execute('select * from wkf_workitem where id=%s',(id_new,))
39 res = cr.dictfetchone()39 res = cr.dictfetchone()
40 wkf_logs.log(cr,ident,act['id'],'active')40 wkf_logs.log(cr,ident,act['id'],'active')
41 process(cr, res, ident, stack=stack)41 process(cr, res, ident, stack=stack, context=context)
4242
43def process(cr, workitem, ident, signal=None, force_running=False, stack=None):43def process(cr, workitem, ident, signal=None, force_running=False, stack=None, context=None):
44 if stack is None:44 if stack is None:
45 raise 'Error !!!'45 raise 'Error !!!'
46 result = True46 result = True
@@ -50,7 +50,7 @@
50 triggers = False50 triggers = False
51 if workitem['state']=='active':51 if workitem['state']=='active':
52 triggers = True52 triggers = True
53 result = _execute(cr, workitem, activity, ident, stack)53 result = _execute(cr, workitem, activity, ident, stack, context)
54 if not result:54 if not result:
55 return False55 return False
5656
@@ -58,7 +58,7 @@
58 pass58 pass
5959
60 if workitem['state']=='complete' or force_running:60 if workitem['state']=='complete' or force_running:
61 ok = _split_test(cr, workitem, activity['split_mode'], ident, signal, stack)61 ok = _split_test(cr, workitem, activity['split_mode'], ident, signal, stack, context)
62 triggers = triggers and not ok62 triggers = triggers and not ok
6363
64 if triggers:64 if triggers:
@@ -66,7 +66,7 @@
66 alltrans = cr.dictfetchall()66 alltrans = cr.dictfetchall()
67 for trans in alltrans:67 for trans in alltrans:
68 if trans['trigger_model']:68 if trans['trigger_model']:
69 ids = wkf_expr._eval_expr(cr,ident,workitem,trans['trigger_expr_id'])69 ids = wkf_expr._eval_expr(cr,ident,workitem,trans['trigger_expr_id'], context)
70 for res_id in ids:70 for res_id in ids:
71 cr.execute('select nextval(\'wkf_triggers_id_seq\')')71 cr.execute('select nextval(\'wkf_triggers_id_seq\')')
72 id =cr.fetchone()[0]72 id =cr.fetchone()[0]
@@ -82,7 +82,7 @@
82 workitem['state'] = state82 workitem['state'] = state
83 wkf_logs.log(cr,ident,activity['id'],state)83 wkf_logs.log(cr,ident,activity['id'],state)
8484
85def _execute(cr, workitem, activity, ident, stack):85def _execute(cr, workitem, activity, ident, stack, context=None):
86 result = True86 result = True
87 #87 #
88 # send a signal to parent workflow (signal: subflow.signal_name)88 # send a signal to parent workflow (signal: subflow.signal_name)
@@ -97,18 +97,18 @@
97 if workitem['state']=='active':97 if workitem['state']=='active':
98 _state_set(cr, workitem, activity, 'complete', ident)98 _state_set(cr, workitem, activity, 'complete', ident)
99 if activity['action_id']:99 if activity['action_id']:
100 res2 = wkf_expr.execute_action(cr, ident, workitem, activity)100 res2 = wkf_expr.execute_action(cr, ident, workitem, activity, context)
101 if res2:101 if res2:
102 stack.append(res2)102 stack.append(res2)
103 result=res2103 result=res2
104 elif activity['kind']=='function':104 elif activity['kind']=='function':
105 if workitem['state']=='active':105 if workitem['state']=='active':
106 _state_set(cr, workitem, activity, 'running', ident)106 _state_set(cr, workitem, activity, 'running', ident)
107 returned_action = wkf_expr.execute(cr, ident, workitem, activity)107 returned_action = wkf_expr.execute(cr, ident, workitem, activity, context)
108 if type(returned_action) in (dict,):108 if type(returned_action) in (dict,):
109 stack.append(returned_action)109 stack.append(returned_action)
110 if activity['action_id']:110 if activity['action_id']:
111 res2 = wkf_expr.execute_action(cr, ident, workitem, activity)111 res2 = wkf_expr.execute_action(cr, ident, workitem, activity, context)
112 # A client action has been returned112 # A client action has been returned
113 if res2:113 if res2:
114 stack.append(res2)114 stack.append(res2)
@@ -119,13 +119,13 @@
119 _state_set(cr, workitem, activity, 'running', ident)119 _state_set(cr, workitem, activity, 'running', ident)
120 cr.execute('delete from wkf_workitem where inst_id=%s and id<>%s', (workitem['inst_id'], workitem['id']))120 cr.execute('delete from wkf_workitem where inst_id=%s and id<>%s', (workitem['inst_id'], workitem['id']))
121 if activity['action']:121 if activity['action']:
122 wkf_expr.execute(cr, ident, workitem, activity)122 wkf_expr.execute(cr, ident, workitem, activity, context)
123 _state_set(cr, workitem, activity, 'complete', ident)123 _state_set(cr, workitem, activity, 'complete', ident)
124 elif activity['kind']=='subflow':124 elif activity['kind']=='subflow':
125 if workitem['state']=='active':125 if workitem['state']=='active':
126 _state_set(cr, workitem, activity, 'running', ident)126 _state_set(cr, workitem, activity, 'running', ident)
127 if activity.get('action', False):127 if activity.get('action', False):
128 id_new = wkf_expr.execute(cr, ident, workitem, activity)128 id_new = wkf_expr.execute(cr, ident, workitem, activity, context)
129 if not (id_new):129 if not (id_new):
130 cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],))130 cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],))
131 return False131 return False
@@ -133,7 +133,7 @@
133 cr.execute('select id from wkf_instance where res_id=%s and wkf_id=%s', (id_new,activity['subflow_id']))133 cr.execute('select id from wkf_instance where res_id=%s and wkf_id=%s', (id_new,activity['subflow_id']))
134 id_new = cr.fetchone()[0]134 id_new = cr.fetchone()[0]
135 else:135 else:
136 id_new = instance.create(cr, ident, activity['subflow_id'])136 id_new = instance.create(cr, ident, activity['subflow_id'], context)
137 cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (id_new, workitem['id']))137 cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (id_new, workitem['id']))
138 workitem['subflow_id'] = id_new138 workitem['subflow_id'] = id_new
139 if workitem['state']=='running':139 if workitem['state']=='running':
@@ -142,11 +142,11 @@
142 if state=='complete':142 if state=='complete':
143 _state_set(cr, workitem, activity, 'complete', ident)143 _state_set(cr, workitem, activity, 'complete', ident)
144 for t in signal_todo:144 for t in signal_todo:
145 instance.validate(cr, t[0], t[1], t[2], force_running=True)145 instance.validate(cr, t[0], t[1], t[2], force_running=True, context=context)
146146
147 return result147 return result
148148
149def _split_test(cr, workitem, split_mode, ident, signal=None, stack=None):149def _split_test(cr, workitem, split_mode, ident, signal=None, stack=None, context=None):
150 if stack is None:150 if stack is None:
151 raise 'Error !!!'151 raise 'Error !!!'
152 cr.execute('select * from wkf_transition where act_from=%s', (workitem['act_id'],))152 cr.execute('select * from wkf_transition where act_from=%s', (workitem['act_id'],))
@@ -155,7 +155,7 @@
155 alltrans = cr.dictfetchall()155 alltrans = cr.dictfetchall()
156 if split_mode=='XOR' or split_mode=='OR':156 if split_mode=='XOR' or split_mode=='OR':
157 for transition in alltrans:157 for transition in alltrans:
158 if wkf_expr.check(cr, workitem, ident, transition,signal):158 if wkf_expr.check(cr, workitem, ident, transition, signal, context):
159 test = True159 test = True
160 transitions.append((transition['id'], workitem['inst_id']))160 transitions.append((transition['id'], workitem['inst_id']))
161 if split_mode=='XOR':161 if split_mode=='XOR':
@@ -163,7 +163,7 @@
163 else:163 else:
164 test = True164 test = True
165 for transition in alltrans:165 for transition in alltrans:
166 if not wkf_expr.check(cr, workitem, ident, transition,signal):166 if not wkf_expr.check(cr, workitem, ident, transition, signal, context):
167 test = False167 test = False
168 break168 break
169 cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (transition['id'], workitem['inst_id']))169 cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (transition['id'], workitem['inst_id']))
@@ -173,15 +173,15 @@
173 cr.executemany('insert into wkf_witm_trans (trans_id,inst_id) values (%s,%s)', transitions)173 cr.executemany('insert into wkf_witm_trans (trans_id,inst_id) values (%s,%s)', transitions)
174 cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],))174 cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],))
175 for t in transitions:175 for t in transitions:
176 _join_test(cr, t[0], t[1], ident, stack)176 _join_test(cr, t[0], t[1], ident, stack, context)
177 return True177 return True
178 return False178 return False
179179
180def _join_test(cr, trans_id, inst_id, ident, stack):180def _join_test(cr, trans_id, inst_id, ident, stack, context=None):
181 cr.execute('select * from wkf_activity where id=(select act_to from wkf_transition where id=%s)', (trans_id,))181 cr.execute('select * from wkf_activity where id=(select act_to from wkf_transition where id=%s)', (trans_id,))
182 activity = cr.dictfetchone()182 activity = cr.dictfetchone()
183 if activity['join_mode']=='XOR':183 if activity['join_mode']=='XOR':
184 create(cr,[activity], inst_id, ident, stack)184 create(cr,[activity], inst_id, ident, stack, context)
185 cr.execute('delete from wkf_witm_trans where inst_id=%s and trans_id=%s', (inst_id,trans_id))185 cr.execute('delete from wkf_witm_trans where inst_id=%s and trans_id=%s', (inst_id,trans_id))
186 else:186 else:
187 cr.execute('select id from wkf_transition where act_to=%s', (activity['id'],))187 cr.execute('select id from wkf_transition where act_to=%s', (activity['id'],))
@@ -196,7 +196,7 @@
196 if ok:196 if ok:
197 for (id,) in trans_ids:197 for (id,) in trans_ids:
198 cr.execute('delete from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))198 cr.execute('delete from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
199 create(cr, [activity], inst_id, ident, stack)199 create(cr, [activity], inst_id, ident, stack, context)
200200
201# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:201# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
202202