Add option to Eliminate user and fields in audit logs

pull/2466/head
Atchuthan Ubendran 2022-03-22 17:43:47 +05:30 committed by Stefan Rijnhart
parent 9018e8414f
commit 470d8def8a
3 changed files with 269 additions and 17 deletions

View File

@ -134,6 +134,19 @@ class AuditlogRule(models.Model):
capture_record = fields.Boolean(
help="Select this if you want to keep track of Unlink Record",
)
users_to_exclude_ids = fields.Many2many(
"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 = [
(
@ -256,6 +269,7 @@ class AuditlogRule(models.Model):
"""Instanciate a create method that log its calls."""
self.ensure_one()
log_type = self.log_type
users_to_exclude = self.mapped("users_to_exclude_ids")
@api.model_create_multi
@api.returns("self", lambda value: value.id)
@ -277,6 +291,8 @@ class AuditlogRule(models.Model):
new_values[new_record.id][fname] = field.convert_to_read(
new_record[fname], new_record
)
if self.env.user in users_to_exclude:
return new_records
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -298,6 +314,8 @@ class AuditlogRule(models.Model):
new_values = {}
for vals, new_record in zip(vals_list2, new_records):
new_values.setdefault(new_record.id, vals)
if self.env.user in users_to_exclude:
return new_records
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -315,6 +333,7 @@ class AuditlogRule(models.Model):
"""Instanciate a read method that log its calls."""
self.ensure_one()
log_type = self.log_type
users_to_exclude = self.mapped("users_to_exclude_ids")
def read(self, fields=None, load="_classic_read", **kwargs):
result = read.origin(self, fields, load, **kwargs)
@ -334,6 +353,8 @@ class AuditlogRule(models.Model):
return result
self = self.with_context(auditlog_disabled=True)
rule_model = self.env["auditlog.rule"]
if self.env.user in users_to_exclude:
return result
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -351,6 +372,7 @@ class AuditlogRule(models.Model):
"""Instanciate a write method that log its calls."""
self.ensure_one()
log_type = self.log_type
users_to_exclude = self.mapped("users_to_exclude_ids")
def write_full(self, vals, **kwargs):
self = self.with_context(auditlog_disabled=True)
@ -369,6 +391,8 @@ class AuditlogRule(models.Model):
.with_context(prefetch_fields=False)
.read(fields_list)
}
if self.env.user in users_to_exclude:
return result
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -391,6 +415,8 @@ class AuditlogRule(models.Model):
old_values = {id_: old_vals2 for id_ in self.ids}
new_values = {id_: vals2 for id_ in self.ids}
result = write_fast.origin(self, vals, **kwargs)
if self.env.user in users_to_exclude:
return result
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -408,6 +434,7 @@ class AuditlogRule(models.Model):
"""Instanciate an unlink method that log its calls."""
self.ensure_one()
log_type = self.log_type
users_to_exclude = self.mapped("users_to_exclude_ids")
def unlink_full(self, **kwargs):
self = self.with_context(auditlog_disabled=True)
@ -419,6 +446,8 @@ class AuditlogRule(models.Model):
.with_context(prefetch_fields=False)
.read(fields_list)
}
if self.env.user in users_to_exclude:
return unlink_full.origin(self, **kwargs)
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -433,6 +462,8 @@ class AuditlogRule(models.Model):
def unlink_fast(self, **kwargs):
self = self.with_context(auditlog_disabled=True)
rule_model = self.env["auditlog.rule"]
if self.env.user in users_to_exclude:
return unlink_fast.origin(self, **kwargs)
rule_model.sudo().create_logs(
self.env.uid,
self._name,
@ -466,17 +497,16 @@ class AuditlogRule(models.Model):
log_model = self.env["auditlog.log"]
http_request_model = self.env["auditlog.http.request"]
http_session_model = self.env["auditlog.http.session"]
model_model = self.env[res_model]
model_id = self.pool._auditlog_model_cache[res_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:
model_model = self.env[res_model]
name = model_model.browse(res_id).name_get()
model_id = self.pool._auditlog_model_cache[res_model]
auditlog_rule = self.env["auditlog.rule"].search(
[("model_id", "=", model_id)]
)
res_name = name and name[0] and name[0][1]
vals = {
"name": res_name,
"model_id": self.pool._auditlog_model_cache[res_model],
"model_id": model_id,
"res_id": res_id,
"method": method,
"user_id": uid,
@ -489,18 +519,26 @@ class AuditlogRule(models.Model):
new_values.get(res_id, EMPTY_DICT), old_values.get(res_id, EMPTY_DICT)
)
if method == "create":
self._create_log_line_on_create(log, diff.added(), new_values)
self._create_log_line_on_create(
log, diff.added(), new_values, fields_to_exclude
)
elif method == "read":
self._create_log_line_on_read(
log, list(old_values.get(res_id, EMPTY_DICT).keys()), old_values
log,
list(old_values.get(res_id, EMPTY_DICT).keys()),
old_values,
fields_to_exclude,
)
elif method == "write":
self._create_log_line_on_write(
log, diff.changed(), old_values, new_values
log, diff.changed(), old_values, new_values, fields_to_exclude
)
elif method == "unlink" and auditlog_rule.capture_record:
self._create_log_line_on_read(
log, list(old_values.get(res_id, EMPTY_DICT).keys()), old_values
log,
list(old_values.get(res_id, EMPTY_DICT).keys()),
old_values,
fields_to_exclude,
)
def _get_field(self, model, field_name):
@ -525,11 +563,14 @@ class AuditlogRule(models.Model):
cache[model.model][field_name] = field_data
return cache[model.model][field_name]
def _create_log_line_on_read(self, log, fields_list, read_values):
def _create_log_line_on_read(
self, log, fields_list, read_values, fields_to_exclude
):
"""Log field filled on a 'read' operation."""
log_line_model = self.env["auditlog.log.line"]
fields_to_exclude = fields_to_exclude + FIELDS_BLACKLIST
for field_name in fields_list:
if field_name in FIELDS_BLACKLIST:
if field_name in fields_to_exclude:
continue
field = self._get_field(log.model_id, field_name)
# not all fields have an ir.models.field entry (ie. related fields)
@ -556,11 +597,14 @@ class AuditlogRule(models.Model):
vals["old_value_text"] = old_value_text
return vals
def _create_log_line_on_write(self, log, fields_list, old_values, new_values):
def _create_log_line_on_write(
self, log, fields_list, old_values, new_values, fields_to_exclude
):
"""Log field updated on a 'write' operation."""
log_line_model = self.env["auditlog.log.line"]
fields_to_exclude = fields_to_exclude + FIELDS_BLACKLIST
for field_name in fields_list:
if field_name in FIELDS_BLACKLIST:
if field_name in fields_to_exclude:
continue
field = self._get_field(log.model_id, field_name)
# not all fields have an ir.models.field entry (ie. related fields)
@ -605,11 +649,14 @@ class AuditlogRule(models.Model):
vals["new_value_text"] = new_value_text
return vals
def _create_log_line_on_create(self, log, fields_list, new_values):
def _create_log_line_on_create(
self, log, fields_list, new_values, fields_to_exclude
):
"""Log field filled on a 'create' operation."""
log_line_model = self.env["auditlog.log.line"]
fields_to_exclude = fields_to_exclude + FIELDS_BLACKLIST
for field_name in fields_list:
if field_name in FIELDS_BLACKLIST:
if field_name in fields_to_exclude:
continue
field = self._get_field(log.model_id, field_name)
# not all fields have an ir.models.field entry (ie. related fields)

View File

@ -3,7 +3,7 @@
# © 2021 Stefan Rijnhart <stefan@opener.amsterdam>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.modules.migration import load_script
from odoo.tests.common import TransactionCase
from odoo.tests.common import Form, TransactionCase
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
@ -379,3 +379,200 @@ class TestAuditlogFullCaptureRecord(TransactionCase, AuditlogCommon):
def tearDown(self):
self.groups_rule.unlink()
super(TestAuditlogFullCaptureRecord, self).tearDown()
class AuditLogRuleTestForUserFields(TransactionCase):
@classmethod
def setUpClass(cls):
super(AuditLogRuleTestForUserFields, cls).setUpClass()
# get Contact model id
cls.contact_model_id = (
cls.env["ir.model"].search([("model", "=", "res.partner")]).id
)
# get phone field id
cls.fields_to_exclude_ids = (
cls.env["ir.model.fields"]
.search([("model", "=", "res.partner"), ("name", "=", "phone")])
.id
)
# get user id
cls.user = (
cls.env["res.users"]
.with_context(no_reset_password=True, tracking_disable=True)
.create(
{
"name": "Test User",
"login": "testuser",
}
)
)
cls.user_2 = (
cls.env["res.users"]
.with_context(no_reset_password=True, tracking_disable=True)
.create(
{
"name": "Test User2",
"login": "testuser2",
}
)
)
cls.users_to_exclude_ids = cls.user.id
# creating auditlog.rule
cls.auditlog_rule = (
cls.env["auditlog.rule"]
.with_context(tracking_disable=True)
.create(
{
"name": "testrule 01",
"model_id": cls.contact_model_id,
"log_read": True,
"log_create": True,
"log_write": True,
"log_unlink": True,
"log_type": "full",
"capture_record": True,
}
)
)
# Updating phone in fields_to_exclude_ids
cls.auditlog_rule.fields_to_exclude_ids = [[4, cls.fields_to_exclude_ids]]
# Updating users_to_exclude_ids
cls.auditlog_rule.users_to_exclude_ids = [[4, cls.users_to_exclude_ids]]
# Subscribe auditlog.rule
cls.auditlog_rule.subscribe()
cls.auditlog_log = cls.env["auditlog.log"]
# Creating new res.partner
cls.testpartner1 = (
cls.env["res.partner"]
.with_context(tracking_disable=True)
.create(
{
"name": "testpartner1",
"phone": "123",
}
)
)
# Creating new res.partner from excluded user
cls.testpartner2 = (
cls.env["res.partner"]
.with_context(tracking_disable=True)
.with_user(cls.user.id)
.create(
{
"name": "testpartner2",
}
)
)
def test_01_AuditlogFull_field_exclude_create_log(self):
# Checking log is created for testpartner1
create_log_record = self.auditlog_log.search(
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "create"),
("res_id", "=", self.testpartner1.id),
]
).ensure_one()
self.assertTrue(create_log_record)
field_names = create_log_record.line_ids.mapped("field_name")
# Checking log lines not created for phone
self.assertTrue("phone" not in field_names)
# Removing created log record
create_log_record.unlink()
def test_02_AuditlogFull_field_exclude_write_log(self):
# Checking fields_to_exclude_ids
self.testpartner1.with_context(tracking_disable=True).write(
{
"phone": "1234567890",
}
)
# Checking log is created for testpartner1
write_log_record = self.auditlog_log.search(
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "write"),
("res_id", "=", self.testpartner1.id),
]
).ensure_one()
self.assertTrue(write_log_record)
field_names = write_log_record.line_ids.mapped("field_name")
# Checking log lines not created for phone
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
)
)
partner_form.email = "vendor@mail.com"
testpartner1 = partner_form.save()
# Checking write log not created
with self.assertRaises(ValueError):
self.auditlog_log.search(
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "write"),
("res_id", "=", testpartner1.id),
("user_id", "=", self.user.id),
]
).ensure_one()
def test_04_AuditlogFull_user_exclude_create_log(self):
# Checking create log not created for testpartner2
with self.assertRaises(ValueError):
self.auditlog_log.search(
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "create"),
("res_id", "=", self.testpartner2.id),
]
).ensure_one()
def test_05_AuditlogFull_user_exclude_unlink_log(self):
# Removing testpartner2 from excluded user
self.testpartner2.with_user(self.user).unlink()
# Checking delete log not created for testpartner2
with self.assertRaises(ValueError):
self.auditlog_log.search(
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "unlink"),
("res_id", "=", self.testpartner2.id),
]
).ensure_one()
def test_06_AuditlogFull_unlink_log(self):
# Removing testpartner1 with user_2
self.testpartner1.with_user(self.user_2).unlink()
delete_log_record = self.auditlog_log.search(
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "unlink"),
("res_id", "=", self.testpartner1.id),
("user_id", "=", self.user_2.id),
]
).ensure_one()
# Checking log lines are created
self.assertTrue(delete_log_record)
# Removing auditlog_rule
self.auditlog_rule.unlink()

View File

@ -44,6 +44,14 @@
name="capture_record"
attrs="{'invisible':['|' ,('log_type','!=', 'full'), ('log_unlink','!=', True)]}"
/>
<field
name="users_to_exclude_ids"
widget="many2many_tags"
/>
<field
name="fields_to_exclude_ids"
widget="many2many_tags"
/>
</group>
<group colspan="1">
<field name="log_read" />