[MERGE] forward port branch saas-14 up to c816efe7de
This commit is contained in:
@@ -204,7 +204,7 @@ class ResPartner(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _credit_debit_get(self):
|
def _credit_debit_get(self):
|
||||||
tables, where_clause, where_params = self.env['account.move.line']._query_get()
|
tables, where_clause, where_params = self.env['account.move.line'].with_context(company_id=self.env.user.company_id.id)._query_get()
|
||||||
where_params = [tuple(self.ids)] + where_params
|
where_params = [tuple(self.ids)] + where_params
|
||||||
if where_clause:
|
if where_clause:
|
||||||
where_clause = 'AND ' + where_clause
|
where_clause = 'AND ' + where_clause
|
||||||
@@ -240,9 +240,9 @@ class ResPartner(models.Model):
|
|||||||
LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
|
LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
|
||||||
RIGHT JOIN account_account acc ON aml.account_id = acc.id
|
RIGHT JOIN account_account acc ON aml.account_id = acc.id
|
||||||
WHERE acc.internal_type = %s
|
WHERE acc.internal_type = %s
|
||||||
AND NOT acc.deprecated
|
AND NOT acc.deprecated AND acc.company_id = %s
|
||||||
GROUP BY partner.id
|
GROUP BY partner.id
|
||||||
HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, sign, operand))
|
HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, self.env.user.company_id.id, sign, operand))
|
||||||
res = self._cr.fetchall()
|
res = self._cr.fetchall()
|
||||||
if not res:
|
if not res:
|
||||||
return [('id', '=', '0')]
|
return [('id', '=', '0')]
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ class AccountFrFec(models.TransientModel):
|
|||||||
TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate,
|
TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate,
|
||||||
CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/'
|
CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/'
|
||||||
WHEN aml.name SIMILAR TO '[\t|\s|\n]*' THEN '/'
|
WHEN aml.name SIMILAR TO '[\t|\s|\n]*' THEN '/'
|
||||||
ELSE replace(replace(replace(aml.name, '|', '/'), '\t', ''), '\n', '') END AS EcritureLib,
|
ELSE replace(replace(replace(replace(aml.name, '|', '/'), '\t', ''), '\n', ''), '\r', '') END AS EcritureLib,
|
||||||
replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
|
replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
|
||||||
replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
|
replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
|
||||||
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
|
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import models
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
"name": "Test RPC",
|
||||||
|
"version": "1.0",
|
||||||
|
"category": "Tests",
|
||||||
|
"description": """A module to test the RPC requests.""",
|
||||||
|
"depends": ["base", "web"],
|
||||||
|
"installable": True,
|
||||||
|
"auto_install": False,
|
||||||
|
"data": ["ir.model.access.csv"],
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||||
|
access_test_rpc_model_a,access_test_rpc_model_a,model_test_rpc_model_a,,1,1,1,1
|
||||||
|
access_test_rpc_model_b,access_test_rpc_model_b,model_test_rpc_model_b,,1,1,1,1
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ModelA(models.Model):
|
||||||
|
_name = "test_rpc.model_a"
|
||||||
|
_description = "Model A"
|
||||||
|
|
||||||
|
name = fields.Char(required=True)
|
||||||
|
field_b1 = fields.Many2one("test_rpc.model_b", string="required field", required=True)
|
||||||
|
field_b2 = fields.Many2one("test_rpc.model_b", string="restricted field", ondelete="restrict")
|
||||||
|
|
||||||
|
|
||||||
|
class ModelB(models.Model):
|
||||||
|
_name = "test_rpc.model_b"
|
||||||
|
_description = "Model B"
|
||||||
|
|
||||||
|
name = fields.Char(required=True)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import test_error
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.tests import common
|
||||||
|
from odoo.tools.misc import mute_logger
|
||||||
|
|
||||||
|
|
||||||
|
class TestError(common.HttpCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestError, self).setUp()
|
||||||
|
self.o = self.xmlrpc_object
|
||||||
|
self.db_name = common.get_db_name()
|
||||||
|
# Reset the admin's lang to avoid breaking tests due to admin not in English
|
||||||
|
self.o.execute(self.db_name, 1, "admin", "res.users", "write", [1], {"lang": False})
|
||||||
|
|
||||||
|
def test_01_create(self):
|
||||||
|
""" Create: mandatory field not provided """
|
||||||
|
self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "create", {"name": "B1"})
|
||||||
|
try:
|
||||||
|
with mute_logger("odoo.sql_db"):
|
||||||
|
self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "create", {})
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
self.assertIn("The operation cannot be completed:", e.faultString)
|
||||||
|
self.assertIn("Create/update: a mandatory field is not set.", e.faultString)
|
||||||
|
self.assertIn(
|
||||||
|
"Delete: another model requires the record being deleted. If possible, archive it instead.",
|
||||||
|
e.faultString,
|
||||||
|
)
|
||||||
|
self.assertIn("Model: Model B (test_rpc.model_b), Field: Name (name)", e.faultString)
|
||||||
|
|
||||||
|
def test_02_delete(self):
|
||||||
|
""" Delete: NOT NULL and ON DELETE RESTRICT constraints """
|
||||||
|
b1 = self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "create", {"name": "B1"})
|
||||||
|
b2 = self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "create", {"name": "B2"})
|
||||||
|
self.o.execute(
|
||||||
|
self.db_name, 1, "admin", "test_rpc.model_a", "create", {"name": "A1", "field_b1": b1, "field_b2": b2}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Unlink b1 => NOT NULL constraint raises
|
||||||
|
# FORWARD-PORT: in master, `required=True` implies `ondelete="restrict"`
|
||||||
|
# => the message should be the same than 'Unlink b2' case.
|
||||||
|
try:
|
||||||
|
with mute_logger("odoo.sql_db"):
|
||||||
|
self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "unlink", b1)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
self.assertIn("The operation cannot be completed:", e.faultString)
|
||||||
|
self.assertIn("Create/update: a mandatory field is not set.", e.faultString)
|
||||||
|
self.assertIn(
|
||||||
|
"Delete: another model requires the record being deleted. If possible, archive it instead.",
|
||||||
|
e.faultString,
|
||||||
|
)
|
||||||
|
self.assertIn("Model: Model A (test_rpc.model_a), Field: required field (field_b1)", e.faultString)
|
||||||
|
|
||||||
|
# Unlink b2 => ON DELETE RESTRICT constraint raises
|
||||||
|
try:
|
||||||
|
with mute_logger("odoo.sql_db"):
|
||||||
|
self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "unlink", b2)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
self.assertIn("The operation cannot be completed:", e.faultString)
|
||||||
|
self.assertIn(
|
||||||
|
" another model requires the record being deleted. If possible, archive it instead.", e.faultString
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"Model: Model A (test_rpc.model_a), Constraint: test_rpc_model_a_field_b2_fkey", e.faultString
|
||||||
|
)
|
||||||
+32
-14
@@ -122,22 +122,40 @@ def check(f):
|
|||||||
if key in inst[0]:
|
if key in inst[0]:
|
||||||
raise ValidationError(tr(registry._sql_error[key], 'sql_constraint') or inst[0])
|
raise ValidationError(tr(registry._sql_error[key], 'sql_constraint') or inst[0])
|
||||||
if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
|
if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
|
||||||
msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
|
msg = _('The operation cannot be completed:')
|
||||||
_logger.debug("IntegrityError", exc_info=True)
|
_logger.debug("IntegrityError", exc_info=True)
|
||||||
try:
|
try:
|
||||||
errortxt = inst.pgerror.replace('«','"').replace('»','"')
|
# Get corresponding model and field
|
||||||
if '"public".' in errortxt:
|
model = field = None
|
||||||
context = errortxt.split('"public".')[1]
|
for name, rclass in registry.items():
|
||||||
model_name = table = context.split('"')[1]
|
if inst.diag.table_name == rclass._table:
|
||||||
else:
|
model = rclass
|
||||||
last_quote_end = errortxt.rfind('"')
|
field = model._fields.get(inst.diag.column_name)
|
||||||
last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
|
break
|
||||||
model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
|
if inst.pgcode == errorcodes.NOT_NULL_VIOLATION:
|
||||||
model = table.replace("_",".")
|
# This is raised when a field is set with `required=True`. 2 cases:
|
||||||
if model in registry:
|
# - Create/update: a mandatory field is not set.
|
||||||
model_class = registry[model]
|
# - Delete: another model has a not nullable using the deleted record.
|
||||||
model_name = model_class._description or model_class._name
|
msg += '\n'
|
||||||
msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
|
msg += _(
|
||||||
|
'- Create/update: a mandatory field is not set.\n'
|
||||||
|
'- Delete: another model requires the record being deleted. If possible, archive it instead.'
|
||||||
|
)
|
||||||
|
if model:
|
||||||
|
msg += '\n\n{} {} ({}), {} {} ({})'.format(
|
||||||
|
_('Model:'), model._description, model._name,
|
||||||
|
_('Field:'), field.string if field else _('Unknown'), field.name if field else _('Unknown'),
|
||||||
|
)
|
||||||
|
elif inst.pgcode == errorcodes.FOREIGN_KEY_VIOLATION:
|
||||||
|
# This is raised when a field is set with `ondelete='restrict'`, at
|
||||||
|
# unlink only.
|
||||||
|
msg += _(' another model requires the record being deleted. If possible, archive it instead.')
|
||||||
|
constraint = inst.diag.constraint_name
|
||||||
|
if model or constraint:
|
||||||
|
msg += '\n\n{} {} ({}), {} {}'.format(
|
||||||
|
_('Model:'), model._description if model else _('Unknown'), model._name if model else _('Unknown'),
|
||||||
|
_('Constraint:'), constraint if constraint else _('Unknown'),
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user