|
@ -12,48 +12,56 @@ from odoo import osv |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExceptionRule(models.Model): |
|
|
class ExceptionRule(models.Model): |
|
|
_name = 'exception.rule' |
|
|
|
|
|
_description = 'Exception Rule' |
|
|
|
|
|
_order = 'active desc, sequence asc' |
|
|
|
|
|
|
|
|
_name = "exception.rule" |
|
|
|
|
|
_description = "Exception Rule" |
|
|
|
|
|
_order = "active desc, sequence asc" |
|
|
|
|
|
|
|
|
name = fields.Char('Exception Name', required=True, translate=True) |
|
|
|
|
|
description = fields.Text('Description', translate=True) |
|
|
|
|
|
|
|
|
name = fields.Char("Exception Name", required=True, translate=True) |
|
|
|
|
|
description = fields.Text("Description", translate=True) |
|
|
sequence = fields.Integer( |
|
|
sequence = fields.Integer( |
|
|
string='Sequence', |
|
|
|
|
|
|
|
|
string="Sequence", |
|
|
help="Gives the sequence order when applying the test", |
|
|
help="Gives the sequence order when applying the test", |
|
|
) |
|
|
) |
|
|
model = fields.Selection(selection=[], string='Apply on', required=True) |
|
|
|
|
|
|
|
|
model = fields.Selection(selection=[], string="Apply on", required=True) |
|
|
|
|
|
|
|
|
exception_type = fields.Selection( |
|
|
exception_type = fields.Selection( |
|
|
selection=[('by_domain', 'By domain'), |
|
|
|
|
|
('by_py_code', 'By python code'), |
|
|
|
|
|
('by_method', 'By method'), |
|
|
|
|
|
], |
|
|
|
|
|
string='Exception Type', required=True, default='by_py_code', |
|
|
|
|
|
|
|
|
selection=[ |
|
|
|
|
|
("by_domain", "By domain"), |
|
|
|
|
|
("by_py_code", "By python code"), |
|
|
|
|
|
("by_method", "By method"), |
|
|
|
|
|
], |
|
|
|
|
|
string="Exception Type", |
|
|
|
|
|
required=True, |
|
|
|
|
|
default="by_py_code", |
|
|
help="By python code: allow to define any arbitrary check\n" |
|
|
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') |
|
|
|
|
|
method = fields.Char('Method', readonly=True) |
|
|
|
|
|
active = fields.Boolean('Active', default=True) |
|
|
|
|
|
|
|
|
"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") |
|
|
|
|
|
method = fields.Char("Method", readonly=True) |
|
|
|
|
|
active = fields.Boolean("Active", default=True) |
|
|
code = fields.Text( |
|
|
code = fields.Text( |
|
|
'Python Code', |
|
|
|
|
|
|
|
|
"Python Code", |
|
|
help="Python code executed to check if the exception apply or " |
|
|
help="Python code executed to check if the exception apply or " |
|
|
"not. Use failed = True to block the exception", |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
"not. Use failed = True to block the exception", |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
@api.constrains('exception_type', 'domain', 'code') |
|
|
|
|
|
|
|
|
@api.constrains("exception_type", "domain", "code") |
|
|
def check_exception_type_consistency(self): |
|
|
def check_exception_type_consistency(self): |
|
|
for rule in self: |
|
|
for rule in self: |
|
|
if ((rule.exception_type == 'by_py_code' and not rule.code) or |
|
|
|
|
|
(rule.exception_type == 'by_domain' and not rule.domain) or |
|
|
|
|
|
(rule.exception_type == 'by_method' and not rule.method)): |
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
(rule.exception_type == "by_py_code" and not rule.code) |
|
|
|
|
|
or (rule.exception_type == "by_domain" and not rule.domain) |
|
|
|
|
|
or (rule.exception_type == "by_method" and not rule.method) |
|
|
|
|
|
): |
|
|
raise ValidationError( |
|
|
raise ValidationError( |
|
|
_("There is a problem of configuration, python code, " |
|
|
|
|
|
"domain or method is missing to match the exception type.") |
|
|
|
|
|
|
|
|
_( |
|
|
|
|
|
"There is a problem of configuration, python code, " |
|
|
|
|
|
"domain or method is missing to match the exception " |
|
|
|
|
|
"type." |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
# TODO in case of by_method exception test that the method exist with hasattr |
|
|
|
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _get_domain(self): |
|
|
def _get_domain(self): |
|
@ -63,8 +71,8 @@ class ExceptionRule(models.Model): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseExceptionMethod(models.AbstractModel): |
|
|
class BaseExceptionMethod(models.AbstractModel): |
|
|
_name = 'base.exception.method' |
|
|
|
|
|
_description = 'Exception Rule Methods' |
|
|
|
|
|
|
|
|
_name = "base.exception.method" |
|
|
|
|
|
_description = "Exception Rule Methods" |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _get_main_records(self): |
|
|
def _get_main_records(self): |
|
@ -85,15 +93,14 @@ class BaseExceptionMethod(models.AbstractModel): |
|
|
By default, only the rules with the correct model |
|
|
By default, only the rules with the correct model |
|
|
will be used. |
|
|
will be used. |
|
|
""" |
|
|
""" |
|
|
return [('model', '=', self._name)] |
|
|
|
|
|
|
|
|
return [("model", "=", self._name)] |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def detect_exceptions(self): |
|
|
def detect_exceptions(self): |
|
|
"""List all exception_ids applied on self |
|
|
"""List all exception_ids applied on self |
|
|
Exception ids are also written on records |
|
|
Exception ids are also written on records |
|
|
""" |
|
|
""" |
|
|
rules = self.env['exception.rule'].sudo().search( |
|
|
|
|
|
self._rule_domain()) |
|
|
|
|
|
|
|
|
rules = self.env["exception.rule"].sudo().search(self._rule_domain()) |
|
|
all_exception_ids = [] |
|
|
all_exception_ids = [] |
|
|
rules_to_remove = {} |
|
|
rules_to_remove = {} |
|
|
rules_to_add = {} |
|
|
rules_to_add = {} |
|
@ -127,23 +134,23 @@ class BaseExceptionMethod(models.AbstractModel): |
|
|
# table |
|
|
# table |
|
|
# and the "to add" part generates one INSERT (with unnest) per rule. |
|
|
# and the "to add" part generates one INSERT (with unnest) per rule. |
|
|
for rule_id, records in rules_to_remove.items(): |
|
|
for rule_id, records in rules_to_remove.items(): |
|
|
records.write({'exception_ids': [(3, rule_id,)]}) |
|
|
|
|
|
|
|
|
records.write({"exception_ids": [(3, rule_id)]}) |
|
|
for rule_id, records in rules_to_add.items(): |
|
|
for rule_id, records in rules_to_add.items(): |
|
|
records.write(({'exception_ids': [(4, rule_id,)]})) |
|
|
|
|
|
|
|
|
records.write(({"exception_ids": [(4, rule_id)]})) |
|
|
return all_exception_ids |
|
|
return all_exception_ids |
|
|
|
|
|
|
|
|
@api.model |
|
|
@api.model |
|
|
def _exception_rule_eval_context(self, rec): |
|
|
def _exception_rule_eval_context(self, rec): |
|
|
return { |
|
|
return { |
|
|
'time': time, |
|
|
|
|
|
'self': rec, |
|
|
|
|
|
|
|
|
"time": time, |
|
|
|
|
|
"self": rec, |
|
|
# object, obj: deprecated. |
|
|
# object, obj: deprecated. |
|
|
# should be removed in future migrations |
|
|
# should be removed in future migrations |
|
|
'object': rec, |
|
|
|
|
|
'obj': rec, |
|
|
|
|
|
|
|
|
"object": rec, |
|
|
|
|
|
"obj": rec, |
|
|
# copy context to prevent side-effects of eval |
|
|
# copy context to prevent side-effects of eval |
|
|
# should be deprecated too, accesible through self. |
|
|
# should be deprecated too, accesible through self. |
|
|
'context': self.env.context.copy() |
|
|
|
|
|
|
|
|
"context": self.env.context.copy(), |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@api.model |
|
|
@api.model |
|
@ -151,28 +158,31 @@ class BaseExceptionMethod(models.AbstractModel): |
|
|
expr = rule.code |
|
|
expr = rule.code |
|
|
space = self._exception_rule_eval_context(rec) |
|
|
space = self._exception_rule_eval_context(rec) |
|
|
try: |
|
|
try: |
|
|
safe_eval(expr, |
|
|
|
|
|
space, |
|
|
|
|
|
mode='exec', |
|
|
|
|
|
nocopy=True) # nocopy allows to return 'result' |
|
|
|
|
|
|
|
|
safe_eval( |
|
|
|
|
|
expr, space, mode="exec", nocopy=True |
|
|
|
|
|
) # nocopy allows to return 'result' |
|
|
except Exception as e: |
|
|
except Exception as e: |
|
|
raise UserError( |
|
|
raise UserError( |
|
|
_('Error when evaluating the exception.rule ' |
|
|
|
|
|
'rule:\n %s \n(%s)') % (rule.name, e)) |
|
|
|
|
|
return space.get('failed', False) |
|
|
|
|
|
|
|
|
_( |
|
|
|
|
|
"Error when evaluating the exception.rule " |
|
|
|
|
|
"rule:\n %s \n(%s)" |
|
|
|
|
|
) |
|
|
|
|
|
% (rule.name, e) |
|
|
|
|
|
) |
|
|
|
|
|
return space.get("failed", False) |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _detect_exceptions(self, rule): |
|
|
def _detect_exceptions(self, rule): |
|
|
if rule.exception_type == 'by_py_code': |
|
|
|
|
|
|
|
|
if rule.exception_type == "by_py_code": |
|
|
return self._detect_exceptions_by_py_code(rule) |
|
|
return self._detect_exceptions_by_py_code(rule) |
|
|
elif rule.exception_type == 'by_domain': |
|
|
|
|
|
|
|
|
elif rule.exception_type == "by_domain": |
|
|
return self._detect_exceptions_by_domain(rule) |
|
|
return self._detect_exceptions_by_domain(rule) |
|
|
elif rule.exception_type == 'by_method': |
|
|
|
|
|
|
|
|
elif rule.exception_type == "by_method": |
|
|
return self._detect_exceptions_by_method(rule) |
|
|
return self._detect_exceptions_by_method(rule) |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _get_base_domain(self): |
|
|
def _get_base_domain(self): |
|
|
return [('ignore_exception', '=', False), ('id', 'in', self.ids)] |
|
|
|
|
|
|
|
|
return [("ignore_exception", "=", False), ("id", "in", self.ids)] |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _detect_exceptions_by_py_code(self, rule): |
|
|
def _detect_exceptions_by_py_code(self, rule): |
|
@ -208,34 +218,31 @@ class BaseExceptionMethod(models.AbstractModel): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseException(models.AbstractModel): |
|
|
class BaseException(models.AbstractModel): |
|
|
_inherit = 'base.exception.method' |
|
|
|
|
|
_name = 'base.exception' |
|
|
|
|
|
_order = 'main_exception_id asc' |
|
|
|
|
|
_description = 'Exception' |
|
|
|
|
|
|
|
|
_inherit = "base.exception.method" |
|
|
|
|
|
_name = "base.exception" |
|
|
|
|
|
_order = "main_exception_id asc" |
|
|
|
|
|
_description = "Exception" |
|
|
|
|
|
|
|
|
main_exception_id = fields.Many2one( |
|
|
main_exception_id = fields.Many2one( |
|
|
'exception.rule', |
|
|
|
|
|
compute='_compute_main_error', |
|
|
|
|
|
string='Main Exception', |
|
|
|
|
|
|
|
|
"exception.rule", |
|
|
|
|
|
compute="_compute_main_error", |
|
|
|
|
|
string="Main Exception", |
|
|
store=True, |
|
|
store=True, |
|
|
) |
|
|
) |
|
|
exceptions_summary = fields.Html( |
|
|
exceptions_summary = fields.Html( |
|
|
'Exceptions Summary', |
|
|
|
|
|
compute='_compute_exceptions_summary', |
|
|
|
|
|
|
|
|
"Exceptions Summary", compute="_compute_exceptions_summary" |
|
|
) |
|
|
) |
|
|
exception_ids = fields.Many2many( |
|
|
exception_ids = fields.Many2many( |
|
|
'exception.rule', |
|
|
|
|
|
string='Exceptions', |
|
|
|
|
|
copy=False, |
|
|
|
|
|
|
|
|
"exception.rule", string="Exceptions", copy=False |
|
|
) |
|
|
) |
|
|
ignore_exception = fields.Boolean('Ignore Exceptions', copy=False) |
|
|
|
|
|
|
|
|
ignore_exception = fields.Boolean("Ignore Exceptions", copy=False) |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def action_ignore_exceptions(self): |
|
|
def action_ignore_exceptions(self): |
|
|
self.write({'ignore_exception': True}) |
|
|
|
|
|
|
|
|
self.write({"ignore_exception": True}) |
|
|
return True |
|
|
return True |
|
|
|
|
|
|
|
|
@api.depends('exception_ids', 'ignore_exception') |
|
|
|
|
|
|
|
|
@api.depends("exception_ids", "ignore_exception") |
|
|
def _compute_main_error(self): |
|
|
def _compute_main_error(self): |
|
|
for rec in self: |
|
|
for rec in self: |
|
|
if not rec.ignore_exception and rec.exception_ids: |
|
|
if not rec.ignore_exception and rec.exception_ids: |
|
@ -243,29 +250,35 @@ class BaseException(models.AbstractModel): |
|
|
else: |
|
|
else: |
|
|
rec.main_exception_id = False |
|
|
rec.main_exception_id = False |
|
|
|
|
|
|
|
|
@api.depends('exception_ids', 'ignore_exception') |
|
|
|
|
|
|
|
|
@api.depends("exception_ids", "ignore_exception") |
|
|
def _compute_exceptions_summary(self): |
|
|
def _compute_exceptions_summary(self): |
|
|
for rec in self: |
|
|
for rec in self: |
|
|
if rec.exception_ids and not rec.ignore_exception: |
|
|
if rec.exception_ids and not rec.ignore_exception: |
|
|
rec.exceptions_summary = '<ul>%s</ul>' % ''.join([ |
|
|
|
|
|
'<li>%s: <i>%s</i></li>' % tuple(map(html.escape, ( |
|
|
|
|
|
e.name, e.description))) for e in rec.exception_ids]) |
|
|
|
|
|
|
|
|
rec.exceptions_summary = "<ul>%s</ul>" % "".join( |
|
|
|
|
|
[ |
|
|
|
|
|
"<li>%s: <i>%s</i></li>" |
|
|
|
|
|
% tuple(map(html.escape, (e.name, e.description))) |
|
|
|
|
|
for e in rec.exception_ids |
|
|
|
|
|
] |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _popup_exceptions(self): |
|
|
def _popup_exceptions(self): |
|
|
action = self._get_popup_action().read()[0] |
|
|
action = self._get_popup_action().read()[0] |
|
|
action.update({ |
|
|
|
|
|
'context': { |
|
|
|
|
|
'active_id': self.ids[0], |
|
|
|
|
|
'active_ids': self.ids, |
|
|
|
|
|
'active_model': self._name, |
|
|
|
|
|
|
|
|
action.update( |
|
|
|
|
|
{ |
|
|
|
|
|
"context": { |
|
|
|
|
|
"active_id": self.ids[0], |
|
|
|
|
|
"active_ids": self.ids, |
|
|
|
|
|
"active_model": self._name, |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
) |
|
|
return action |
|
|
return action |
|
|
|
|
|
|
|
|
@api.model |
|
|
@api.model |
|
|
def _get_popup_action(self): |
|
|
def _get_popup_action(self): |
|
|
return self.env.ref('base_exception.action_exception_rule_confirm') |
|
|
|
|
|
|
|
|
return self.env.ref("base_exception.action_exception_rule_confirm") |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def _check_exception(self): |
|
|
def _check_exception(self): |
|
@ -281,5 +294,5 @@ class BaseException(models.AbstractModel): |
|
|
""" |
|
|
""" |
|
|
exception_ids = self.detect_exceptions() |
|
|
exception_ids = self.detect_exceptions() |
|
|
if exception_ids: |
|
|
if exception_ids: |
|
|
exceptions = self.env['exception.rule'].browse(exception_ids) |
|
|
|
|
|
raise ValidationError('\n'.join(exceptions.mapped('name'))) |
|
|
|
|
|
|
|
|
exceptions = self.env["exception.rule"].browse(exception_ids) |
|
|
|
|
|
raise ValidationError("\n".join(exceptions.mapped("name"))) |