[MIG] auditlog: Migration to 17.0

pull/2755/head
Raf Ven 2024-02-05 08:36:22 +01:00
parent e5c22848ff
commit dcab07e824
7 changed files with 102 additions and 92 deletions

View File

@ -3,7 +3,7 @@
{
"name": "Audit Log",
"version": "16.0.2.0.2",
"version": "17.0.1.0.0",
"author": "ABF OSIELL, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/server-tools",

View File

@ -61,4 +61,4 @@ class AuditlogLogLineView(models.Model):
@property
def _table_query(self):
return "SELECT %s FROM %s" % (self._select_query(), self._from_query())
return f"SELECT {self._select_query()} FROM {self._from_query()}"

View File

@ -31,7 +31,6 @@ class AuditlogAutovacuum(models.TransientModel):
order="create_date asc",
)
nb_records = len(records)
with self.env.norecompute():
records.unlink()
_logger.info("AUTOVACUUM - %s '%s' records deleted", nb_records, data_model)
return True

View File

@ -51,12 +51,11 @@ class AuditlogRule(models.Model):
_name = "auditlog.rule"
_description = "Auditlog - Rule"
name = fields.Char(required=True, states={"subscribed": [("readonly", True)]})
name = fields.Char(required=True)
model_id = fields.Many2one(
"ir.model",
"Model",
help="Select model for which you want to generate log.",
states={"subscribed": [("readonly", True)]},
ondelete="set null",
index=True,
)
@ -68,8 +67,7 @@ class AuditlogRule(models.Model):
"user_id",
"rule_id",
string="Users",
help="if User is not added then it will applicable for all users",
states={"subscribed": [("readonly", True)]},
help="if no user is added then it will applicable for all users",
)
log_read = fields.Boolean(
"Log Reads",
@ -77,7 +75,6 @@ class AuditlogRule(models.Model):
"Select this if you want to keep track of read/open on any "
"record of the model of this rule"
),
states={"subscribed": [("readonly", True)]},
)
log_write = fields.Boolean(
"Log Writes",
@ -86,7 +83,6 @@ class AuditlogRule(models.Model):
"Select this if you want to keep track of modification on any "
"record of the model of this rule"
),
states={"subscribed": [("readonly", True)]},
)
log_unlink = fields.Boolean(
"Log Deletes",
@ -95,7 +91,6 @@ class AuditlogRule(models.Model):
"Select this if you want to keep track of deletion on any "
"record of the model of this rule"
),
states={"subscribed": [("readonly", True)]},
)
log_create = fields.Boolean(
"Log Creates",
@ -104,7 +99,6 @@ class AuditlogRule(models.Model):
"Select this if you want to keep track of creation on any "
"record of the model of this rule"
),
states={"subscribed": [("readonly", True)]},
)
log_type = fields.Selection(
[("full", "Full log"), ("fast", "Fast log")],
@ -118,7 +112,6 @@ class AuditlogRule(models.Model):
"Fast log: only log the changes made through the create and "
"write operations (less information, but it is faster)"
),
states={"subscribed": [("readonly", True)]},
)
state = fields.Selection(
@ -129,7 +122,6 @@ class AuditlogRule(models.Model):
action_id = fields.Many2one(
"ir.actions.act_window",
string="Action",
states={"subscribed": [("readonly", True)]},
)
capture_record = fields.Boolean(
help="Select this if you want to keep track of Unlink Record",
@ -138,14 +130,12 @@ class AuditlogRule(models.Model):
"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 = [
@ -161,7 +151,7 @@ class AuditlogRule(models.Model):
def _register_hook(self):
"""Get all rules and apply them to log method calls."""
super(AuditlogRule, self)._register_hook()
super()._register_hook()
if not hasattr(self.pool, "_auditlog_field_cache"):
self.pool._auditlog_field_cache = {}
if not hasattr(self.pool, "_auditlog_model_cache"):
@ -170,6 +160,24 @@ class AuditlogRule(models.Model):
self = self.search([("state", "=", "subscribed")])
return self._patch_methods()
def _patch_method(self, model, method_name, check_attr):
result = new_method = False
model_class = type(model)
if method_name == "create":
new_method = self._make_create()
elif method_name == "read":
new_method = self._make_read()
elif method_name == "write":
new_method = self._make_write()
elif method_name == "unlink":
new_method = self._make_unlink()
if new_method:
new_method.origin = getattr(model_class, method_name)
setattr(model_class, method_name, new_method)
setattr(type(model), check_attr, True)
result = True
return result
def _patch_methods(self):
"""Patch ORM methods of models defined in rules to log their calls."""
updated = False
@ -185,27 +193,19 @@ class AuditlogRule(models.Model):
# -> create
check_attr = "auditlog_ruled_create"
if rule.log_create and not hasattr(model_model, check_attr):
model_model._patch_method("create", rule._make_create())
setattr(type(model_model), check_attr, True)
updated = True
updated = rule._patch_method(model_model, "create", check_attr)
# -> read
check_attr = "auditlog_ruled_read"
if rule.log_read and not hasattr(model_model, check_attr):
model_model._patch_method("read", rule._make_read())
setattr(type(model_model), check_attr, True)
updated = True
updated = rule._patch_method(model_model, "read", check_attr)
# -> write
check_attr = "auditlog_ruled_write"
if rule.log_write and not hasattr(model_model, check_attr):
model_model._patch_method("write", rule._make_write())
setattr(type(model_model), check_attr, True)
updated = True
updated = rule._patch_method(model_model, "write", check_attr)
# -> unlink
check_attr = "auditlog_ruled_unlink"
if rule.log_unlink and not hasattr(model_model, check_attr):
model_model._patch_method("unlink", rule._make_unlink())
setattr(type(model_model), check_attr, True)
updated = True
updated = rule._patch_method(model_model, "unlink", check_attr)
return updated
def _revert_methods(self):
@ -217,7 +217,9 @@ class AuditlogRule(models.Model):
if getattr(rule, "log_%s" % method) and hasattr(
getattr(model_model, method), "origin"
):
model_model._revert_method(method)
setattr(
type(model_model), method, getattr(model_model, method).origin
)
delattr(type(model_model), "auditlog_ruled_%s" % method)
updated = True
if updated:
@ -252,7 +254,7 @@ class AuditlogRule(models.Model):
def unlink(self):
"""Unsubscribe rules before removing them."""
self.unsubscribe()
return super(AuditlogRule, self).unlink()
return super().unlink()
@api.model
def get_auditlog_fields(self, model):
@ -315,7 +317,7 @@ class AuditlogRule(models.Model):
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):
for vals, new_record in zip(vals_list2, new_records, strict=True):
new_values.setdefault(new_record.id, vals)
if self.env.user in users_to_exclude:
return new_records
@ -511,10 +513,9 @@ class AuditlogRule(models.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:
name = model_model.browse(res_id).name_get()
res_name = name and name[0] and name[0][1]
res = model_model.browse(res_id)
vals = {
"name": res_name,
"name": res.display_name,
"model_id": model_id,
"res_id": res_id,
"method": method,
@ -600,10 +601,10 @@ class AuditlogRule(models.Model):
"new_value_text": False,
}
if field["relation"] and "2many" in field["ttype"]:
old_value_text = (
self.env[field["relation"]].browse(vals["old_value"]).name_get()
)
vals["old_value_text"] = old_value_text
vals["old_value_text"] = [
(x.id, x.display_name)
for x in self.env[field["relation"]].browse(vals["old_value"])
]
return vals
def _create_log_line_on_write(
@ -635,27 +636,27 @@ class AuditlogRule(models.Model):
"new_value": new_values[log.res_id][field["name"]],
"new_value_text": new_values[log.res_id][field["name"]],
}
# for *2many fields, log the name_get
# for *2many fields, log the display_name
if log.log_type == "full" and field["relation"] and "2many" in field["ttype"]:
# Filter IDs to prevent a 'name_get()' call on deleted resources
# Filter IDs to prevent a 'display_name' call on deleted resources
existing_ids = self.env[field["relation"]]._search(
[("id", "in", vals["old_value"])]
)
old_value_text = []
if existing_ids:
existing_values = (
self.env[field["relation"]].browse(existing_ids).name_get()
)
old_value_text.extend(existing_values)
old_value_text = [
(x.id, x.display_name)
for x in self.env[field["relation"]].browse(existing_ids)
]
# Deleted resources will have a 'DELETED' text representation
deleted_ids = set(vals["old_value"]) - set(existing_ids)
for deleted_id in deleted_ids:
old_value_text.append((deleted_id, "DELETED"))
vals["old_value_text"] = old_value_text
new_value_text = (
self.env[field["relation"]].browse(vals["new_value"]).name_get()
)
vals["new_value_text"] = new_value_text
vals["new_value_text"] = [
(x.id, x.display_name)
for x in self.env[field["relation"]].browse(vals["new_value"])
]
return vals
def _create_log_line_on_create(
@ -686,10 +687,10 @@ class AuditlogRule(models.Model):
"new_value_text": new_values[log.res_id][field["name"]],
}
if log.log_type == "full" and field["relation"] and "2many" in field["ttype"]:
new_value_text = (
self.env[field["relation"]].browse(vals["new_value"]).name_get()
)
vals["new_value_text"] = new_value_text
vals["new_value_text"] = [
(x.id, x.display_name)
for x in self.env[field["relation"]].browse(vals["new_value"])
]
return vals
def subscribe(self):

View File

@ -2,7 +2,7 @@
# © 2018 Pieter Paulussen <pieter_paulussen@me.com>
# © 2021 Stefan Rijnhart <stefan@opener.amsterdam>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests.common import Form, TransactionCase
from odoo.tests.common import TransactionCase
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
from odoo.addons.base.models.res_users import name_boolean_group
@ -50,11 +50,15 @@ class AuditlogCommon:
self.groups_rule.subscribe()
auditlog_log = self.env["auditlog.log"]
testgroup3 = testgroup3 = self.env["res.groups"].create({"name": "testgroup3"})
testgroup4 = self.env["res.groups"].create(
{"name": "testgroup4", "implied_ids": [(4, testgroup3.id)]}
testgroup3 = self.env["res.groups"].create({"name": "testgroup3"})
testgroup4 = self.env["res.groups"].create({"name": "testgroup4"})
testgroup5 = self.env["res.groups"].create(
{
"name": "testgroup5",
"implied_ids": [(4, testgroup3.id), (4, testgroup4.id)],
}
)
testgroup4.write({"implied_ids": [(2, testgroup3.id)]})
testgroup5.write({"implied_ids": [(2, testgroup3.id)]})
self.assertTrue(
auditlog_log.search(
[
@ -69,7 +73,7 @@ class AuditlogCommon:
[
("model_id", "=", self.groups_model_id),
("method", "=", "create"),
("res_id", "=", testgroup4.id),
("res_id", "=", testgroup5.id),
]
).ensure_one()
)
@ -78,7 +82,7 @@ class AuditlogCommon:
[
("model_id", "=", self.groups_model_id),
("method", "=", "write"),
("res_id", "=", testgroup4.id),
("res_id", "=", testgroup5.id),
]
).ensure_one()
)
@ -206,7 +210,14 @@ class AuditlogCommon:
def test_LogUpdate(self):
"""Tests write results with different M2O values."""
self.groups_rule.subscribe()
group = self.env["res.groups"].create({"name": "testgroup1"})
testgroup3 = self.env["res.groups"].create({"name": "testgroup3"})
testgroup4 = self.env["res.groups"].create({"name": "testgroup4"})
group = self.env["res.groups"].create(
{
"name": "testgroup1",
"implied_ids": [(4, testgroup3.id), (4, testgroup4.id)],
}
)
cat = self.env["ir.module.category"].create({"name": "Test Category"})
group.write(
{
@ -262,7 +273,7 @@ class AuditlogCommon:
class TestAuditlogFull(TransactionCase, AuditlogCommon):
def setUp(self):
super(TestAuditlogFull, self).setUp()
super().setUp()
self.groups_model_id = self.env.ref("base.model_res_groups").id
self.groups_rule = self.env["auditlog.rule"].create(
{
@ -278,12 +289,12 @@ class TestAuditlogFull(TransactionCase, AuditlogCommon):
def tearDown(self):
self.groups_rule.unlink()
super(TestAuditlogFull, self).tearDown()
super().tearDown()
class TestAuditlogFast(TransactionCase, AuditlogCommon):
def setUp(self):
super(TestAuditlogFast, self).setUp()
super().setUp()
self.groups_model_id = self.env.ref("base.model_res_groups").id
self.groups_rule = self.env["auditlog.rule"].create(
{
@ -299,7 +310,7 @@ class TestAuditlogFast(TransactionCase, AuditlogCommon):
def tearDown(self):
self.groups_rule.unlink()
super(TestAuditlogFast, self).tearDown()
super().tearDown()
class TestFieldRemoval(TransactionCase):
@ -398,7 +409,7 @@ class TestFieldRemoval(TransactionCase):
class TestAuditlogFullCaptureRecord(TransactionCase, AuditlogCommon):
def setUp(self):
super(TestAuditlogFullCaptureRecord, self).setUp()
super().setUp()
self.groups_model_id = self.env.ref("base.model_res_groups").id
self.groups_rule = self.env["auditlog.rule"].create(
{
@ -415,13 +426,13 @@ class TestAuditlogFullCaptureRecord(TransactionCase, AuditlogCommon):
def tearDown(self):
self.groups_rule.unlink()
super(TestAuditlogFullCaptureRecord, self).tearDown()
super().tearDown()
class AuditLogRuleTestForUserFields(TransactionCase):
@classmethod
def setUpClass(cls):
super(AuditLogRuleTestForUserFields, cls).setUpClass()
super().setUpClass()
# get Contact model id
cls.contact_model_id = (
cls.env["ir.model"].search([("model", "=", "res.partner")]).id
@ -551,14 +562,11 @@ class AuditLogRuleTestForUserFields(TransactionCase):
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(
# Update email with excluded user
partner = self.testpartner1.with_user(self.user.id).with_context(
tracking_disable=True
)
)
partner_form.email = "vendor@mail.com"
testpartner1 = partner_form.save()
partner.email = "vendor@mail.com"
# Checking write log not created
with self.assertRaises(ValueError):
@ -566,7 +574,7 @@ class AuditLogRuleTestForUserFields(TransactionCase):
[
("model_id", "=", self.auditlog_rule.model_id.id),
("method", "=", "write"),
("res_id", "=", testpartner1.id),
("res_id", "=", partner.id),
("user_id", "=", self.user.id),
]
).ensure_one()

View File

@ -7,7 +7,7 @@ from odoo.tests.common import TransactionCase
class TestAuditlogAutovacuum(TransactionCase):
def setUp(self):
super(TestAuditlogAutovacuum, self).setUp()
super().setUp()
self.groups_model_id = self.env.ref("base.model_res_groups").id
self.groups_rule = self.env["auditlog.rule"].create(
{
@ -24,7 +24,7 @@ class TestAuditlogAutovacuum(TransactionCase):
def tearDown(self):
self.groups_rule.unlink()
super(TestAuditlogAutovacuum, self).tearDown()
super().tearDown()
def test_autovacuum(self):
log_model = self.env["auditlog.log"]

View File

@ -18,46 +18,48 @@
string="Subscribe"
name="subscribe"
type="object"
states="draft"
invisible="state != 'draft'"
class="oe_highlight"
/>
<button
string="Unsubscribe"
name="unsubscribe"
type="object"
states="subscribed"
invisible="state != '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="log_type" />
<field name="name" readonly="state == 'subscribed'" />
<field name="model_id" readonly="state == 'subscribed'" />
<field name="log_type" readonly="state == 'subscribed'" />
<field
name="action_id"
readonly="1"
readonly="state == 'subscribed'"
groups="base.group_no_one"
/>
<field
name="capture_record"
attrs="{'invisible':['|' ,('log_type','!=', 'full'), ('log_unlink','!=', True)]}"
invisible="log_type != 'full' or log_unlink != True"
/>
<field
name="users_to_exclude_ids"
widget="many2many_tags"
readonly="state == 'subscribed'"
/>
<field
name="fields_to_exclude_ids"
widget="many2many_tags"
readonly="state == 'subscribed'"
/>
</group>
<group colspan="1">
<field name="log_read" />
<field name="log_write" />
<field name="log_unlink" />
<field name="log_create" />
<field name="log_read" readonly="state == 'subscribed'" />
<field name="log_write" readonly="state == 'subscribed'" />
<field name="log_unlink" readonly="state == 'subscribed'" />
<field name="log_create" readonly="state == 'subscribed'" />
</group>
</group>
</sheet>
@ -142,12 +144,12 @@
<field name="model_id" readonly="1" />
<field
name="model_name"
attrs="{'invisible': [('model_id', '!=', False)]}"
invisible="model_id != False"
readonly="1"
/>
<field
name="model_model"
attrs="{'invisible': [('model_id', '!=', False)]}"
invisible="model_id != False"
readonly="1"
/>
<field name="res_id" readonly="1" />
@ -165,7 +167,7 @@
<field name="field_id" readonly="1" />
<field
name="field_name"
attrs="{'invisible': [('field_id', '!=', False)]}"
invisible="field_id != False"
readonly="1"
/>
</group>