Browse Source

[IMP] black, flake8 nitpick

12.0-mig-module_prototyper_last
KevinKhao 5 years ago
parent
commit
6498d43a33
  1. 32
      base_exception/__manifest__.py
  2. 1
      base_exception/models/__init__.py
  3. 171
      base_exception/models/base_exception.py
  4. 1
      base_exception/tests/__init__.py
  5. 5
      base_exception/tests/common.py
  6. 48
      base_exception/tests/purchase_test.py
  7. 87
      base_exception/tests/test_base_exception.py
  8. 1
      base_exception/wizard/__init__.py
  9. 25
      base_exception/wizard/base_exception_confirm.py

32
base_exception/__manifest__.py

@ -3,24 +3,22 @@
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> # Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Exception Rule',
'version': '12.0.3.0.1',
'category': 'Generic Modules',
'summary': """
"name": "Exception Rule",
"version": "12.0.3.0.1",
"category": "Generic Modules",
"summary": """
This module provide an abstract model to manage customizable This module provide an abstract model to manage customizable
exceptions to be applied on different models (sale order, invoice, ...)""", exceptions to be applied on different models (sale order, invoice, ...)""",
'author':
"Akretion, Sodexis, Camptocamp, Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'depends': [
'base_setup',
"author": "Akretion, Sodexis, Camptocamp, "
"Odoo Community Association (OCA)",
"website": "https://github.com/OCA/server-tools",
"depends": ["base_setup"],
"license": "AGPL-3",
"data": [
"security/base_exception_security.xml",
"security/ir.model.access.csv",
"wizard/base_exception_confirm_view.xml",
"views/base_exception_view.xml",
], ],
'license': 'AGPL-3',
'data': [
'security/base_exception_security.xml',
'security/ir.model.access.csv',
'wizard/base_exception_confirm_view.xml',
'views/base_exception_view.xml',
],
'installable': True,
"installable": True,
} }

1
base_exception/models/__init__.py

@ -1,2 +1 @@
from . import base_exception from . import base_exception

171
base_exception/models/base_exception.py

@ -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")))

1
base_exception/tests/__init__.py

@ -1,4 +1,3 @@
from . import common from . import common
from . import purchase_test from . import purchase_test
from . import test_base_exception from . import test_base_exception

5
base_exception/tests/common.py

@ -8,6 +8,7 @@ def setup_test_model(env, model_clses):
env.registry.setup_models(env.cr) env.registry.setup_models(env.cr)
env.registry.init_models( env.registry.init_models(
env.cr, [model_cls._name for model_cls in model_clses],
dict(env.context, update_custom_fields=True)
env.cr,
[model_cls._name for model_cls in model_clses],
dict(env.context, update_custom_fields=True),
) )

48
base_exception/tests/purchase_test.py

