[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
This commit is contained in:
Victor Feyens
2020-11-20 10:04:27 +00:00
parent dce6a8e080
commit 8d32f8724e
8 changed files with 53 additions and 24 deletions
+6 -2
View File
@@ -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):
+6 -2
View File
@@ -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.
+3 -2
View File
@@ -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.'))
+6 -2
View File
@@ -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()
+3 -2
View File
@@ -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):
+3 -2
View File
@@ -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):
+24 -10
View File
@@ -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):
+2 -2
View File
@@ -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")