Browse Source

[ADD] Module 'auditlog' - A substitute to the deprecated 'audittrail' module

pull/119/head
sebalix 10 years ago
parent
commit
ddee4461c8
  1. 1
      auditlog/AUTHORS.txt
  2. 24
      auditlog/__init__.py
  3. 47
      auditlog/__openerp__.py
  4. 301
      auditlog/i18n/fr.po
  5. 25
      auditlog/models/__init__.py
  6. 61
      auditlog/models/log.py
  7. 394
      auditlog/models/rule.py
  8. 8
      auditlog/security/ir.model.access.csv
  9. 191
      auditlog/views/auditlog_view.xml

1
auditlog/AUTHORS.txt

@ -0,0 +1 @@
Sebastien Alix <sebastien.alix@osiell.com>

24
auditlog/__init__.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 ABF OSIELL (<http://osiell.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import models
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

47
auditlog/__openerp__.py

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 ABF OSIELL (<http://osiell.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': "Audit Log",
'description': """
Track every user operation on all the objects of the system.
============================================================
The administrator can subscribe to rules for create, read, write and delete on
models and can check logs.
""",
'version': "1.0",
'author': "ABF OSIELL",
'website': "http://www.osiell.com",
'category': "Tools",
'depends': [
'base',
],
'data': [
'security/ir.model.access.csv',
'views/auditlog_view.xml',
],
'application': True,
'installable': True,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

301
auditlog/i18n/fr.po

@ -0,0 +1,301 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auditlog
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-22 09:51+0000\n"
"PO-Revision-Date: 2015-01-22 09:51+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: auditlog
#: field:auditlog.rule,action_id:0
msgid "Action"
msgstr "Action"
#. module: auditlog
#: model:ir.ui.menu,name:auditlog.menu_audit
msgid "Audit"
msgstr "Audit"
#. module: auditlog
#: model:ir.model,name:auditlog.model_auditlog_log
msgid "Auditlog - Log"
msgstr "Auditlog - Log"
#. module: auditlog
#: model:ir.model,name:auditlog.model_auditlog_log_line
msgid "Auditlog - Log details (fields updated)"
msgstr "Auditlog - Détails (champs modifiés)"
#. module: auditlog
#: model:ir.model,name:auditlog.model_auditlog_rule
msgid "Auditlog - Rule"
msgstr "Auditlog - Règle"
#. module: auditlog
#: field:auditlog.log,create_uid:0
#: field:auditlog.log.line,create_uid:0
#: field:auditlog.rule,create_uid:0
msgid "Created by"
msgstr ""
#. module: auditlog
#: field:auditlog.log,create_date:0
#: field:auditlog.log.line,create_date:0
#: field:auditlog.rule,create_date:0
msgid "Created on"
msgstr ""
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_search
#: field:auditlog.log,timestamp:0
msgid "Date"
msgstr "Date"
#. module: auditlog
#: field:auditlog.log.line,field_description:0
msgid "Description"
msgstr "Description"
#. module: auditlog
#: view:auditlog.rule:auditlog.view_auditlog_rule_search
#: selection:auditlog.rule,state:0
msgid "Draft"
msgstr "Brouillon"
#. module: auditlog
#: field:auditlog.log.line,field_id:0
msgid "Field"
msgstr "Champ"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_form
#: field:auditlog.log,line_ids:0
msgid "Fields updated"
msgstr "Champs modifiés"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_search
#: view:auditlog.rule:auditlog.view_auditlog_rule_search
msgid "Group By..."
msgstr "Grouper par..."
#. module: auditlog
#: field:auditlog.log,id:0
#: field:auditlog.log.line,id:0
#: field:auditlog.rule,id:0
msgid "ID"
msgstr "ID"
#. module: auditlog
#: field:auditlog.log,write_uid:0
#: field:auditlog.log.line,write_uid:0
#: field:auditlog.rule,write_uid:0
msgid "Last Updated by"
msgstr ""
#. module: auditlog
#: field:auditlog.log,write_date:0
#: field:auditlog.log.line,write_date:0
#: field:auditlog.rule,write_date:0
msgid "Last Updated on"
msgstr ""
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_form
#: field:auditlog.log.line,log_id:0
msgid "Log"
msgstr "Log"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_form
msgid "Log - Field updated"
msgstr "Log - Champs modifiés"
#. module: auditlog
#: field:auditlog.rule,log_create:0
msgid "Log Creates"
msgstr "Enregistrer les créations"
#. module: auditlog
#: field:auditlog.rule,log_unlink:0
msgid "Log Deletes"
msgstr "Enregistrer les suppressions"
#. module: auditlog
#: field:auditlog.rule,log_read:0
msgid "Log Reads"
msgstr "Enregistrer les lectures"
#. module: auditlog
#: field:auditlog.rule,log_write:0
msgid "Log Writes"
msgstr "Enregistrer les écritures"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_search
#: view:auditlog.log:auditlog.view_auditlog_log_tree
#: model:ir.actions.act_window,name:auditlog.action_auditlog_log_tree
#: model:ir.ui.menu,name:auditlog.menu_audit_logs
msgid "Logs"
msgstr "Journaux"
#. module: auditlog
#: field:auditlog.log,method:0
msgid "Method"
msgstr "Méthode"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_search
#: field:auditlog.log,model_id:0
#: field:auditlog.rule,model_id:0
msgid "Model"
msgstr "Modèle"
#. module: auditlog
#: field:auditlog.rule,name:0
msgid "Name"
msgstr "Nom"
#. module: auditlog
#: field:auditlog.log.line,new_value:0
msgid "New Value"
msgstr "Nouvelle valeur"
#. module: auditlog
#: field:auditlog.log.line,new_value_text:0
msgid "New value Text"
msgstr "Nouvelle valeur texte"
#. module: auditlog
#: field:auditlog.log.line,old_value:0
msgid "Old Value"
msgstr "Ancienne valeur"
#. module: auditlog
#: field:auditlog.log.line,old_value_text:0
msgid "Old value Text"
msgstr "Ancienne valeur texte"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_search
#: field:auditlog.log,res_id:0
msgid "Resource ID"
msgstr "ID de l'enregistrement"
#. module: auditlog
#: field:auditlog.log,name:0
msgid "Resource Name"
msgstr "Nom de l'enregistrement"
#. module: auditlog
#: view:auditlog.rule:auditlog.view_auditlog_rule_form
msgid "Rule"
msgstr "Règle"
#. module: auditlog
#: view:auditlog.rule:auditlog.view_auditlog_rule_search
#: view:auditlog.rule:auditlog.view_auditlog_rule_tree
#: model:ir.actions.act_window,name:auditlog.action_auditlog_rule_tree
#: model:ir.ui.menu,name:auditlog.menu_action_auditlog_rule_tree
msgid "Rules"
msgstr "Règles"
#. module: auditlog
#: help:auditlog.rule,model_id:0
msgid "Select model for which you want to generate log."
msgstr "Sélectionnez le modèle pour lequel vous voulez générer un historique."
#. module: auditlog
#: help:auditlog.rule,log_create:0
msgid "Select this if you want to keep track of creation on any record of the model of this rule"
msgstr ""
"Cochez cette case si vous voulez garder une trace de la création d'un nouvel "
"enregistrement concernant le modèle défini dans cette règle."
#. module: auditlog
#: help:auditlog.rule,log_unlink:0
msgid "Select this if you want to keep track of deletion on any record of the model of this rule"
msgstr ""
"Cochez cette case si vous voulez garder une trace des suppressions des "
"enregistrements du modèle défini dans cette règle."
#. module: auditlog
#: help:auditlog.rule,log_write:0
msgid "Select this if you want to keep track of modification on any record of the model of this rule"
msgstr ""
"Cochez cette case si vous voulez garder une trace des modifications sur "
"chaque enregistrement du modèle défini dans cette règle."
#. module: auditlog
#: help:auditlog.rule,log_read:0
msgid "Select this if you want to keep track of read/open on any record of the model of this rule"
msgstr ""
"Cochez cette case si vous voulez garder une trace de la lecture/ouverture de "
"chaque enregistrement du modèle défini dans cette règle."
#. module: auditlog
#: field:auditlog.rule,state:0
#: view:auditlog.rule:auditlog.view_auditlog_rule_search
msgid "State"
msgstr "État"
#. module: auditlog
#: view:auditlog.rule:auditlog.view_auditlog_rule_form
msgid "Subscribe"
msgstr "Abonner"
#. module: auditlog
#: view:auditlog.rule:auditlog.view_auditlog_rule_search
#: selection:auditlog.rule,state:0
msgid "Subscribed"
msgstr "Abonné"
#. module: auditlog
#: field:auditlog.log.line,field_name:0
msgid "Technical name"
msgstr "Nom technique"
#. module: auditlog
#: sql_constraint:auditlog.rule:0
msgid "There is already a rule defined on this model\n"
"You cannot define another: please edit the existing one."
msgstr ""
"Il existe déjà une règle définie sur ce modèle\n"
"Vous ne pouvez pas en définir une nouvelle, vous devez modifier celle "
"existante."
#. module: auditlog
#: view:auditlog.rule:auditlog.view_auditlog_rule_form
msgid "Unsubscribe"
msgstr "Désabonner"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_search
#: field:auditlog.log,user_id:0
msgid "User"
msgstr "Utilisateur"
#. module: auditlog
#: field:auditlog.rule,user_id:0
msgid "Users"
msgstr "Utilisateurs"
#. module: auditlog
#: view:auditlog.log:auditlog.view_auditlog_log_form
msgid "Values"
msgstr "Valeurs"
#. module: auditlog
#: code:addons/auditlog/models/rule.py:0
#, python-format
msgid "View logs"
msgstr "Consulter les journaux"

25
auditlog/models/__init__.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 ABF OSIELL (<http://osiell.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import rule
from . import log
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

61
auditlog/models/log.py

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 ABF OSIELL (<http://osiell.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from openerp import models, fields
class auditlog_log(models.Model):
_name = 'auditlog.log'
_description = "Auditlog - Log"
_order = "timestamp desc"
name = fields.Char("Resource Name", size=64)
model_id = fields.Many2one(
'ir.model', string=u"Model")
res_id = fields.Integer(u"Resource ID")
user_id = fields.Many2one(
'res.users', string=u"User")
method = fields.Char(u"Method", size=64)
timestamp = fields.Datetime(
u"Date", default=lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'))
line_ids = fields.One2many(
'auditlog.log.line', 'log_id', string=u"Fields updated")
class auditlog_log_line(models.Model):
_name = 'auditlog.log.line'
_description = "Auditlog - Log details (fields updated)"
field_id = fields.Many2one(
'ir.model.fields', string=u"Field", required=True)
log_id = fields.Many2one(
'auditlog.log', string=u"Log", ondelete='cascade')
#log = fields.Integer(u"Log ID")
old_value = fields.Text(u"Old Value")
new_value = fields.Text(u"New Value")
old_value_text = fields.Text(u"Old value Text")
new_value_text = fields.Text(u"New value Text")
field_name = fields.Char(u"Technical name", size=64)
field_description = fields.Char(u"Description", size=64)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

394
auditlog/models/rule.py

@ -0,0 +1,394 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 ABF OSIELL (<http://osiell.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields, api, modules
from openerp.tools.translate import _
FIELDS_BLACKLIST = [
'id', 'create_uid', 'create_date', 'write_uid', 'write_date',
'display_name', '__last_update',
]
# Used for performance, to avoid a dictionary instanciation when we need an
# empty dict to simplify algorithms
EMPTY_DICT = {}
class DictDiffer(object):
"""Calculate the difference between two dictionaries as:
(1) items added
(2) items removed
(3) keys same in both but changed values
(4) keys same in both and unchanged values
"""
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.intersect = self.set_current.intersection(self.set_past)
def added(self):
return self.set_current - self.intersect
def removed(self):
return self.set_past - self.intersect
def changed(self):
return set(o for o in self.intersect
if self.past_dict[o] != self.current_dict[o])
def unchanged(self):
return set(o for o in self.intersect
if self.past_dict[o] == self.current_dict[o])
class auditlog_rule(models.Model):
_name = 'auditlog.rule'
_description = "Auditlog - Rule"
name = fields.Char(u"Name", size=32, required=True)
model_id = fields.Many2one(
'ir.model', u"Model", required=True,
help=u"Select model for which you want to generate log.")
user_ids = fields.Many2many(
'res.users',
'audittail_rules_users',
'user_id', 'rule_id',
string=u"Users",
help=u"if User is not added then it will applicable for all users")
log_read = fields.Boolean(
u"Log Reads",
help=(u"Select this if you want to keep track of read/open on any "
u"record of the model of this rule"))
log_write = fields.Boolean(
u"Log Writes", default=True,
help=(u"Select this if you want to keep track of modification on any "
u"record of the model of this rule"))
log_unlink = fields.Boolean(
u"Log Deletes", default=True,
help=(u"Select this if you want to keep track of deletion on any "
u"record of the model of this rule"))
log_create = fields.Boolean(
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_action = fields.Boolean(
# "Log Action",
# help=("Select this if you want to keep track of actions on the "
# "model of this rule"))
#log_workflow = fields.Boolean(
# "Log Workflow",
# help=("Select this if you want to keep track of workflow on any "
# "record of the model of this rule"))
state = fields.Selection(
[('draft', "Draft"), ('subscribed', "Subscribed")],
string=u"State", required=True, default='draft')
action_id = fields.Many2one(
'ir.actions.act_window', string="Action")
_sql_constraints = [
('model_uniq', 'unique(model_id)',
("There is already a rule defined on this model\n"
"You cannot define another: please edit the existing one."))
]
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 ids is None:
ids = self.search(cr, 1, [('state', '=', 'subscribed')])
return self._patch_methods(cr, 1, ids)
@api.multi
def _patch_methods(self):
"""Patch ORM methods of models defined in rules to log their calls."""
updated = False
for rule in self:
if rule.state != 'subscribed':
continue
model_model = self.env[rule.model_id.model]
# CRUD
# -> create
check_attr = 'auditlog_ruled_create'
if getattr(rule, 'log_create') \
and not hasattr(model_model, check_attr):
model_model._patch_method('create', self._make_create())
setattr(model_model, check_attr, True)
updated = True
# -> read
check_attr = 'auditlog_ruled_read'
if getattr(rule, 'log_read') \
and not hasattr(model_model, check_attr):
model_model._patch_method('read', self._make_read())
setattr(model_model, check_attr, True)
updated = True
# -> write
check_attr = 'auditlog_ruled_write'
if getattr(rule, 'log_write') \
and not hasattr(model_model, check_attr):
model_model._patch_method('write', self._make_write())
setattr(model_model, check_attr, True)
updated = True
# -> unlink
check_attr = 'auditlog_ruled_unlink'
if getattr(rule, 'log_unlink') \
and not hasattr(model_model, check_attr):
model_model._patch_method('unlink', self._make_unlink())
setattr(model_model, check_attr, True)
updated = True
return updated
@api.multi
def _revert_methods(self):
"""Restore original ORM methods of models defined in rules."""
updated = False
for rule in self:
model_model = self.env[rule.model_id.model]
for method in ['create', 'read', 'write', 'unlink']:
if getattr(rule, 'log_%s' % method):
model_model._revert_method(method)
updated = True
if updated:
modules.registry.RegistryManager.signal_registry_change(
self.env.cr.dbname)
def create(self, cr, uid, vals, context=None):
"""Update the registry when a new rule is created."""
res_id = super(auditlog_rule, self).create(
cr, uid, vals, context=context)
if self._register_hook(cr, [res_id]):
modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res_id
def write(self, cr, uid, ids, vals, context=None):
"""Update the registry when existing rules are updated."""
if isinstance(ids, (int, long)):
ids = [ids]
super(auditlog_rule, self).write(cr, uid, ids, vals, context=context)
if self._register_hook(cr, ids):
modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return True
def _make_create(self):
"""Instanciate a create method that log its calls."""
@api.model
def create(self, vals, **kwargs):
rule_model = self.env['auditlog.rule']
new_record = create.origin(self, vals, **kwargs)
new_values = dict(
(d['id'], d) for d in new_record.sudo().read(
list(self._columns)))
rule_model.sudo().create_logs(
self.env.uid, self._name, new_record.ids,
'create', None, new_values)
return new_record
return create
def _make_read(self):
"""Instanciate a read method that log its calls."""
# FIXME: read() seems a bit tricky, improve to handle old/new api
#@api.v7
#def read(self, cr, user, ids, fields=None, context=None, load='_classic_read', **kwargs):
# print "LOG READ", fields, load, kwargs
# # avoid loops
# if self.env.context.get('auditlog_method_intercepted'):
# return read.origin(self, cr, user, ids, fields, context, load, **kwargs)
# # call original method with a modified context
# context = dict(self.env.context, auditlog_method_intercepted=True)
# result = read.origin(
# self.with_context(context),
# cr, user, ids, fields, context, load, **kwargs)
# print "RESULT", result
# return result
#@api.v8
#def read(self, fields=None, load='_classic_read', **kwargs):
# print "LOG READ", fields, load, kwargs
# # avoid loops
# if self.env.context.get('auditlog_method_intercepted'):
# return read.origin(self, fields, load, **kwargs)
# # call original method with a modified context
# context = dict(self.env.context, auditlog_method_intercepted=True)
# result = read.origin(
# self.with_context(context), fields, load, **kwargs)
# print "RESULT", result
# return result
def read(self, *args, **kwargs):
result = read.origin(self, *args, **kwargs)
return result
return read
def _make_write(self):
"""Instanciate a write method that log its calls."""
@api.multi
def write(self, vals, **kwargs):
rule_model = self.env['auditlog.rule']
old_values = dict(
(d['id'], d) for d in self.sudo().read(list(self._columns)))
result = write.origin(self, vals, **kwargs)
new_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,
'write', old_values, new_values)
return result
return write
def _make_unlink(self):
"""Instanciate an unlink method that log its calls."""
@api.multi
def unlink(self, **kwargs):
rule_model = self.env['auditlog.rule']
rule_model.sudo().create_logs(
self.env.uid, self._name, self.ids, 'unlink')
return unlink.origin(self, **kwargs)
return unlink
def create_logs(self, uid, res_model, res_ids, method,
old_values=None, new_values=None):
"""Create logs. `old_values` and `new_values` are dictionnaries, e.g:
{RES_ID: {'FIELD': VALUE, ...}}
"""
if old_values is None:
old_values = EMPTY_DICT
if new_values is None:
new_values = EMPTY_DICT
log_model = self.env['auditlog.log']
log_line_model = self.env['auditlog.log.line']
ir_model = self.env['ir.model']
ir_model_field = self.env['ir.model.fields']
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,
'res_id': res_id,
'method': method,
'user_id': uid,
}
log = log_model.create(vals)
diff = DictDiffer(
new_values.get(res_id, EMPTY_DICT),
old_values.get(res_id, EMPTY_DICT))
# 'write' case (old_values and new_values defined)
for fchanged in diff.changed():
if fchanged in FIELDS_BLACKLIST:
continue
field_ = ir_model_field.search(
[('model_id', '=', model.id), ('name', '=', fchanged)])
log_vals = {
'field_id': field_.id,
'field_name': field_.name,
'field_description': field_.field_description,
'log_id': log.id,
'old_value': old_values[res_id][fchanged],
'old_value_text': old_values[res_id][fchanged],
'new_value': new_values[res_id][fchanged],
'new_value_text': new_values[res_id][fchanged],
}
# for *2many fields, log the name_get
if field_.relation and '2many' in field_.ttype:
old_value_text = self.env[field_.relation].browse(
log_vals['old_value']).name_get()
log_vals['old_value_text'] = old_value_text
new_value_text = self.env[field_.relation].browse(
log_vals['new_value']).name_get()
log_vals['new_value_text'] = new_value_text
log_line_model.create(log_vals)
# 'create' case (old_values => EMPTY_DICT)
for fchanged in diff.added():
if fchanged in FIELDS_BLACKLIST:
continue
field_ = ir_model_field.search(
[('model_id', '=', model.id), ('name', '=', fchanged)])
log_vals = {
'field_id': field_.id,
'field_name': field_.name,
'field_description': field_.field_description,
'log_id': log.id,
'old_value': False,
'old_value_text': False,
'new_value': new_values[res_id][fchanged],
'new_value_text': new_values[res_id][fchanged],
}
if field_.relation and '2many' in field_.ttype:
new_value_text = self.env[field_.relation].browse(
log_vals['new_value']).name_get()
log_vals['new_value_text'] = new_value_text
log_line_model.create(log_vals)
@api.multi
def subscribe(self):
"""Subscribe Rule for auditing changes on model and apply shortcut
to view logs on that model.
"""
act_window_model = self.env['ir.actions.act_window']
model_data_model = self.env['ir.model.data']
for rule in self:
# Create a shortcut to view logs
domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % (
rule.model_id.id)
vals = {
'name': _(u"View logs"),
'res_model': 'auditlog.log',
'src_model': rule.model_id.model,
'domain': domain,
}
act_window = act_window_model.sudo().create(vals)
rule.write({'state': 'subscribed', 'action_id': act_window.id})
keyword = 'client_action_relate'
value = 'ir.actions.act_window,%s' % act_window.id
model_data_model.sudo().ir_set(
'action', keyword, 'View_log_' + rule.model_id.model,
[rule.model_id.model], value, replace=True,
isobject=True, xml_id=False)
return True
@api.multi
def unsubscribe(self):
"""Unsubscribe Auditing Rule on model."""
act_window_model = self.env['ir.actions.act_window']
ir_values_model = self.env['ir.values']
value = ''
self._revert_methods()
for rule in self:
# Revert patched methods
# Remove the shortcut to view logs
act_window = act_window_model.search(
[('name', '=', 'View Log'),
('res_model', '=', 'auditlog.log'),
('src_model', '=', rule.model_id.model)])
if act_window:
value = 'ir.actions.act_window,%s' % act_window.id
act_window.unlink()
if value:
ir_value = ir_values_model.search(
[('model', '=', rule.model_id.model), ('value', '=', value)])
if ir_value:
ir_value.unlink()
self.write({'state': 'draft'})
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

8
auditlog/security/ir.model.access.csv

@ -0,0 +1,8 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_auditlog_rule_user,auditlog_rule_user,model_auditlog_rule,base.group_user,0,0,0,0
access_auditlog_log_user,auditlog_log_user,model_auditlog_log,base.group_user,0,0,0,0
access_auditlog_log_line_user,auditlog_log_line_user,model_auditlog_log_line,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

191
auditlog/views/auditlog_view.xml

@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem id="menu_audit" name="Audit"
parent="base.menu_reporting" sequence="50"
groups="base.group_system"/>
<!-- auditlog.rule -->
<record model="ir.ui.view" id="view_auditlog_rule_form">
<field name="name">auditlog.rule.form</field>
<field name="model">auditlog.rule</field>
<field name="arch" type="xml">
<form string="Rule">
<header>
<button string="Subscribe" name="subscribe"
type="object" states="draft" class="oe_highlight"/>
<button string="Unsubscribe" name="unsubscribe"
type="object" states="subscribed"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group string="Rule">
<group colspan="1">
<field name="name" required="1"/>
<field name="model_id"/>
<field name="action_id" readonly="1" groups="base.group_no_one"/>
</group>
<group colspan="1">
<field name="log_read" invisible="1"/>
<field name="log_write"/>
<field name="log_unlink"/>
<field name="log_create"/>
<!--<field name="log_action"/>-->
<!--<field name="log_workflow"/>-->
</group>
</group>
<!--
<group string="Users">
<field name="user_ids" nolabel="1"/>
</group>
-->
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_auditlog_rule_tree">
<field name="name">auditlog.rule.tree</field>
<field name="model">auditlog.rule</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';black:state == 'subscribed'" string="Rules">
<field name="name"/>
<field name="model_id"/>
<field name="log_read"/>
<field name="log_write"/>
<field name="log_unlink"/>
<field name="log_create"/>
<!--<field name="log_action"/>-->
<!--<field name="log_workflow"/>-->
<field name="state"/>
</tree>
</field>
</record>
<record id="view_auditlog_rule_search" model="ir.ui.view">
<field name="name">auditlog.rule.search</field>
<field name="model">auditlog.rule</field>
<field name="arch" type="xml">
<search string="Rules">
<field name="name"/>
<filter domain="[('state','=','draft')]" string="Draft"/>
<filter domain="[('state','=','subscribed')]" string="Subscribed"/>
<field name="model_id"/>
<group expand="0" string="Group By...">
<filter string="State"
domain="[]" context="{'group_by':'state'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_auditlog_rule_tree">
<field name="name">Rules</field>
<field name="res_model">auditlog.rule</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_draft': 1}</field>
<field name="search_view_id" ref="view_auditlog_rule_search"/>
</record>
<menuitem id="menu_action_auditlog_rule_tree" parent="menu_audit" action="action_auditlog_rule_tree"/>
<!-- auditlog.log -->
<record model="ir.ui.view" id="view_auditlog_log_form">
<field name="name">auditlog.log.form</field>
<field name="model">auditlog.log</field>
<field name="arch" type="xml">
<form string="Log">
<sheet>
<group string="Log">
<group colspan="1">
<field name="timestamp" required="1" readonly="1"/>
<field name="user_id" readonly="1"/>
<field name="method" readonly="1"/>
</group>
<group colspan="1">
<field name="model_id" readonly="1"/>
<field name="res_id" readonly="1"/>
<field name="name" readonly="1"/>
</group>
</group>
<group string="Fields updated">
<field name="line_ids" readonly="1" nolabel="1">
<form string="Log - Field updated">
<group>
<field name="field_id" readonly="1"/>
</group>
<group string="Values" col="4">
<field name="old_value" readonly="1"/>
<field name="new_value" readonly="1"/>
<field name="old_value_text" readonly="1"/>
<field name="new_value_text" readonly="1"/>
</group>
</form>
<tree>
<field name="field_description"/>
<field name="field_name"/>
<!--<field name="old_value"/>-->
<field name="old_value_text"/>
<!--<field name="new_value"/>-->
<field name="new_value_text"/>
</tree>
</field>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_auditlog_log_tree">
<field name="name">auditlog.log.tree</field>
<field name="model">auditlog.log</field>
<field name="arch" type="xml">
<tree string="Logs" create="false">
<field name="timestamp"/>
<field name="name"/>
<field name="model_id"/>
<field name="res_id"/>
<field name="method"/>
<field name="user_id"/>
</tree>
</field>
</record>
<record id="view_auditlog_log_search" model="ir.ui.view">
<field name="name">auditlog.log.search</field>
<field name="model">auditlog.log</field>
<field name="arch" type="xml">
<search string="Logs">
<field name="name"/>
<field name="model_id"/>
<field name="res_id"/>
<field name="user_id"/>
<group expand="0" string="Group By...">
<filter string="User" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Model" domain="[]" context="{'group_by':'model_id'}"/>
<filter string="Resource ID" domain="[]" context="{'group_by':'res_id'}"/>
<filter string="Date" domain="[]" context="{'group_by':'timestamp'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_auditlog_log_tree">
<field name="name">Logs</field>
<field name="res_model">auditlog.log</field>
<field name="view_type">form</field>
<field name="search_view_id" ref="view_auditlog_log_search"/>
</record>
<menuitem id="menu_audit_logs" name="Logs"
parent="menu_audit" action="action_auditlog_log_tree"/>
</data>
</openerp>
Loading…
Cancel
Save