Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fe00f4c
[ADD] pre-review version
gfcapalbo Apr 11, 2022
aded99e
[ADD] review fixes
gfcapalbo Apr 13, 2022
386127b
fixup! [ADD] review fixes
Jun 2, 2022
98f670a
[ADD] Lint XML
gfcapalbo Jun 7, 2022
b332384
[ADD] show nicer group_ids and field_ids in treeview
gfcapalbo Jun 7, 2022
de40a70
[FIX] open issues and errors
KKamaa Jul 5, 2022
1896427
fixup! [FIX] open issues and errors
KKamaa Jul 5, 2022
42a3b7e
[FIX] fixes points 1,3,4,7
gfcapalbo Jul 20, 2022
4173a72
[FIX] point 2: server actions instead of window action
Kiplangatdan Jul 21, 2022
db95a56
fixup! [FIX] point 2: server actions instead of window action
renzo-brown Jul 21, 2022
7eff32c
[FIX] user_id fix, use related fields , allows for massive cleanup too.
gfcapalbo Jul 26, 2022
9bcce33
fixup! [FIX] user_id fix, use related fields , allows for massive cle…
gfcapalbo Jul 26, 2022
df143e6
[IMP] auditlog: prevent cascading delete of logs when models or field…
Mar 24, 2021
568c754
fixup! [IMP] auditlog: prevent cascading delete of logs when models o…
gfcapalbo Jul 26, 2022
c3fb342
[IMP] review improvements
gfcapalbo Jul 26, 2022
eb494a8
fixup! [IMP] review improvements
gfcapalbo Jul 27, 2022
6dbb364
[FIX] second review
gfcapalbo Jul 27, 2022
f8bd300
fixup! [FIX] second review
gfcapalbo Jul 27, 2022
385407e
fixup! [FIX] second review
gfcapalbo Jul 27, 2022
b55f32d
fixup! [FIX] second review
gfcapalbo Jul 27, 2022
80426f0
fixup! [FIX] second review
gfcapalbo Jul 27, 2022
4c04c1b
[ADD] standard log view now restricted, only loglines for normal users
gfcapalbo Jul 27, 2022
999aff1
fixup! fixup! [FIX] second review
gfcapalbo Jul 27, 2022
ff6b23f
[IMP] Add filtering and correct problems with display
gfcapalbo Jul 28, 2022
b09f5b8
[IMP] no more access errors, regressions, and methods to reduce number
gfcapalbo Jul 28, 2022
5cac37f
fixup! [IMP] no more access errors, regressions, and methods to reduc…
Aug 19, 2022
d9c99f2
fixup! fixup! [IMP] no more access errors, regressions, and methods t…
Aug 19, 2022
f08ee94
fixup! fixup! fixup! [IMP] no more access errors, regressions, and me…
Sep 13, 2022
e1c4f7c
fixup! fixup! fixup! fixup! [IMP] no more access errors, regressions,…
Sep 14, 2022
6b71963
fixup! fixup! fixup! fixup! fixup! [IMP] no more access errors, regre…
Sep 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion auditlog/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{
'name': "Audit Log",
'version': "11.0.1.0.1",
'version': "11.0.1.1.1",
'author': "ABF OSIELL,Odoo Community Association (OCA)",
'license': "AGPL-3",
'website': "https://github.com/OCA/server-tools/",
Expand Down
73 changes: 73 additions & 0 deletions auditlog/migrations/11.0.1.1.1/pre-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# © 2018 Pieter Paulussen <pieter_paulussen@me.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging


def migrate(cr, version):
if not version:
return
logger = logging.getLogger(__name__)
logger.info(
"Creating columns: auditlog_log (model_name, model_model) "
"and auditlog_log_line (field_name, field_description)."
)
cr.execute(
"""
ALTER TABLE auditlog_log
ADD COLUMN IF NOT EXISTS model_name VARCHAR,
ADD COLUMN IF NOT EXISTS model_model VARCHAR;
ALTER TABLE auditlog_log_line
ADD COLUMN IF NOT EXISTS field_name VARCHAR,
ADD COLUMN IF NOT EXISTS field_description VARCHAR;
ALTER TABLE auditlog_rule
ADD COLUMN IF NOT EXISTS model_name VARCHAR,
ADD COLUMN IF NOT EXISTS model_model VARCHAR;
"""
)
logger.info(
"Creating indexes on auditlog_log column 'model_id' and "
"auditlog_log_line column 'field_id'."
)
cr.execute(
"""
CREATE INDEX IF NOT EXISTS
auditlog_log_model_id_index ON auditlog_log (model_id);
CREATE INDEX IF NOT EXISTS
auditlog_log_line_field_id_index ON auditlog_log_line (field_id);
"""
)
logger.info(
"Preemptive fill auditlog_log columns: 'model_name' and " "'model_model'."
)
cr.execute(
"""
UPDATE auditlog_log al
SET model_name = im.name, model_model = im.model
FROM ir_model im
WHERE im.id = al.model_id AND model_name IS NULL
"""
)
logger.info(
"Preemtive fill of auditlog_log_line columns: 'field_name' and"
" 'field_description'."
)
cr.execute(
"""
UPDATE auditlog_log_line al
SET field_name = imf.name, field_description = imf.field_description
FROM ir_model_fields imf
WHERE imf.id = al.field_id AND field_name IS NULL
"""
)
logger.info(
"Preemptive fill auditlog_rule columns: 'model_name' and " "'model_model'."
)
cr.execute(
"""
UPDATE auditlog_rule al
SET model_name = im.name, model_model = im.model
FROM ir_model im
WHERE im.id = al.model_id AND model_name IS NULL
"""
)
logger.info("Successfully updated auditlog tables")
15 changes: 12 additions & 3 deletions auditlog/models/log.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2015 ABF OSIELL <https://osiell.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models, fields
from odoo import _, api, fields, models
from odoo.exceptions import UserError


class AuditlogLog(models.Model):
Expand All @@ -10,7 +11,13 @@ class AuditlogLog(models.Model):

name = fields.Char("Resource Name", size=64)
model_id = fields.Many2one(
'ir.model', string="Model")
"ir.model", string="Model", index=True,
required=True
)
model_name = fields.Char(readonly=True, related='model_id.name' )
model_model = fields.Char(string="Technical Model Name", readonly=True,
related="model_id.model"
)
res_id = fields.Integer("Resource ID")
user_id = fields.Many2one(
'res.users', string="User")
Expand All @@ -33,7 +40,9 @@ class AuditlogLogLine(models.Model):
_description = "Auditlog - Log details (fields updated)"

