From 6b647139b446f6196baa119a609da3bd4ad719f1 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 25 Apr 2019 07:12:56 +0000 Subject: [IMP] base: make the integrity constraint violation error messages clearer When an `IntegrityError` is raised, the user receives a cryptic error message which doesn't provide much valuable information in order to solve the problem. However, such an error is raised in 3 cases: 1. A mandatory field is not provided at creation/update. 2. The deletion of a record makes a mandatory Many2one field NULL on a referenced table (`ON DELETE SET NULL` on a not nullable field). 3. The deletion of a record raises a `ON DELETE RESTRICT` foreign-key constaints. In the first and second cases, we provide the table and field on which the `NOT NULL` constaint is raised. We also suggest to archive the record in case of a deletion. In the third case, we provide the table and the constraint raised. We also suggest to archive the record. Notes: - The IDs of the records causing the issue is not provided since the information is not provided by PostgreSQL. - Although cases 2 and 3 have a different root cause, the error message raised is very similar. Indeed, from an end-user perspective the solution is identical: archive the record. Another solution would also be to manually edit the records raising the error, but most of the time this is not an option: these records are locked and cannot be edited anymore. task-1970853 Closes #32949 closes odoo/odoo#33922 Signed-off-by: Nicolas Martinelli (nim) Co-authored-by: Sapan Zaveri Co-authored-by: Pragya Ladda --- odoo/addons/test_rpc/tests/test_error.py | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 odoo/addons/test_rpc/tests/test_error.py (limited to 'odoo/addons/test_rpc/tests/test_error.py') diff --git a/odoo/addons/test_rpc/tests/test_error.py b/odoo/addons/test_rpc/tests/test_error.py new file mode 100644 index 00000000000..3266b97a699 --- /dev/null +++ b/odoo/addons/test_rpc/tests/test_error.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from odoo.tests import common + + +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: + 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: + 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: Field b1 (field_b1)", e.faultString) + + # Unlink b2 => ON DELETE RESTRICT constraint raises + try: + 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 + ) -- cgit v1.2.3 From 24b677a3597beaf0e0509fd09d8f71c7803d8f09 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 3 Jul 2019 19:20:31 +0200 Subject: [FIX] test_rpc: more robust tests - mute logger to avoid logging bad queries - enforce fields strings These changes will forbid tests to fail in following versions. --- odoo/addons/test_rpc/tests/test_error.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'odoo/addons/test_rpc/tests/test_error.py') diff --git a/odoo/addons/test_rpc/tests/test_error.py b/odoo/addons/test_rpc/tests/test_error.py index 3266b97a699..6896b1f180d 100644 --- a/odoo/addons/test_rpc/tests/test_error.py +++ b/odoo/addons/test_rpc/tests/test_error.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo.tests import common +from odoo.tools.misc import mute_logger class TestError(common.HttpCase): @@ -15,7 +16,8 @@ class TestError(common.HttpCase): """ Create: mandatory field not provided """ self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "create", {"name": "B1"}) try: - self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "create", {}) + 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) @@ -38,7 +40,8 @@ class TestError(common.HttpCase): # FORWARD-PORT: in master, `required=True` implies `ondelete="restrict"` # => the message should be the same than 'Unlink b2' case. try: - self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "unlink", b1) + 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) @@ -47,11 +50,12 @@ class TestError(common.HttpCase): "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: Field b1 (field_b1)", 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: - self.o.execute(self.db_name, 1, "admin", "test_rpc.model_b", "unlink", b2) + 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) -- cgit v1.2.3