diff --git a/auditlog/__manifest__.py b/auditlog/__manifest__.py index b033bf0a2..12810bf6c 100644 --- a/auditlog/__manifest__.py +++ b/auditlog/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Audit Log", - "version": "16.0.2.0.2", + "version": "17.0.1.0.0", "author": "ABF OSIELL, Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/server-tools", diff --git a/auditlog/models/auditlog_log_line_view.py b/auditlog/models/auditlog_log_line_view.py index 98a660e2c..98e3084bd 100644 --- a/auditlog/models/auditlog_log_line_view.py +++ b/auditlog/models/auditlog_log_line_view.py @@ -61,4 +61,4 @@ class AuditlogLogLineView(models.Model): @property def _table_query(self): - return "SELECT %s FROM %s" % (self._select_query(), self._from_query()) + return f"SELECT {self._select_query()} FROM {self._from_query()}" diff --git a/auditlog/models/autovacuum.py b/auditlog/models/autovacuum.py index bf56fc529..84015854b 100644 --- a/auditlog/models/autovacuum.py +++ b/auditlog/models/autovacuum.py @@ -31,7 +31,6 @@ class AuditlogAutovacuum(models.TransientModel): order="create_date asc", ) nb_records = len(records) - with self.env.norecompute(): - records.unlink() + records.unlink() _logger.info("AUTOVACUUM - %s '%s' records deleted", nb_records, data_model) return True diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 067077c67..bcf7cf9bf 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -51,12 +51,11 @@ class AuditlogRule(models.Model): _name = "auditlog.rule" _description = "Auditlog - Rule" - name = fields.Char(required=True, states={"subscribed": [("readonly", True)]}) + name = fields.Char(required=True) model_id = fields.Many2one( "ir.model", "Model", help="Select model for which you want to generate log.", - states={"subscribed": [("readonly", True)]}, ondelete="set null", index=True, ) @@ -68,8 +67,7 @@ class AuditlogRule(models.Model): "user_id", "rule_id", string="Users", - help="if User is not added then it will applicable for all users", - states={"subscribed": [("readonly", True)]}, + help="if no user is added then it will applicable for all users", ) log_read = fields.Boolean( "Log Reads", @@ -77,7 +75,6 @@ class AuditlogRule(models.Model): "Select this if you want to keep track of read/open on any " "record of the model of this rule" ), - states={"subscribed": [("readonly", True)]}, ) log_write = fields.Boolean( "Log Writes", @@ -86,7 +83,6 @@ class AuditlogRule(models.Model): "Select this if you want to keep track of modification on any " "record of the model of this rule" ), - states={"subscribed": [("readonly", True)]}, ) log_unlink = fields.Boolean( "Log Deletes", @@ -95,7 +91,6 @@ class AuditlogRule(models.Model): "Select this if you want to keep track of deletion on any " "record of the model of this rule" ), - states={"subscribed": [("readonly", True)]}, ) log_create = fields.Boolean( "Log Creates", @@ -104,7 +99,6 @@ class AuditlogRule(models.Model): "Select this if you want to keep track of creation on any " "record of the model of this rule" ), - states={"subscribed": [("readonly", True)]}, ) log_type = fields.Selection( [("full", "Full log"), ("fast", "Fast log")], @@ -118,7 +112,6 @@ class AuditlogRule(models.Model): "Fast log: only log the changes made through the create and " "write operations (less information, but it is faster)" ), - states={"subscribed": [("readonly", True)]}, ) state = fields.Selection( @@ -129,7 +122,6 @@ class AuditlogRule(models.Model): action_id = fields.Many2one( "ir.actions.act_window", string="Action", - states={"subscribed": [("readonly", True)]}, ) capture_record = fields.Boolean( help="Select this if you want to keep track of Unlink Record", @@ -138,14 +130,12 @@ class AuditlogRule(models.Model): "res.users", string="Users to Exclude", context={"active_test": False}, - states={"subscribed": [("readonly", True)]}, ) fields_to_exclude_ids = fields.Many2many( "ir.model.fields", domain="[('model_id', '=', model_id)]", string="Fields to Exclude", - states={"subscribed": [("readonly", True)]}, ) _sql_constraints = [ @@ -161,7 +151,7 @@ class AuditlogRule(models.Model): def _register_hook(self): """Get all rules and apply them to log method calls.""" - super(AuditlogRule, self)._register_hook() + super()._register_hook() if not hasattr(self.pool, "_auditlog_field_cache"): self.pool._auditlog_field_cache = {} if not hasattr(self.pool, "_auditlog_model_cache"): @@ -170,6 +160,24 @@ class AuditlogRule(models.Model): self = self.search([("state", "=", "subscribed")]) return self._patch_methods() + def _patch_method(self, model, method_name, check_attr): + result = new_method = False + model_class = type(model) + if method_name == "create": + new_method = self._make_create() + elif method_name == "read": + new_method = self._make_read() + elif method_name == "write": + new_method = self._make_write() + elif method_name == "unlink": + new_method = self._make_unlink() + if new_method: + new_method.origin = getattr(model_class, method_name) + setattr(model_class, method_name, new_method) + setattr(type(model), check_attr, True) + result = True + return result + def _patch_methods(self): """Patch ORM methods of models defined in rules to log their calls.""" updated = False @@ -185,27 +193,19 @@ class AuditlogRule(models.Model): # -> create check_attr = "auditlog_ruled_create" if rule.log_create and not hasattr(model_model, check_attr): - model_model._patch_method("create", rule._make_create()) - setattr(type(model_model), check_attr, True) - updated = True + updated = rule._patch_method(model_model, "create", check_attr) # -> read check_attr = "auditlog_ruled_read" if rule.log_read and not hasattr(model_model, check_attr): - model_model._patch_method("read", rule._make_read()) - setattr(type(model_model), check_attr, True) - updated = True + updated = rule._patch_method(model_model, "read", check_attr) # -> write check_attr = "auditlog_ruled_write" if rule.log_write and not hasattr(model_model, check_attr): - model_model._patch_method("write", rule._make_write()) - setattr(type(model_model), check_attr, True) - updated = True + updated = rule._patch_method(model_model, "write", check_attr) # -> unlink check_attr = "auditlog_ruled_unlink" if rule.log_unlink and not hasattr(model_model, check_attr): - model_model._patch_method("unlink", rule._make_unlink()) - setattr(type(model_model), check_attr, True) - updated = True + updated = rule._patch_method(model_model, "unlink", check_attr) return updated def _revert_methods(self): @@ -217,7 +217,9 @@ class AuditlogRule(models.Model): if getattr(rule, "log_%s" % method) and hasattr( getattr(model_model, method), "origin" ): - model_model._revert_method(method) + setattr( + type(model_model), method, getattr(model_model, method).origin + ) delattr(type(model_model), "auditlog_ruled_%s" % method) updated = True if updated: @@ -252,7 +254,7 @@ class AuditlogRule(models.Model): def unlink(self): """Unsubscribe rules before removing them.""" self.unsubscribe() - return super(AuditlogRule, self).unlink() + return super().unlink() @api.model def get_auditlog_fields(self, model): @@ -315,7 +317,7 @@ class AuditlogRule(models.Model): vals_list2 = copy.deepcopy(vals_list) new_records = create_fast.origin(self, vals_list, **kwargs) new_values = {} - for vals, new_record in zip(vals_list2, new_records): + for vals, new_record in zip(vals_list2, new_records, strict=True): new_values.setdefault(new_record.id, vals) if self.env.user in users_to_exclude: return new_records @@ -511,10 +513,9 @@ class AuditlogRule(models.Model): auditlog_rule = self.env["auditlog.rule"].search([("model_id", "=", model_id)]) fields_to_exclude = auditlog_rule.fields_to_exclude_ids.mapped("name") for res_id in res_ids: - name = model_model.browse(res_id).name_get() - res_name = name and name[0] and name[0][1] + res = model_model.browse(res_id) vals = { - "name": res_name, + "name": res.display_name, "model_id": model_id, "res_id": res_id, "method": method, @@ -600,10 +601,10 @@ class AuditlogRule(models.Model): "new_value_text": False, } if field["relation"] and "2many" in field["ttype"]: - old_value_text = ( - self.env[field["relation"]].browse(vals["old_value"]).name_get() - ) - vals["old_value_text"] = old_value_text + vals["old_value_text"] = [ + (x.id, x.display_name) + for x in self.env[field["relation"]].browse(vals["old_value"]) + ] return vals def _create_log_line_on_write( @@ -635,27 +636,27 @@ class AuditlogRule(models.Model): "new_value": new_values[log.res_id][field["name"]], "new_value_text": new_values[log.res_id][field["name"]], } - # for *2many fields, log the name_get + # for *2many fields, log the display_name if log.log_type == "full" and field["relation"] and "2many" in field["ttype"]: - # Filter IDs to prevent a 'name_get()' call on deleted resources + # Filter IDs to prevent a 'display_name' call on deleted resources existing_ids = self.env[field["relation"]]._search( [("id", "in", vals["old_value"])] ) old_value_text = [] if existing_ids: - existing_values = ( - self.env[field["relation"]].browse(existing_ids).name_get() - ) - old_value_text.extend(existing_values) + old_value_text = [ + (x.id, x.display_name) + for x in self.env[field["relation"]].browse(existing_ids) + ] # Deleted resources will have a 'DELETED' text representation deleted_ids = set(vals["old_value"]) - set(existing_ids) for deleted_id in deleted_ids: old_value_text.append((deleted_id, "DELETED")) vals["old_value_text"] = old_value_text - new_value_text = ( - self.env[field["relation"]].browse(vals["new_value"]).name_get() - ) - vals["new_value_text"] = new_value_text + vals["new_value_text"] = [ + (x.id, x.display_name) + for x in self.env[field["relation"]].browse(vals["new_value"]) + ] return vals def _create_log_line_on_create( @@ -686,10 +687,10 @@ class AuditlogRule(models.Model): "new_value_text": new_values[log.res_id][field["name"]], } if log.log_type == "full" and field["relation"] and "2many" in field["ttype"]: - new_value_text = ( - self.env[field["relation"]].browse(vals["new_value"]).name_get() - ) - vals["new_value_text"] = new_value_text + vals["new_value_text"] = [ + (x.id, x.display_name) + for x in self.env[field["relation"]].browse(vals["new_value"]) + ] return vals def subscribe(self): diff --git a/auditlog/tests/test_auditlog.py b/auditlog/tests/test_auditlog.py index 342b2d8b2..f60b7e2e0 100644 --- a/auditlog/tests/test_auditlog.py +++ b/auditlog/tests/test_auditlog.py @@ -2,7 +2,7 @@ # © 2018 Pieter Paulussen # © 2021 Stefan Rijnhart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo.tests.common import Form, TransactionCase +from odoo.tests.common import TransactionCase from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG from odoo.addons.base.models.res_users import name_boolean_group @@ -50,11 +50,15 @@ class AuditlogCommon: self.groups_rule.subscribe() auditlog_log = self.env["auditlog.log"] - testgroup3 = testgroup3 = self.env["res.groups"].create({"name": "testgroup3"}) - testgroup4 = self.env["res.groups"].create( - {"name": "testgroup4", "implied_ids": [(4, testgroup3.id)]} + testgroup3 = self.env["res.groups"].create({"name": "testgroup3"}) + testgroup4 = self.env["res.groups"].create({"name": "testgroup4"}) + testgroup5 = self.env["res.groups"].create( + { + "name": "testgroup5", + "implied_ids": [(4, testgroup3.id), (4, testgroup4.id)], + } ) - testgroup4.write({"implied_ids": [(2, testgroup3.id)]}) + testgroup5.write({"implied_ids": [(2, testgroup3.id)]}) self.assertTrue( auditlog_log.search( [ @@ -69,7 +73,7 @@ class AuditlogCommon: [ ("model_id", "=", self.groups_model_id), ("method", "=", "create"), - ("res_id", "=", testgroup4.id), + ("res_id", "=", testgroup5.id), ] ).ensure_one() ) @@ -78,7 +82,7 @@ class AuditlogCommon: [ ("model_id", "=", self.groups_model_id), ("method", "=", "write"), - ("res_id", "=", testgroup4.id), + ("res_id", "=", testgroup5.id), ] ).ensure_one() ) @@ -206,7 +210,14 @@ class AuditlogCommon: def test_LogUpdate(self): """Tests write results with different M2O values.""" self.groups_rule.subscribe() - group = self.env["res.groups"].create({"name": "testgroup1"}) + testgroup3 = self.env["res.groups"].create({"name": "testgroup3"}) + testgroup4 = self.env["res.groups"].create({"name": "testgroup4"}) + group = self.env["res.groups"].create( + { + "name": "testgroup1", + "implied_ids": [(4, testgroup3.id), (4, testgroup4.id)], + } + ) cat = self.env["ir.module.category"].create({"name": "Test Category"}) group.write( { @@ -262,7 +273,7 @@ class AuditlogCommon: class TestAuditlogFull(TransactionCase, AuditlogCommon): def setUp(self): - super(TestAuditlogFull, self).setUp() + super().setUp() self.groups_model_id = self.env.ref("base.model_res_groups").id self.groups_rule = self.env["auditlog.rule"].create( { @@ -278,12 +289,12 @@ class TestAuditlogFull(TransactionCase, AuditlogCommon): def tearDown(self): self.groups_rule.unlink() - super(TestAuditlogFull, self).tearDown() + super().tearDown() class TestAuditlogFast(TransactionCase, AuditlogCommon): def setUp(self): - super(TestAuditlogFast, self).setUp() + super().setUp() self.groups_model_id = self.env.ref("base.model_res_groups").id self.groups_rule = self.env["auditlog.rule"].create( { @@ -299,7 +310,7 @@ class TestAuditlogFast(TransactionCase, AuditlogCommon): def tearDown(self): self.groups_rule.unlink() - super(TestAuditlogFast, self).tearDown() + super().tearDown() class TestFieldRemoval(TransactionCase): @@ -398,7 +409,7 @@ class TestFieldRemoval(TransactionCase): class TestAuditlogFullCaptureRecord(TransactionCase, AuditlogCommon): def setUp(self): - super(TestAuditlogFullCaptureRecord, self).setUp() + super().setUp() self.groups_model_id = self.env.ref("base.model_res_groups").id self.groups_rule = self.env["auditlog.rule"].create( { @@ -415,13 +426,13 @@ class TestAuditlogFullCaptureRecord(TransactionCase, AuditlogCommon): def tearDown(self): self.groups_rule.unlink() - super(TestAuditlogFullCaptureRecord, self).tearDown() + super().tearDown() class AuditLogRuleTestForUserFields(TransactionCase): @classmethod def setUpClass(cls): - super(AuditLogRuleTestForUserFields, cls).setUpClass() + super().setUpClass() # get Contact model id cls.contact_model_id = ( cls.env["ir.model"].search([("model", "=", "res.partner")]).id @@ -551,14 +562,11 @@ class AuditLogRuleTestForUserFields(TransactionCase): self.assertTrue("phone" not in field_names) def test_03_AuditlogFull_user_exclude_write_log(self): - # Update email in Form view with excluded user - partner_form = Form( - self.testpartner1.with_user(self.user.id).with_context( - tracking_disable=True - ) + # Update email with excluded user + partner = self.testpartner1.with_user(self.user.id).with_context( + tracking_disable=True ) - partner_form.email = "vendor@mail.com" - testpartner1 = partner_form.save() + partner.email = "vendor@mail.com" # Checking write log not created with self.assertRaises(ValueError): @@ -566,7 +574,7 @@ class AuditLogRuleTestForUserFields(TransactionCase): [ ("model_id", "=", self.auditlog_rule.model_id.id), ("method", "=", "write"), - ("res_id", "=", testpartner1.id), + ("res_id", "=", partner.id), ("user_id", "=", self.user.id), ] ).ensure_one() diff --git a/auditlog/tests/test_autovacuum.py b/auditlog/tests/test_autovacuum.py index 2b4de24dd..362d4fb19 100644 --- a/auditlog/tests/test_autovacuum.py +++ b/auditlog/tests/test_autovacuum.py @@ -7,7 +7,7 @@ from odoo.tests.common import TransactionCase class TestAuditlogAutovacuum(TransactionCase): def setUp(self): - super(TestAuditlogAutovacuum, self).setUp() + super().setUp() self.groups_model_id = self.env.ref("base.model_res_groups").id self.groups_rule = self.env["auditlog.rule"].create( { @@ -24,7 +24,7 @@ class TestAuditlogAutovacuum(TransactionCase): def tearDown(self): self.groups_rule.unlink() - super(TestAuditlogAutovacuum, self).tearDown() + super().tearDown() def test_autovacuum(self): log_model = self.env["auditlog.log"] diff --git a/auditlog/views/auditlog_view.xml b/auditlog/views/auditlog_view.xml index 848b25044..449982e9f 100644 --- a/auditlog/views/auditlog_view.xml +++ b/auditlog/views/auditlog_view.xml @@ -18,46 +18,48 @@ string="Subscribe" name="subscribe" type="object" - states="draft" + invisible="state != 'draft'" class="oe_highlight" />