field_id = fields.Many2one(
'ir.model.fields', ondelete='cascade', string="Field", required=True)
"ir.model.fields", ondelete="set null", string="Field", index=True,
required=True
)
log_id = fields.Many2one(
'auditlog.log', string="Log", ondelete='cascade', index=True)
old_value = fields.Text("Old Value")
Expand Down
44 changes: 32 additions & 12 deletions auditlog/models/rule.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Copyright 2015 ABF OSIELL <https://osiell.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models, fields, api, modules, _
import copy

from odoo import _, api, fields, models, modules
from odoo.exceptions import UserError

FIELDS_BLACKLIST = [
'id', 'create_uid', 'create_date', 'write_uid', 'write_date',
Expand Down Expand Up @@ -46,8 +49,15 @@ class AuditlogRule(models.Model):

name = fields.Char("Name", size=32, required=True)
model_id = fields.Many2one(
'ir.model', "Model", required=True,
help="Select model for which you want to generate log.")
"ir.model",
"Model",
help="Select model for which you want to generate log.",
states={"subscribed": [("readonly", True)]},
ondelete="set null",
index=True,
)
model_name = fields.Char(readonly=True)
model_model = fields.Char(string="Technical Model Name", readonly=True)
user_ids = fields.Many2many(
'res.users',
'audittail_rules_users',
Expand Down Expand Up @@ -119,11 +129,11 @@ def _patch_methods(self):
for rule in self:
if rule.state != 'subscribed':
continue
if not self.pool.get(rule.model_id.model):
if not self.pool.get(rule.model_id.model or rule.model_model):
# ignore rules for models not loadable currently
continue
model_cache[rule.model_id.model] = rule.model_id.id
model_model = self.env[rule.model_id.model]
model_model = self.env[rule.model_id.model or rule.model_model]
# CRUD
# -> create
check_attr = 'auditlog_ruled_create'
Expand Down Expand Up @@ -160,10 +170,11 @@ 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) and hasattr(
getattr(model_model, method), 'origin'):
model_model = self.env[rule.model_id.model or rule.model_model]
for method in ["create", "read", "write", "unlink"]:
if getattr(rule, "log_%s" % method) and hasattr(
getattr(model_model, method), "origin"
):
model_model._revert_method(method)
delattr(type(model_model), 'auditlog_ruled_%s' % method)
updated = True
Expand All @@ -173,18 +184,27 @@ def _revert_methods(self):
@api.model
def create(self, vals):
"""Update the registry when a new rule is created."""
new_record = super(AuditlogRule, self).create(vals)
if "model_id" not in vals or not vals["model_id"]:
raise UserError(_("No model defined to create line."))
model = self.env["ir.model"].browse(vals["model_id"])
vals.update({"model_name": model.name, "model_model": model.model})
new_record = super().create(vals)
if new_record._register_hook():
modules.registry.Registry(self.env.cr.dbname).signal_changes()
return new_record

