From 0cdb74c82e73480bec9d189765b44a786efef1c7 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 12:25:05 +0100 Subject: [PATCH 1/8] [IMP] use new import of _, SUPERUSER_ID --- auditlog/models/rule.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 56c8ebc5c..ecab76811 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -19,8 +19,7 @@ # ############################################################################## -from openerp import models, fields, api, modules -from openerp.tools.translate import _ +from openerp import models, fields, api, modules, _, SUPERUSER_ID FIELDS_BLACKLIST = [ 'id', 'create_uid', 'create_date', 'write_uid', 'write_date', @@ -113,8 +112,8 @@ class auditlog_rule(models.Model): """Get all rules and apply them to log method calls.""" super(auditlog_rule, self)._register_hook(cr) if ids is None: - ids = self.search(cr, 1, [('state', '=', 'subscribed')]) - return self._patch_methods(cr, 1, ids) + ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'subscribed')]) + return self._patch_methods(cr, SUPERUSER_ID, ids) @api.multi def _patch_methods(self): From df07d3130adddef578ed04df92dd0ff2bd835953 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 13:04:49 +0100 Subject: [PATCH 2/8] [ADD] allow overriding modules to pass additional log values --- auditlog/models/rule.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index ecab76811..4c897ecfe 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -274,7 +274,8 @@ class auditlog_rule(models.Model): return unlink def create_logs(self, uid, res_model, res_ids, method, - old_values=None, new_values=None): + old_values=None, new_values=None, + additional_log_values=None): """Create logs. `old_values` and `new_values` are dictionnaries, e.g: {RES_ID: {'FIELD': VALUE, ...}} """ @@ -295,6 +296,7 @@ class auditlog_rule(models.Model): 'method': method, 'user_id': uid, } + vals.update(additional_log_values) log = log_model.create(vals) diff = DictDiffer( new_values.get(res_id, EMPTY_DICT), From df6ced6fe0253f5a2e8956d8a767995bb1cff907 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 13:27:07 +0100 Subject: [PATCH 3/8] [IMP] cache model and field ids --- auditlog/models/rule.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 4c897ecfe..04be8f9e0 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -119,12 +119,15 @@ class auditlog_rule(models.Model): def _patch_methods(self): """Patch ORM methods of models defined in rules to log their calls.""" updated = False + self.pool._auditlog_field_cache = {} + model_cache = self.pool._auditlog_model_cache = {} for rule in self: if rule.state != 'subscribed': continue if not self.pool.get(rule.model_id.model): # ignore rules for models not loadable currently continue + model_cache[rule.model_id.model] = rule.model_id model_model = self.env[rule.model_id.model] # CRUD # -> create @@ -285,13 +288,12 @@ class auditlog_rule(models.Model): new_values = EMPTY_DICT log_model = self.env['auditlog.log'] ir_model = self.env['ir.model'] - model = ir_model.search([('model', '=', res_model)]) for res_id in res_ids: model_model = self.env[res_model] res_name = model_model.browse(res_id).name_get() vals = { 'name': res_name and res_name[0] and res_name[0][1] or False, - 'model_id': model.id, + 'model_id': self.pool._auditlog_model_cache[res_model].id, 'res_id': res_id, 'method': method, 'user_id': uid, @@ -305,17 +307,26 @@ class auditlog_rule(models.Model): log, diff.changed(), old_values, new_values) self._create_log_line_on_create(log, diff.added(), new_values) + def _get_field(self, model, field_name): + cache = self.pool._auditlog_field_cache + if field_name not in cache.get(model.model, {}): + cache.setdefault(model.model, {}) + cache[model.model][field_name] = self.env['ir.model.fields']\ + .search( + [ + ('model_id', '=', model.id), + ('name', '=', field_name), + ]) + return cache[model.model][field_name] + def _create_log_line_on_write( self, log, fields_list, old_values, new_values): """Log field updated on a 'write' operation.""" log_line_model = self.env['auditlog.log.line'] - ir_model_field = self.env['ir.model.fields'] for field_name in fields_list: if field_name in FIELDS_BLACKLIST: continue - field = ir_model_field.search( - [('model_id', '=', log.model_id.id), - ('name', '=', field_name)]) + field = self._get_field(log.model_id, field_name) log_vals = self._prepare_log_line_vals_on_write( log, field, old_values, new_values) log_line_model.create(log_vals) @@ -349,13 +360,10 @@ class auditlog_rule(models.Model): self, log, fields_list, new_values): """Log field filled on a 'create' operation.""" log_line_model = self.env['auditlog.log.line'] - ir_model_field = self.env['ir.model.fields'] for field_name in fields_list: if field_name in FIELDS_BLACKLIST: continue - field = ir_model_field.search( - [('model_id', '=', log.model_id.id), - ('name', '=', field_name)]) + field = self._get_field(log.model_id, field_name) log_vals = self._prepare_log_line_vals_on_create( log, field, new_values) log_line_model.create(log_vals) From d11d9df02d95e6988711ec81bab8b7403c969377 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 13:38:26 +0100 Subject: [PATCH 4/8] [IMP] don't make a temporary copy of dict keys --- auditlog/models/rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 04be8f9e0..ce00d629d 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -39,8 +39,8 @@ class DictDiffer(object): """ def __init__(self, current_dict, past_dict): self.current_dict, self.past_dict = current_dict, past_dict - self.set_current = set(current_dict.keys()) - self.set_past = set(past_dict.keys()) + self.set_current = set(current_dict) + self.set_past = set(past_dict) self.intersect = self.set_current.intersection(self.set_past) def added(self): From 719714340bab8d226e1b4dd82deed9052a5c914e Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 15:38:29 +0100 Subject: [PATCH 5/8] [FIX] don't reset caches in register_hook --- auditlog/models/rule.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index ce00d629d..bcc75b3ba 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -111,6 +111,10 @@ class auditlog_rule(models.Model): def _register_hook(self, cr, ids=None): """Get all rules and apply them to log method calls.""" super(auditlog_rule, self)._register_hook(cr) + if not hasattr(self.pool, '_auditlog_field_cache'): + self.pool._auditlog_field_cache = {} + if not hasattr(self.pool, '_auditlog_model_cache'): + self.pool._auditlog_model_cache = {} if ids is None: ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'subscribed')]) return self._patch_methods(cr, SUPERUSER_ID, ids) @@ -119,8 +123,7 @@ class auditlog_rule(models.Model): def _patch_methods(self): """Patch ORM methods of models defined in rules to log their calls.""" updated = False - self.pool._auditlog_field_cache = {} - model_cache = self.pool._auditlog_model_cache = {} + model_cache = self.pool._auditlog_model_cache for rule in self: if rule.state != 'subscribed': continue From 153bb5c3e6ddc2f1c9d8311dfdbfe95a70546d58 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 15:39:14 +0100 Subject: [PATCH 6/8] [ADD] pass old_values to create_log when deleting a record --- auditlog/models/rule.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index bcc75b3ba..cec0e49b9 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -274,8 +274,10 @@ class auditlog_rule(models.Model): @api.multi def unlink(self, **kwargs): rule_model = self.env['auditlog.rule'] + old_values = dict( + (d['id'], d) for d in self.sudo().read(list(self._columns))) rule_model.sudo().create_logs( - self.env.uid, self._name, self.ids, 'unlink') + self.env.uid, self._name, self.ids, 'unlink', old_values) return unlink.origin(self, **kwargs) return unlink From f754bf04b58fa36b96a54a831eadcf0457a18868 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 15:40:15 +0100 Subject: [PATCH 7/8] [FIX] cope with no additional log values --- auditlog/models/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index cec0e49b9..38a635ba1 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -303,7 +303,7 @@ class auditlog_rule(models.Model): 'method': method, 'user_id': uid, } - vals.update(additional_log_values) + vals.update(additional_log_values or {}) log = log_model.create(vals) diff = DictDiffer( new_values.get(res_id, EMPTY_DICT), From 04944c61cd76c76728e74478429d0d3ccfacc6bb Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Jan 2015 16:20:29 +0100 Subject: [PATCH 8/8] [ADD] tests --- auditlog/tests/__init__.py | 21 +++++++++++++ auditlog/tests/test_auditlog.py | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 auditlog/tests/__init__.py create mode 100644 auditlog/tests/test_auditlog.py diff --git a/auditlog/tests/__init__.py b/auditlog/tests/__init__.py new file mode 100644 index 000000000..a688b88a7 --- /dev/null +++ b/auditlog/tests/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import test_auditlog diff --git a/auditlog/tests/test_auditlog.py b/auditlog/tests/test_auditlog.py new file mode 100644 index 000000000..bc63dc1a2 --- /dev/null +++ b/auditlog/tests/test_auditlog.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.tests.common import TransactionCase + + +class TestAuditlog(TransactionCase): + def test_LogCreation(self): + auditlog_log = self.env['auditlog.log'] + user_model_id = self.env.ref('base.model_res_users').id + self.env['auditlog.rule'].create({ + 'name': 'testrule for users', + 'model_id': user_model_id, + 'log_create': True, + 'log_write': True, + 'log_unlink': True, + 'state': 'subscribed', + }) + user = self.env['res.users'].create({ + 'login': 'testuser', + 'name': 'testuser', + }) + self.assertTrue(auditlog_log.search([ + ('model_id', '=', user_model_id), + ('method', '=', 'create'), + ('res_id', '=', user.id), + ])) + user.write({'name': 'Test User'}) + self.assertTrue(auditlog_log.search([ + ('model_id', '=', user_model_id), + ('method', '=', 'write'), + ('res_id', '=', user.id), + ])) + user.unlink() + self.assertTrue(auditlog_log.search([ + ('model_id', '=', user_model_id), + ('method', '=', 'unlink'), + ('res_id', '=', user.id), + ]))