diff --git a/auditlog/README.rst b/auditlog/README.rst index 18d6231b5..c9a727be2 100644 --- a/auditlog/README.rst +++ b/auditlog/README.rst @@ -7,7 +7,8 @@ Audit Log - Track user operations ================================= This module allows the administrator to log user operations performed on data -models such as ``create``, ``read``, ``write`` and ``delete``. +models such as ``create``, ``read``, ``write`` and ``delete``. Furthermore +it allows logging of custom methods. Usage ===== diff --git a/auditlog/models/__init__.py b/auditlog/models/__init__.py index ce5b89899..6fa4d9fc4 100644 --- a/auditlog/models/__init__.py +++ b/auditlog/models/__init__.py @@ -7,3 +7,4 @@ from . import http_session from . import http_request from . import log from . import autovacuum +from . import methods \ No newline at end of file diff --git a/auditlog/models/methods.py b/auditlog/models/methods.py new file mode 100644 index 000000000..bf08d1f52 --- /dev/null +++ b/auditlog/models/methods.py @@ -0,0 +1,15 @@ +# coding: utf-8 +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openerp import api, fields, models + + +class AuditlogMethods(models.Model): + _name = 'auditlog.methods' + _description = 'Auditlog custom methods' + _order = 'name' + + name = fields.Char(required=True) + message = fields.Char(required=True) + rule_id = fields.Many2one('auditlog.rule', readonly=True) + use_active_ids = fields.Boolean(default=False) + context_field_number = fields.Integer(default=0) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index bf5d05c62..a5026ecf4 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -3,6 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp import models, fields, api, modules, _, SUPERUSER_ID, sql_db +from openerp.exceptions import ValidationError FIELDS_BLACKLIST = [ 'id', 'create_uid', 'create_date', 'write_uid', 'write_date', @@ -71,6 +72,11 @@ class AuditlogRule(models.Model): u"Log Creates", default=True, help=(u"Select this if you want to keep track of creation on any " u"record of the model of this rule")) + log_custom = fields.Boolean( + u"Log Methods", + help=(u"Select this if you want to keep track of custom methods on " + u"any record of the model of this rule")) + custom_method_ids = fields.One2many('auditlog.methods', 'rule_id') log_type = fields.Selection( [('full', u"Full log"), ('fast', u"Fast log"), @@ -154,6 +160,29 @@ class AuditlogRule(models.Model): model_model._patch_method('unlink', rule._make_unlink()) setattr(model_model, check_attr, True) updated = True + # Check if custom methods are enabled and patch the different + # rule methods + if getattr(rule, 'log_custom'): + for custom_method in rule.custom_method_ids: + check_attr = 'auditlog_ruled_%s' % custom_method.name + + if not hasattr(model_model, custom_method.name): + raise ValidationError( + _('Method %s does not exist for model %s.' % ( + custom_method.name, + model_model + ))) + + if not hasattr(model_model, check_attr): + model_model._patch_method( + custom_method.name, + rule._make_custom( + custom_method.message, + custom_method.use_active_ids, + custom_method.context_field_number) + ) + setattr(model_model, check_attr, True) + updated = True return updated @api.multi @@ -167,6 +196,12 @@ class AuditlogRule(models.Model): getattr(model_model, method), 'origin'): model_model._revert_method(method) updated = True + if hasattr(rule, 'log_custom'): + for custom_method in rule.custom_method_ids: + method = custom_method.name + if hasattr(getattr(model_model, method), 'origin'): + model_model._revert_method(method) + updated = True if updated: modules.registry.RegistryManager.signal_registry_change( self.env.cr.dbname) @@ -349,6 +384,79 @@ class AuditlogRule(models.Model): return unlink_full if self.log_type == 'full' else unlink_fast + @api.multi + def _make_custom(self, message, use_active_ids, context_field_number): + """Instanciate a read method that log its calls.""" + self.ensure_one() + log_type = self.log_type + + def custom(self, *args, **kwargs): + result = custom.origin(self, *args, **kwargs) + + result2 = result + if not isinstance(result2, list): + result2 = [result] + # Old API + if args and isinstance(args[0], sql_db.Cursor): + cr, uid, ids = args[0], args[1], args[2] + if isinstance(ids, (int, long)): + ids = [ids] + + context = kwargs.get('context', {}) + + # Set specific context if it is defined by our rule + if not context and context_field_number: + if context_field_number - 1 < len(args): + context = args[context_field_number - 1] + + if context.get('auditlog_disabled'): + return result + + env = api.Environment(cr, uid, {'auditlog_disabled': True}) + rule_model = env['auditlog.rule'] + + # Overwrite the ids and object_model if it is required + # by the auditlog rule + object_model = self._name + if use_active_ids: + if context.get('active_model'): + if context.get('active_ids'): + object_model = context.get( + 'active_model', + object_model) + ids = context.get('active_ids', ids) + + rule_model.sudo().create_logs( + env.uid, object_model, ids, + message, None, None, {'log_type': log_type}) + # New API + else: + if self.env.context.get('auditlog_disabled'): + return result + self = self.with_context(auditlog_disabled=True) + + context = self.env.context + + # Overwrite the ids and object_model if it is required + # by the auditlog rule + ids = self.ids + object_model = self._name + if use_active_ids: + if context.get('active_model'): + if context.get('active_ids'): + object_model = context.get( + 'active_model', + object_model) + ids = context.get('active_ids', ids) + + rule_model = self.env['auditlog.rule'] + rule_model.sudo().create_logs( + self.env.uid, object_model, ids, + message, None, None, {'log_type': log_type}) + return result + + return custom + def create_logs(self, uid, res_model, res_ids, method, old_values=None, new_values=None, additional_log_values=None): diff --git a/auditlog/security/ir.model.access.csv b/auditlog/security/ir.model.access.csv index 32744cc21..baf95d61d 100644 --- a/auditlog/security/ir.model.access.csv +++ b/auditlog/security/ir.model.access.csv @@ -4,9 +4,11 @@ access_auditlog_log_user,auditlog_log_user,model_auditlog_log,base.group_user,0, access_auditlog_log_line_user,auditlog_log_line_user,model_auditlog_log_line,base.group_user,0,0,0,0 access_auditlog_http_session_user,auditlog_http_session_user,model_auditlog_http_session,base.group_user,0,0,0,0 access_auditlog_http_request_user,auditlog_http_request_user,model_auditlog_http_request,base.group_user,0,0,0,0 +access_auditlog_methods_user,auditlog_methods_user,model_auditlog_methods,base.group_user,0,0,0,0 access_auditlog_rule_manager,auditlog_rule_manager,model_auditlog_rule,base.group_erp_manager,1,1,1,1 access_auditlog_log_manager,auditlog_log_manager,model_auditlog_log,base.group_erp_manager,1,1,1,1 access_auditlog_log_line_manager,auditlog_log_line_manager,model_auditlog_log_line,base.group_erp_manager,1,1,1,1 access_auditlog_http_session_manager,auditlog_http_session_manager,model_auditlog_http_session,base.group_erp_manager,1,1,1,1 access_auditlog_http_request_manager,auditlog_http_request_manager,model_auditlog_http_request,base.group_erp_manager,1,1,1,1 +access_auditlog_methods_manager,auditlog_methods_manager,model_auditlog_methods,base.group_erp_manager,1,1,1,1 diff --git a/auditlog/tests/test_auditlog.py b/auditlog/tests/test_auditlog.py index 556af97c9..a3b0905f6 100644 --- a/auditlog/tests/test_auditlog.py +++ b/auditlog/tests/test_auditlog.py @@ -114,3 +114,59 @@ class TestAuditlogFast(TransactionCase, TestAuditlog): def tearDown(self): self.groups_rule.unlink() super(TestAuditlogFast, self).tearDown() + + +class TestMethods(TransactionCase): + def setUp(self): + super(TestMethods, self).setUp() + + # Clear all existing logging lines + existing_audit_logs = self.env['auditlog.log'].search([]) + existing_audit_logs.unlink() + + # Create account period to test + self.partner = self.env['res.partner'].create({ + 'name': 'Test User' + }) + + self.partner_model = self.env['ir.model'].search([ + ('model', '=', 'res.partner')]) + + # Setup rule for model "account.period.close" and method "data_save" + self.auditlog_rule = self.env['auditlog.rule'].create({ + 'name': 'res.partner', + 'model_id': self.partner_model.id, + 'log_type': 'fast', + 'log_read': False, + 'log_create': False, + 'log_write': False, + 'log_unlink': False, + 'log_custom': True, + 'custom_method_ids': [(0, 0, { + 'name': 'copy', + 'message': 'execute_copy', + })] + }) + + self.auditlog_rule.subscribe() + + def tearDown(self): + self.auditlog_rule.unsubscribe() + super(TestMethods, self).tearDown() + + def test_01_subscribe_unsubscribe(self): + """The test is subscribed by default, so let's try both""" + self.auditlog_rule.unsubscribe() + self.auditlog_rule.subscribe() + + def test_02_copy_res_partner_logging(self): + self.partner.copy() + + logs = self.env['auditlog.log'].search([ + ('res_id', '=', self.partner.id), + ('model_id', '=', self.partner_model.id), + ('method', '=', 'execute_copy') + ]) + + self.assertEqual(len(logs), 1) + diff --git a/auditlog/views/auditlog_view.xml b/auditlog/views/auditlog_view.xml index 0e5b5345b..92b54c3bb 100644 --- a/auditlog/views/auditlog_view.xml +++ b/auditlog/views/auditlog_view.xml @@ -38,6 +38,21 @@ + + +
+ You can only edit custom methods when the rule is unsubscribed. +
+ + + + + + + + +
+