@api.multi
def write(self, vals):
"""Update the registry when existing rules are updated."""
super(AuditlogRule, self).write(vals)
if "model_id" in vals:
if not vals["model_id"]:
raise UserError(_("Field 'model_id' cannot be empty."))
model = self.env["ir.model"].browse(vals["model_id"])
vals.update({"model_name": model.name, "model_model": model.model})
res = super().write(vals)
if self._register_hook():
modules.registry.Registry(self.env.cr.dbname).signal_changes()
return True
return res

@api.multi
def unlink(self):
Expand Down
4 changes: 4 additions & 0 deletions auditlog/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
* Sebastien Alix <sebastien.alix@camptocamp.com>
* Holger Brunn <hbrunn@therp.nl>
* Holden Rehg <holdenrehg@gmail.com>
* Eric Lembregts <eric@lembregts.eu>
* Pieter Paulussen <pieter.paulussen@me.com>
* Alan Ramos <alan.ramos@jarsa.com.mx>
* Stefan Rijnhart <stefan@opener.amsterdam>
118 changes: 117 additions & 1 deletion auditlog/tests/test_auditlog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Copyright 2015 Therp BV <https://therp.nl>
# © 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 TransactionCase
from odoo.modules.migration import load_script
from odoo.tests.common import SavepointCase, TransactionCase

from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG


class AuditlogCommon(object):
Expand Down Expand Up @@ -119,3 +124,114 @@ def setUp(self):
def tearDown(self):
self.groups_rule.unlink()
super(TestAuditlogFast, self).tearDown()


