@ -42,10 +42,14 @@ class ExceptionRule(models.Model):
)
model = fields . Selection ( selection = [ ] , string = ' Apply on ' , required = True )
type = fields . Selection (
exception_ type = fields . Selection (
selection = [ ( ' by_domain ' , ' By domain ' ) ,
( ' by_py_code ' , ' By Python Code ' ) ] ,
string = ' Exception Type ' , required = True )
( ' by_py_code ' , ' By python code ' ) ] ,
string = ' Exception Type ' , required = True , default = ' by_py_code ' ,
help = " By python code: allow to define any arbitrary check \n "
" By domain: limited to a selection by an odoo domain: \n "
" performance can be better when exceptions "
" are evaluated with several records " )
domain = fields . Char ( ' Domain ' )
active = fields . Boolean ( ' Active ' )
@ -89,11 +93,11 @@ class ExceptionRule(models.Model):
self . ensure_one ( )
return safe_eval ( self . domain )
@api.onchange ( ' type ' , )
def onchange_type ( self ) :
if self . type == ' by_domain ' :
@api.onchange ( ' exception_ type' , )
def onchange_exception_ type ( self ) :
if self . exception_ type == ' by_domain ' :
self . code = False
elif self . type == ' by_py_code ' :
elif self . exception_ type == ' by_py_code ' :
self . domain = False
@ -162,30 +166,75 @@ class BaseException(models.AbstractModel):
return False
return True
@api.multi
def _reverse_field ( self ) :
""" Name of the many2many field from exception rule to self.
In order to take advantage of domain optimisation , exception rule
model should have a many2many field to inherited object .
The opposit relation already exists in the name of exception_ids
Example :
class ExceptionRule ( models . Model ) :
_inherit = ' exception.rule '
model = fields . Selection (
selection_add = [
( ' sale.order ' , ' Sale order ' ) ,
[ . . . ]
] )
sale_ids = fields . Many2many (
' sale.order ' ,
string = ' Sales ' )
[ . . . ]
"""
exception_obj = self . env [ ' exception.rule ' ]
reverse_fields = self . env [ ' ir.model.fields ' ] . search ( [
[ ' model ' , ' = ' , ' exception.rule ' ] ,
[ ' ttype ' , ' = ' , ' many2many ' ] ,
[ ' relation ' , ' = ' , self [ 0 ] . _name ] ,
] )
# ir.model.fields may contain old variable name
# so we check if the field exists on exception rule
return ( [
field . name for field in reverse_fields
if hasattr ( exception_obj , field . name )
] or [ None ] ) [ 0 ]
@api.multi
def detect_exceptions ( self ) :
""" returns the list of exception_ids for all the considered base.exceptions
""" List all exception_ids applied on self
Exception ids are also written on records
"""
import pdb ; pdb . set_trace ( )
if not self :
return [ ]
exception_obj = self . env [ ' exception.rule ' ]
all_exceptions = exception_obj . sudo ( ) . search (
[ ( ' rule_group ' , ' = ' , self [ 0 ] . rule_group ) ] )
# TODO fix self[0] : it may not be the same on all ids in self
model_exceptions = all_exceptions . filtered (
lambda ex : ex . model == self . _name )
sub_exceptions = all_exceptions . filtered (
lambda ex : ex . model != self . _name )
reverse_field = self . _reverse_field ( )
if reverse_field :
optimize = True
else :
optimize = False
exception_by_rec , exception_by_rule = self . _detect_exceptions (
model_exceptions , sub_exceptions , optimize )
all_exception_ids = [ ]
for obj in self :
if obj . ignore_exception :
continue
exception_ids = obj . _detect_exceptions (
model_exceptions , sub_exceptions )
for obj , exception_ids in exception_by_rec . iteritems ( ) :
obj . exception_ids = [ ( 6 , 0 , exception_ids ) ]
all_exception_ids + = exception_ids
return all_exception_ids
for rule , exception_ids in exception_by_rule . iteritems ( ) :
rule [ reverse_field ] = [ ( 6 , 0 , exception_ids . ids ) ]
if exception_ids :
all_exception_ids + = [ rule . id ]
return list ( set ( all_exception_ids ) )
@api.model
def _exception_rule_eval_context ( self , obj_name , rec ) :
@ -214,49 +263,96 @@ class BaseException(models.AbstractModel):
return eval_ctx . get ( ' failed ' , False )
@api.multi
def _detect_exceptions ( self , model_exceptions , sub_exceptions ) :
self . ensure_one ( )
import pdb ; pdb . set_trace ( )
exception_ids = [ ]
next_state_rule = False
def _detect_exceptions (
self , model_exceptions , sub_exceptions ,
optimize = False ,
) :
""" Find exceptions found on self.
@returns
exception_by_rec : { record_id : exception_ids }
exception_by_rule : { rule_id : record_ids }
"""
exception_by_rec = { }
exception_by_rule = { }
exception_set = set ( )
python_rules = [ ]
dom_rules = [ ]
optim_rules = [ ]
for rule in model_exceptions :
if rule . type == ' by_py_code ' and self . _rule_eval (
rule , self . rule_group , self ) :
exception_ids . append ( rule . id )
if rule . exception_type == ' by_py_code ' :
python_rules . append ( rule )
elif rule . exception_type == ' by_domain ' and rule . domain :
if optimize :
optim_rules . append ( rule )
else :
dom_rules . append ( rule )
elif rule . type == ' by_domain ' and rule . domain :
for rule in optim_rules :
domain = rule . _get_domain ( )
domain . append ( ( ' id ' , ' = ' , self . id ) )
if self . search ( domain ) :
exception_ids . append ( rule . id )
if rule . next_state :
if not next_state_rule or \
rule . sequence < next_state_rule . sequence :
next_state_rule = rule
domain . append ( [ ' ignore_exception ' , ' = ' , False ] )
domain . append ( [ ' id ' , ' in ' , self . ids ] )
records_with_exception = self . search ( domain )
exception_by_rule [ rule ] = records_with_exception
if records_with_exception :
exception_set . add ( rule . id )
if len ( python_rules ) or len ( dom_rules ) or sub_exceptions :
for rec in self :
for rule in python_rules :
if (
not rec . ignore_exception and
self . _rule_eval ( rule , rec . rule_group , rec )
) :
exception_by_rec . setdefault ( rec , [ ] ) . append ( rule . id )
exception_set . add ( rule . id )
for rule in dom_rules :
# there is no reverse many2many, so this rule
# can't be optimized, see _reverse_field
domain = rule . _get_domain ( )
domain . append ( [ ' ignore_exception ' , ' = ' , False ] )
domain . append ( [ ' id ' , ' = ' , rec . id ] )
if self . search_count ( domain ) :
exception_by_rec . setdefault (
rec , [ ] ) . append ( rule . id )
exception_set . add ( rule . id )
if sub_exceptions :
for obj_line in self . _get_lines ( ) :
group_line = rec . rule_group + ' _line '
for obj_line in rec . _get_lines ( ) :
for rule in sub_exceptions :
if rule . id in exception_ids :
# we do not matter if the exception as already been
if rule . id in exception_set :
# we do not matter if the exception as
# already been
# found for an line of this object
# (ex sale order line if obj is sale order)
continue
group_line = self . rule_group + ' _line '
if rule . type == ' by_py_code ' and self . _rule_eval (
rule , group_line , obj_line ) :
exception_ids . append ( rule . id )
elif rule . type == ' by_domain ' and rule . domain :
if rule . exception_type == ' by_py_code ' :
if self . _rule_eval (
rule , group_line , obj_line
) :
exception_by_rec . setdefault (
rec , [ ] ) . append ( rule . id )
elif (
rule . exception_type == ' by_domain ' and
rule . domain
) :
# sub_exception are currently not optimizable
domain = rule . _get_domain ( )
domain . append ( ( ' id ' , ' = ' , obj_line . id ) )
if obj_line . search ( domain ) :
exception_ids . append ( rule . id )
if obj_line . search_count ( domain ) :
exception_by_rec . setdefault (
rec , [ ] ) . append ( rule . id )
# set object to next state
if next_state_rule :
self . state = next_state_rule . next_state
return exception_ids
# find exception that raised error and has next_state
next_state_exception_ids = model_exceptions . filtered (
lambda r : r . id in exception_set and r . next_state )
if next_state_exception_ids :
self . state = next_state_exception_ids [ 0 ] . next_state
return exception_by_rec , exception_by_rule
@implemented_by_base_exception
def _get_lines ( self ) :