From a68087698c072dd78848ffb4929b48a963842d47 Mon Sep 17 00:00:00 2001 From: sebalix Date: Tue, 1 Oct 2019 09:58:18 +0200 Subject: [PATCH 1/2] [FIX] auditlog: log computed fields stored in db as expected Fixing #1134. Odoo stores values of computed fields at the end of the transaction only, as such performing a 'read()' to make a data snapshot on the record created in the current transaction doesn't return the expected result regarding these fields. Also as a side-effect 'read()' alters the environment cache and break the values on the record inducing issues in the whole user transaction/workflow. This fix replaces the use of 'read()' to do the data snapshot directly from the cache of the record (computed values are already there). --- auditlog/models/rule.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index c4d061cd4..8af2bedd1 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -214,9 +214,14 @@ class AuditlogRule(models.Model): self = self.with_context(auditlog_disabled=True) rule_model = self.env['auditlog.rule'] new_record = create_full.origin(self, vals, **kwargs) - new_values = dict( - (d['id'], d) for d in new_record.sudo() - .with_context(prefetch_fields=False).read(list(self._fields))) + # Take a snapshot of record values from the cache instead of using + # 'read()'. It avoids issues with related/computed fields which + # stored in the database only at the end of the transaction, but + # their values exist in cache. + new_values = {new_record.id: {}} + for fname, field in new_record._fields.items(): + new_values[new_record.id][fname] = field.convert_to_read( + new_record[fname], new_record) rule_model.sudo().create_logs( self.env.uid, self._name, new_record.ids, 'create', None, new_values, {'log_type': log_type}) From 0e2f928eb5bb3943d0b9fa43c10a575d661bb84e Mon Sep 17 00:00:00 2001 From: sebalix Date: Tue, 1 Oct 2019 11:58:29 +0200 Subject: [PATCH 2/2] [FIX] auditlog: add support for create multi --- auditlog/models/rule.py | 38 +++++++++++++++++++-------------- auditlog/tests/test_auditlog.py | 27 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 8af2bedd1..ca48e4143 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -1,6 +1,8 @@ # Copyright 2015 ABF OSIELL # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import copy + from odoo import models, fields, api, modules, _ FIELDS_BLACKLIST = [ @@ -208,37 +210,41 @@ class AuditlogRule(models.Model): self.ensure_one() log_type = self.log_type - @api.model + @api.model_create_multi @api.returns('self', lambda value: value.id) - def create_full(self, vals, **kwargs): + def create_full(self, vals_list, **kwargs): self = self.with_context(auditlog_disabled=True) rule_model = self.env['auditlog.rule'] - new_record = create_full.origin(self, vals, **kwargs) + new_records = create_full.origin(self, vals_list, **kwargs) # Take a snapshot of record values from the cache instead of using # 'read()'. It avoids issues with related/computed fields which # stored in the database only at the end of the transaction, but # their values exist in cache. - new_values = {new_record.id: {}} - for fname, field in new_record._fields.items(): - new_values[new_record.id][fname] = field.convert_to_read( - new_record[fname], new_record) + new_values = {} + for new_record in new_records: + new_values.setdefault(new_record.id, {}) + for fname, field in new_record._fields.items(): + new_values[new_record.id][fname] = field.convert_to_read( + new_record[fname], new_record) rule_model.sudo().create_logs( - self.env.uid, self._name, new_record.ids, + self.env.uid, self._name, new_records.ids, 'create', None, new_values, {'log_type': log_type}) - return new_record + return new_records - @api.model + @api.model_create_multi @api.returns('self', lambda value: value.id) - def create_fast(self, vals, **kwargs): + def create_fast(self, vals_list, **kwargs): self = self.with_context(auditlog_disabled=True) rule_model = self.env['auditlog.rule'] - vals2 = dict(vals) - new_record = create_fast.origin(self, vals, **kwargs) - new_values = {new_record.id: vals2} + 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): + new_values.setdefault(new_record.id, vals) rule_model.sudo().create_logs( - self.env.uid, self._name, new_record.ids, + self.env.uid, self._name, new_records.ids, 'create', None, new_values, {'log_type': log_type}) - return new_record + return new_records return create_full if self.log_type == 'full' else create_fast diff --git a/auditlog/tests/test_auditlog.py b/auditlog/tests/test_auditlog.py index 996cf5fa1..9c490eb52 100644 --- a/auditlog/tests/test_auditlog.py +++ b/auditlog/tests/test_auditlog.py @@ -80,6 +80,33 @@ class AuditlogCommon(object): ('res_id', '=', testgroup4.id), ]).ensure_one()) + def test_LogCreation4(self): + """Fourth test, create several records at once (with create multi + feature starting from Odoo 12) and check that the same number of logs + has been generated. + """ + + self.groups_rule.subscribe() + + auditlog_log = self.env['auditlog.log'] + groups_vals = [ + {'name': 'testgroup1'}, + {'name': 'testgroup3'}, + {'name': 'testgroup2'}, + ] + groups = self.env['res.groups'].create(groups_vals) + # Ensure that the recordset returns is in the same order + # than list of vals + expected_names = ['testgroup1', 'testgroup3', 'testgroup2'] + self.assertEqual(groups.mapped('name'), expected_names) + + logs = auditlog_log.search([ + ('model_id', '=', self.groups_model_id), + ('method', '=', 'create'), + ('res_id', 'in', groups.ids), + ]) + self.assertEqual(len(logs), len(groups)) + class TestAuditlogFull(TransactionCase, AuditlogCommon):