@ -4,58 +4,63 @@ from odoo import api, fields, models
class PurchaseTest(models.Model): class PurchaseTest(models.Model):
_inherit = 'base.exception'
_inherit = "base.exception"
_name = "base.exception.test.purchase" _name = "base.exception.test.purchase"
_description = "Base Ecxeption Test Model" _description = "Base Ecxeption Test Model"
name = fields.Char(required=True) name = fields.Char(required=True)
user_id = fields.Many2one('res.users', string='Responsible')
user_id = fields.Many2one("res.users", string="Responsible")
state = fields.Selection( state = fields.Selection(
[('draft', 'New'), ('cancel', 'Cancelled'),
('purchase', 'Purchase'),
('to approve', 'To approve'), ('done', 'Done')],
string="Status", readonly=True, default='draft')
[
("draft", "New"),
("cancel", "Cancelled"),
("purchase", "Purchase"),
("to approve", "To approve"),
("done", "Done"),
],
string="Status",
readonly=True,
default="draft",
)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
partner_id = fields.Many2one('res.partner', string='Partner')
line_ids = fields.One2many(
'base.exception.test.purchase.line', 'lead_id')
amount_total = fields.Float(
compute='_compute_amount_total', store=True)
partner_id = fields.Many2one("res.partner", string="Partner")
line_ids = fields.One2many("base.exception.test.purchase.line", "lead_id")
amount_total = fields.Float(compute="_compute_amount_total", store=True)
@api.depends('line_ids')
@api.depends("line_ids")
def _compute_amount_total(cls): def _compute_amount_total(cls):
for record in cls: for record in cls:
for line in record.line_ids: for line in record.line_ids:
record.amount_total += line.amount * line.qty record.amount_total += line.amount * line.qty
@api.constrains('ignore_exception', 'line_ids', 'state')
@api.constrains("ignore_exception", "line_ids", "state")
def test_purchase_check_exception(cls): def test_purchase_check_exception(cls):
orders = cls.filtered(lambda s: s.state == 'purchase')
orders = cls.filtered(lambda s: s.state == "purchase")
if orders: if orders:
orders._check_exception() orders._check_exception()
@api.multi @api.multi
def button_approve(cls, force=False): def button_approve(cls, force=False):
cls.write({'state': 'to approve'})
cls.write({"state": "to approve"})
return {} return {}
@api.multi @api.multi
def button_draft(cls): def button_draft(cls):
cls.write({'state': 'draft'})
cls.write({"state": "draft"})
return {} return {}
@api.multi @api.multi
def button_confirm(cls): def button_confirm(cls):
cls.write({'state': 'purchase'})
cls.write({"state": "purchase"})
return True return True
@api.multi @api.multi
def button_cancel(cls): def button_cancel(cls):
cls.write({'state': 'cancel'})
cls.write({"state": "cancel"})
@api.multi @api.multi
def _reverse_field(self): def _reverse_field(self):
return 'test_purchase_ids'
return "test_purchase_ids"
class LineTest(models.Model): class LineTest(models.Model):
@ -63,7 +68,8 @@ class LineTest(models.Model):
_description = "Base Exception Test Model Line" _description = "Base Exception Test Model Line"
name = fields.Char() name = fields.Char()
lead_id = fields.Many2one('base.exception.test.purchase',
ondelete='cascade')
lead_id = fields.Many2one(
"base.exception.test.purchase", ondelete="cascade"
)
qty = fields.Float() qty = fields.Float()
amount = fields.Float() amount = fields.Float()

87
base_exception/tests/test_base_exception.py

@ -14,57 +14,64 @@ _logger = logging.getLogger(__name__)
@common.at_install(False) @common.at_install(False)
@common.post_install(True) @common.post_install(True)
class TestBaseException(common.SavepointCase): class TestBaseException(common.SavepointCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TestBaseException, cls).setUpClass() super(TestBaseException, cls).setUpClass()
setup_test_model(cls.env, [PurchaseTest, LineTest]) setup_test_model(cls.env, [PurchaseTest, LineTest])
cls.base_exception = cls.env['base.exception']
cls.exception_rule = cls.env['exception.rule']
if 'test_purchase_ids' not in cls.exception_rule._fields:
field = fields.Many2many('base.exception.test.purchase')
cls.exception_rule._add_field('test_purchase_ids', field)
cls.exception_confirm = cls.env['exception.rule.confirm']
cls.exception_rule._fields['model'].selection.append(
('base.exception.test.purchase', 'Purchase Order'))
cls.base_exception = cls.env["base.exception"]
cls.exception_rule = cls.env["exception.rule"]
if "test_purchase_ids" not in cls.exception_rule._fields:
field = fields.Many2many("base.exception.test.purchase")
cls.exception_rule._add_field("test_purchase_ids", field)
cls.exception_confirm = cls.env["exception.rule.confirm"]
cls.exception_rule._fields["model"].selection.append(
("base.exception.test.purchase", "Purchase Order")
)
cls.exception_rule._fields['model'].selection.append(
('base.exception.test.purchase.line', 'Purchase Order Line'))
cls.exception_rule._fields["model"].selection.append(
("base.exception.test.purchase.line", "Purchase Order Line")
)
cls.exceptionnozip = cls.env['exception.rule'].create({
'name': "No ZIP code on destination",
'sequence': 10,
'model': "base.exception.test.purchase",
'code': "if not obj.partner_id.zip: failed=True",
})
cls.exceptionnozip = cls.env["exception.rule"].create(
{
"name": "No ZIP code on destination",
"sequence": 10,
"model": "base.exception.test.purchase",
"code": "if not obj.partner_id.zip: failed=True",
}
)
cls.exceptionno_minorder = cls.env['exception.rule'].create({
'name': "Min order except",
'sequence': 10,
'model': "base.exception.test.purchase",
'code': "if obj.amount_total <= 200.0: failed=True",
})
cls.exceptionno_minorder = cls.env["exception.rule"].create(
{
"name": "Min order except",
"sequence": 10,
"model": "base.exception.test.purchase",
"code": "if obj.amount_total <= 200.0: failed=True",
}
)
cls.exceptionno_lineqty = cls.env['exception.rule'].create({
'name': "Qty > 0",
'sequence': 10,
'model': "base.exception.test.purchase.line",
'code': "if obj.qty <= 0: failed=True"
})
cls.exceptionno_lineqty = cls.env["exception.rule"].create(
{
"name": "Qty > 0",
"sequence": 10,
"model": "base.exception.test.purchase.line",
"code": "if obj.qty <= 0: failed=True",
}
)
def test_purchase_order_exception(self): def test_purchase_order_exception(self):
partner = self.env.ref('base.res_partner_1')
partner = self.env.ref("base.res_partner_1")
partner.zip = False partner.zip = False
potest1 = self.env['base.exception.test.purchase'].create({
'name': 'Test base exception to basic purchase',
'partner_id': partner.id,
'line_ids': [(0, 0, {
'name': "line test",
'amount': 120.0,
'qty': 1.5,
})],
})
potest1 = self.env["base.exception.test.purchase"].create(
{
"name": "Test base exception to basic purchase",
"partner_id": partner.id,
"line_ids": [
(0, 0, {"name": "line test", "amount": 120.0, "qty": 1.5})
],
}
)
# Block because of exception during validation # Block because of exception during validation
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
potest1.button_confirm() potest1.button_confirm()
@ -73,4 +80,4 @@ class TestBaseException(common.SavepointCase):
# Test ignore exeception make possible for the po to validate # Test ignore exeception make possible for the po to validate
potest1.ignore_exception = True potest1.ignore_exception = True
potest1.button_confirm() potest1.button_confirm()
self.assertTrue(potest1.state == 'purchase')
self.assertTrue(potest1.state == "purchase")