class TestFieldRemoval(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()

# Clear all existing logging lines
existing_audit_logs = cls.env["auditlog.log"].search([])
existing_audit_logs.unlink()

# Create a test model to remove
cls.test_model = cls.env["ir.model"].create(
{"name": "x_test_model", "model": "x_test.model", "state": "manual"}
)

# Create a test model field to remove
cls.test_field = cls.env["ir.model.fields"].create(
{
"name": "x_test_field",
"field_description": "x_Test Field",
"model_id": cls.test_model.id,
"ttype": "char",
"state": "manual",
}
)

# Setup auditlog rule
cls.auditlog_rule = cls.env["auditlog.rule"].create(
{
"name": "test.model",
"model_id": cls.test_model.id,
"log_type": "fast",
"log_read": False,
"log_create": True,
"log_write": True,
"log_unlink": False,
}
)

cls.auditlog_rule.subscribe()
# Trigger log creation
rec = cls.env["x_test.model"].create({"x_test_field": "test value"})
rec.write({"x_test_field": "test value 2"})

cls.logs = cls.env["auditlog.log"].search(
[("res_id", "=", rec.id), ("model_id", "=", cls.test_model.id)]
)

def assert_values(self):
"""Assert that the denormalized field and model info is present
on the auditlog records"""
self.logs.refresh()
self.assertEqual(self.logs[0].model_name, "x_test_model")
self.assertEqual(self.logs[0].model_model, "x_test.model")

log_lines = self.logs.mapped("line_ids")
self.assertEqual(len(log_lines), 2)
self.assertEqual(log_lines[0].field_name, "x_test_field")
self.assertEqual(log_lines[0].field_description, "x_Test Field")

self.auditlog_rule.refresh()
self.assertEqual(self.auditlog_rule.model_name, "x_test_model")
self.assertEqual(self.auditlog_rule.model_model, "x_test.model")

def test_01_field_and_model_removal(self):
""" Test field and model removal to check auditlog line persistence """
self.assert_values()

# Remove the field
self.test_field.with_context({MODULE_UNINSTALL_FLAG: True}).unlink()
self.assert_values()
# The field should not be linked
self.assertFalse(self.logs.mapped("line_ids.field_id"))

# Remove the model
self.test_model.with_context({MODULE_UNINSTALL_FLAG: True}).unlink()
self.assert_values()

# The model should not be linked
self.assertFalse(self.logs.mapped("model_id"))
# Assert rule values
self.assertFalse(self.auditlog_rule.model_id)

def test_02_migration(self):
"""Test the migration of the data model related to this feature"""
# Drop the data model
self.env.cr.execute(
"""ALTER TABLE auditlog_log
DROP COLUMN model_name, DROP COLUMN model_model"""
)
self.env.cr.execute(
"""ALTER TABLE auditlog_rule
DROP COLUMN model_name, DROP COLUMN model_model"""
)
self.env.cr.execute(
"""ALTER TABLE auditlog_log_line
DROP COLUMN field_name, DROP COLUMN field_description"""
)

# Recreate the data model
mod = load_script(
"auditlog/migrations/14.0.1.1.0/pre-migration.py", "pre-migration"
)
mod.migrate(self.env.cr, "14.0.1.0.2")

# Values are restored
self.assert_values()

# The migration script is tolerant if the data model is already in place
mod.migrate(self.env.cr, "14.0.1.0.2")
23 changes: 19 additions & 4 deletions auditlog/views/auditlog_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,19 @@
<field name="log_type" readonly="1"/>
</group>
<group colspan="1">
<field name="model_id" readonly="1"/>
<field name="res_id" readonly="1"/>
<field name="name" readonly="1"/>
<field name="model_id" readonly="1" />
<field
name="model_name"
attrs="{'invisible': [('model_id', '!=', False)]}"
readonly="1"
/>
<field
name="model_model"
attrs="{'invisible': [('model_id', '!=', False)]}"
readonly="1"
/>
<field name="res_id" readonly="1" />
<field name="name" readonly="1" />
</group>
</group>
<group string="HTTP Context">
Expand All @@ -117,7 +127,12 @@
<field name="line_ids" readonly="1" nolabel="1">
<form string="Log - Field updated">
<group>
<field name="field_id" readonly="1"/>
<field name="field_id" readonly="1" />
<field
name="field_name"
attrs="{'invisible': [('field_id', '!=', False)]}"
readonly="1"
/>
</group>
<group string="Values" col="4">
<field name="old_value" readonly="1"/>
Expand Down
Loading