From 8d32f8724ed8971dcc130ff3ebbbe78f2e1bf5b2 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 7 Aug 2020 13:37:37 +0000 Subject: [PATCH] [FIX] *: adapt python constraints to support multi recordsets Otherwise, they may crash or be incorrectly verified when creating/updating recordsets containing multiple records. Also improve the constraint error messages to be more detailed. When possible, use a sql constraint instead to speed up the records validation. Task Id: 2328664 COM PR: https://github.com/odoo/odoo/pull/55525 ENT PR: https://github.com/odoo/enterprise/pull/12250 --- addons/hr_contract/models/hr_contract.py | 8 +++-- addons/hr_expense/models/hr_expense.py | 8 +++-- addons/l10n_se_ocr/models/account_journal.py | 5 +-- addons/mail/models/mail_alias.py | 8 +++-- addons/mrp/models/mrp_unbuild.py | 5 +-- addons/mrp/models/mrp_workcenter.py | 5 +-- addons/point_of_sale/models/pos_config.py | 34 ++++++++++++++------ odoo/addons/test_inherits/models.py | 4 +-- 8 files changed, 53 insertions(+), 24 deletions(-) diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py index 20665da6879..eda744d43f3 100644 --- a/addons/hr_contract/models/hr_contract.py +++ b/addons/hr_contract/models/hr_contract.py @@ -126,8 +126,12 @@ class Contract(models.Model): @api.constrains('date_start', 'date_end') def _check_dates(self): - if self.filtered(lambda c: c.date_end and c.date_start > c.date_end): - raise ValidationError(_('Contract start date must be earlier than contract end date.')) + for contract in self: + if contract.date_end and contract.date_start > contract.date_end: + raise ValidationError(_( + 'Contract %(contract)s: start date (%(start)s) must be earlier than contract end date (%(end)s).', + contract=contract.name, start=contract.date_start, end=contract.date_end, + )) @api.model def update_state(self): diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 5702e84f4cd..755ffd27db8 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -194,8 +194,12 @@ class HrExpense(models.Model): @api.constrains('product_id', 'product_uom_id') def _check_product_uom_category(self): - if self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id: - raise UserError(_('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')) + for expense in self: + if expense.product_id and expense.product_uom_id.category_id != expense.product_id.uom_id.category_id: + raise UserError(_( + 'Selected Unit of Measure for expense %(expense)s does not belong to the same category as the Unit of Measure of product %(product)s.', + expense=expense.name, product=expense.product_id.name, + )) def create_expense_from_attachments(self, attachment_ids=None, view_type='tree'): ''' Create the expenses from files. diff --git a/addons/l10n_se_ocr/models/account_journal.py b/addons/l10n_se_ocr/models/account_journal.py index 806f07c595c..0327c08a957 100644 --- a/addons/l10n_se_ocr/models/account_journal.py +++ b/addons/l10n_se_ocr/models/account_journal.py @@ -13,5 +13,6 @@ class AccountJournal(models.Model): @api.constrains('l10n_se_invoice_ocr_length') def _check_l10n_se_invoice_ocr_length(self): - if self.l10n_se_invoice_ocr_length < 6: - return ValidationError(_('OCR Reference Number length need to be greater than 5. Please correct settings under invoice journal settings.')) + for journal in self: + if journal.l10n_se_invoice_ocr_length < 6: + return ValidationError(_('OCR Reference Number length need to be greater than 5. Please correct settings under invoice journal settings.')) diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py index b41b267947f..246451724c5 100644 --- a/addons/mail/models/mail_alias.py +++ b/addons/mail/models/mail_alias.py @@ -87,8 +87,12 @@ class Alias(models.Model): local-part. Quoted-string and internationnal characters are to be rejected. See rfc5322 sections 3.4.1 and 3.2.3 """ - if self.alias_name and not dot_atom_text.match(self.alias_name): - raise ValidationError(_("You cannot use anything else than unaccented latin characters in the alias address.")) + for alias in self: + if alias.alias_name and not dot_atom_text.match(alias.alias_name): + raise ValidationError(_( + "You cannot use anything else than unaccented latin characters in the alias address (%s).", + alias.alias_name, + )) def _compute_alias_domain(self): alias_domain = self._default_alias_domain() diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py index 03eb22dbf00..3ae4b339b2e 100644 --- a/addons/mrp/models/mrp_unbuild.py +++ b/addons/mrp/models/mrp_unbuild.py @@ -124,8 +124,9 @@ class MrpUnbuild(models.Model): @api.constrains('product_qty') def _check_qty(self): - if self.product_qty <= 0: - raise ValueError(_('Unbuild Order product quantity has to be strictly positive.')) + for unbuild in self: + if unbuild.product_qty <= 0: + raise ValueError(_('Unbuild Order product quantity has to be strictly positive.')) @api.model def create(self, vals): diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py index 969461a4d61..b79efe353d2 100644 --- a/addons/mrp/models/mrp_workcenter.py +++ b/addons/mrp/models/mrp_workcenter.py @@ -73,8 +73,9 @@ class MrpWorkcenter(models.Model): @api.constrains('alternative_workcenter_ids') def _check_alternative_workcenter(self): - if self in self.alternative_workcenter_ids: - raise ValidationError(_("A workcenter cannot be an alternative of itself")) + for workcenter in self: + if workcenter in workcenter.alternative_workcenter_ids: + raise ValidationError(_("Workcenter %s cannot be an alternative of itself.", workcenter.name)) @api.depends('order_ids.duration_expected', 'order_ids.workcenter_id', 'order_ids.state', 'order_ids.date_planned_start') def _compute_workorder_count(self): diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 627c83e6e54..e39c4f4614c 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -328,13 +328,24 @@ class PosConfig(models.Model): @api.constrains('rounding_method') def _check_rounding_method_strategy(self): - if self.cash_rounding and self.rounding_method.strategy != 'add_invoice_line': - raise ValidationError(_("Cash rounding strategy must be: 'Add a rounding line'")) + for config in self: + if config.cash_rounding and config.rounding_method.strategy != 'add_invoice_line': + selection_value = "Add a rounding line" + for key, val in self.env["account.cash.rounding"]._fields["stategy"]._description_selection(config.env): + if key == "add_invoice_line": + selection_value = val + break + raise ValidationError(_( + "The cash rounding strategy of the point of sale %(pos)s must be: '%(value)s'", + pos=config.name, + value=selection_value, + )) @api.constrains('company_id', 'journal_id') def _check_company_journal(self): - if self.journal_id and self.journal_id.company_id.id != self.company_id.id: - raise ValidationError(_("The sales journal and the point of sale must belong to the same company.")) + for config in self: + if config.journal_id and config.journal_id.company_id.id != config.company_id.id: + raise ValidationError(_("The sales journal of the point of sale %s must belong to its company.", config.name)) def _check_profit_loss_cash_journal(self): if self.cash_control and self.payment_method_ids: @@ -344,13 +355,15 @@ class PosConfig(models.Model): @api.constrains('company_id', 'invoice_journal_id') def _check_company_invoice_journal(self): - if self.invoice_journal_id and self.invoice_journal_id.company_id.id != self.company_id.id: - raise ValidationError(_("The invoice journal and the point of sale must belong to the same company.")) + for config in self: + if config.invoice_journal_id and config.invoice_journal_id.company_id.id != config.company_id.id: + raise ValidationError(_("The invoice journal of the point of sale %s must belong to the same company.", config.name)) @api.constrains('company_id', 'payment_method_ids') def _check_company_payment(self): - if self.env['pos.payment.method'].search_count([('id', 'in', self.payment_method_ids.ids), ('company_id', '!=', self.company_id.id)]): - raise ValidationError(_("The payment methods and the point of sale must belong to the same company.")) + for config in self: + if self.env['pos.payment.method'].search_count([('id', 'in', config.payment_method_ids.ids), ('company_id', '!=', config.company_id.id)]): + raise ValidationError(_("The payment methods for the point of sale %s must belong to its company.", self.name)) @api.constrains('pricelist_id', 'use_pricelist', 'available_pricelist_ids', 'journal_id', 'invoice_journal_id', 'payment_method_ids') def _check_currencies(self): @@ -393,8 +406,9 @@ class PosConfig(models.Model): @api.constrains('company_id', 'available_pricelist_ids') def _check_companies(self): - if any(self.available_pricelist_ids.mapped(lambda pl: pl.company_id.id not in (False, self.company_id.id))): - raise ValidationError(_("The selected pricelists must belong to no company or the company of the point of sale.")) + for config in self: + if any(pricelist.company_id.id not in [False, config.company_id.id] for pricelist in config.available_pricelist_ids): + raise ValidationError(_("The selected pricelists must belong to no company or the company of the point of sale.")) @api.onchange('iface_tipproduct') def _onchange_tipproduct(self): diff --git a/odoo/addons/test_inherits/models.py b/odoo/addons/test_inherits/models.py index b503e8c173b..129c5afd0b5 100644 --- a/odoo/addons/test_inherits/models.py +++ b/odoo/addons/test_inherits/models.py @@ -73,6 +73,6 @@ class AnotherBox(models.Model): val2 = fields.Integer('Value 2', required=True) @api.constrains('val1', 'val2') - def _check(self): - if self.val1 != self.val2: + def _check_values(self): + if any(box.val1 != box.val2 for box in self): raise ValidationError("The two values must be equals")