1
base_exception/wizard/__init__.py

@ -1,2 +1 @@
from . import base_exception_confirm from . import base_exception_confirm

25
base_exception/wizard/base_exception_confirm.py

@ -7,34 +7,33 @@ from odoo.exceptions import ValidationError
class ExceptionRuleConfirm(models.AbstractModel): class ExceptionRuleConfirm(models.AbstractModel):
_name = 'exception.rule.confirm'
_description = 'Exception Rule Confirm Wizard'
_name = "exception.rule.confirm"
_description = "Exception Rule Confirm Wizard"
related_model_id = fields.Many2one('base.exception',)
related_model_id = fields.Many2one("base.exception")
exception_ids = fields.Many2many( exception_ids = fields.Many2many(
'exception.rule',
string='Exceptions to resolve',
readonly=True,
"exception.rule", string="Exceptions to resolve", readonly=True
) )
ignore = fields.Boolean('Ignore Exceptions')
ignore = fields.Boolean("Ignore Exceptions")
@api.model @api.model
def default_get(self, field_list): def default_get(self, field_list):
res = super(ExceptionRuleConfirm, self).default_get(field_list) res = super(ExceptionRuleConfirm, self).default_get(field_list)
current_model = self.env.context.get('active_model')
current_model = self.env.context.get("active_model")
model_except_obj = self.env[current_model] model_except_obj = self.env[current_model]
active_ids = self.env.context.get('active_ids')
active_ids = self.env.context.get("active_ids")
if len(active_ids) > 1: if len(active_ids) > 1:
raise ValidationError( raise ValidationError(
_('Only 1 ID accepted, got %r.') % active_ids)
_("Only 1 ID accepted, got %r.") % active_ids
)
active_id = active_ids[0] active_id = active_ids[0]
related_model_except = model_except_obj.browse(active_id) related_model_except = model_except_obj.browse(active_id)
exception_ids = related_model_except.exception_ids.ids exception_ids = related_model_except.exception_ids.ids
res.update({'exception_ids': [(6, 0, exception_ids)]})
res.update({'related_model_id': active_id})
res.update({"exception_ids": [(6, 0, exception_ids)]})
res.update({"related_model_id": active_id})
return res return res
@api.multi @api.multi
def action_confirm(self): def action_confirm(self):
self.ensure_one() self.ensure_one()
return {'type': 'ir.actions.act_window_close'}
return {"type": "ir.actions.act_window_close"}
Loading…
Cancel
Save