Browse Source

add execution rule based instead of record based

improve the perfs dramastically when there is a lot of records
pull/1557/head
hparfr 6 years ago
committed by Iván Todorovich
parent
commit
00569defba
  1. 206
      base_exception/models/base_exception.py
  2. 1
      base_exception/readme/CONTRIBUTORS.rst
  3. 9
      base_exception/views/base_exception_view.xml

206
base_exception/models/base_exception.py

@ -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)
elif rule.type == 'by_domain' and rule.domain:
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
if sub_exceptions:
for obj_line in self._get_lines():
for rule in sub_exceptions:
if rule.id in exception_ids:
# 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:
domain = rule._get_domain()
domain.append(('id', '=', obj_line.id))
if obj_line.search(domain):
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)
for rule in optim_rules:
domain = rule._get_domain()
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:
group_line = rec.rule_group + '_line'
for obj_line in rec._get_lines():
for rule in sub_exceptions:
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
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_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):

1
base_exception/readme/CONTRIBUTORS.rst

@ -5,4 +5,5 @@
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
* SodexisTeam <dev@sodexis.com>
* Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
* Raphaël Reverdy <raphael.reverdy@akretion.com>
* Iván Todorovich <ivan.todorovich@gmail.com>

9
base_exception/views/base_exception_view.xml

@ -40,14 +40,15 @@
<field name="rule_group"/>
<field name="model"/>
<field name="next_state"/>
<field name="domain" attrs="{'invisible': [('type','!=','by_domain')]}"/>
<field name="exception_type" widget="radio"/>
<field name="domain" attrs="{'invisible': [('exception_type','!=','by_domain')]}"/>
</group>
</group>
<notebook attrs="{'invisible': [('type','!=','by_py_code')]}">
<page name="code" string="Python Code">
<notebook>
<page name="code" string="Python Code" attrs="{'invisible': [('exception_type','!=','by_py_code')]}">
<field name="code" widget="ace" options="{'mode': 'python'}" placeholder="Enter Python code here. Help about Python expression is available in the help tab of this document."/>
</page>
<page name="help" string="Help">
<page name="help" string="Help" attrs="{'invisible': [('exception_type','!=','by_py_code')]}">
<group>
<div style="margin-top: 4px;">
<h3>Help with Python expressions</h3>

Loading…
Cancel
Save