From da570c257161cc4f3855ee9d056135b32b059594 Mon Sep 17 00:00:00 2001 From: Kev-Roche Date: Thu, 20 Oct 2022 18:34:47 +0200 Subject: [PATCH 01/44] [14.0][ADD] tracking_manager --- tracking_manager/__init__.py | 1 + tracking_manager/__manifest__.py | 27 +++ tracking_manager/i18n/fr.po | 184 ++++++++++++++++ tracking_manager/models/__init__.py | 3 + tracking_manager/models/ir_model.py | 117 ++++++++++ tracking_manager/models/mail_thread.py | 208 ++++++++++++++++++ .../models/security/ir.model.access.csv | 3 + tracking_manager/models/tracking_model.py | 52 +++++ tracking_manager/readme/CONTRIBUTORS.rst | 1 + tracking_manager/readme/DESCRIPTION.rst | 2 + tracking_manager/readme/USAGE.rst | 4 + tracking_manager/security/ir.model.access.csv | 2 + tracking_manager/tests/__init__.py | 1 + .../tests/test_tracking_manager.py | 155 +++++++++++++ tracking_manager/views/ir_model.xml | 67 ++++++ tracking_manager/views/ir_model_fields.xml | 19 ++ tracking_manager/views/message_template.xml | 52 +++++ 17 files changed, 898 insertions(+) create mode 100644 tracking_manager/__init__.py create mode 100644 tracking_manager/__manifest__.py create mode 100644 tracking_manager/i18n/fr.po create mode 100644 tracking_manager/models/__init__.py create mode 100644 tracking_manager/models/ir_model.py create mode 100644 tracking_manager/models/mail_thread.py create mode 100644 tracking_manager/models/security/ir.model.access.csv create mode 100644 tracking_manager/models/tracking_model.py create mode 100644 tracking_manager/readme/CONTRIBUTORS.rst create mode 100644 tracking_manager/readme/DESCRIPTION.rst create mode 100644 tracking_manager/readme/USAGE.rst create mode 100644 tracking_manager/security/ir.model.access.csv create mode 100644 tracking_manager/tests/__init__.py create mode 100644 tracking_manager/tests/test_tracking_manager.py create mode 100644 tracking_manager/views/ir_model.xml create mode 100644 tracking_manager/views/ir_model_fields.xml create mode 100644 tracking_manager/views/message_template.xml diff --git a/tracking_manager/__init__.py b/tracking_manager/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/tracking_manager/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/tracking_manager/__manifest__.py b/tracking_manager/__manifest__.py new file mode 100644 index 00000000000..9d4800b0a95 --- /dev/null +++ b/tracking_manager/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Tracking Manager", + "summary": """This module tracks all fields of a model, + including one2many and many2many ones.""", + "version": "14.0.1.0.0", + "category": "Tools", + "website": "https://github.com/OCA/server-tools", + "author": "Akretion, Odoo Community Association (OCA)", + "maintainers": ["Kev-Roche"], + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "base", + "mail", + ], + "data": [ + "views/ir_model.xml", + "views/ir_model_fields.xml", + "views/message_template.xml", + "security/ir.model.access.csv", + ], +} diff --git a/tracking_manager/i18n/fr.po b/tracking_manager/i18n/fr.po new file mode 100644 index 00000000000..c8681b31060 --- /dev/null +++ b/tracking_manager/i18n/fr.po @@ -0,0 +1,184 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * tracking_manager +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-10-20 16:02+0000\n" +"PO-Revision-Date: 2022-10-20 18:03+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.1.1\n" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "Change :" +msgstr "Modifié :" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "Delete :" +msgstr "Supprimé :" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "New :" +msgstr "Nouveau :" + +#. module: tracking_manager +#: model:ir.model.fields,help:tracking_manager.field_ir_model__apply_custom_tracking +msgid "" +"Add tracking on all this model fields if they are not readonly True, " +"neither computed." +msgstr "" +"Active le suivi des champs de ce modèles qui ne sont pas en lecture seule, " +"ni reliés, ni calculés." + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__apply_custom_tracking +msgid "Apply custom tracking on fields" +msgstr "Active le suivi personnalisé des champs" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "Changed" +msgstr "Modifié" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Custom Tracked fields" +msgstr "Champs Suivis" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__custom_tracking +msgid "Custom Tracking" +msgstr "Suivi personnalisé" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__display_name +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__display_name +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__display_name +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_mail_thread +msgid "Email Thread" +msgstr "Discussion par email" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__tracking_field_id +msgid "Field" +msgstr "Champ" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__field_count +msgid "Field Count" +msgstr "Nb de champs" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__name +msgid "Field Name" +msgstr "Nom du champs" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_ir_model_fields +msgid "Fields" +msgstr "Champs" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__id +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__id +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__id +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__id +msgid "ID" +msgstr "" + +#. module: tracking_manager +#: model:ir.model.fields,help:tracking_manager.field_tracking_model_field__native_tracking_field +msgid "" +"If set every modification done to this field is tracked in the chatter. " +"Value is used to order tracking values." +msgstr "" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__type_field +msgid "Kind Field" +msgstr "Type de champs" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model____last_update +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields____last_update +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread____last_update +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__write_date +msgid "Last Updated on" +msgstr "" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_ir_model +msgid "Models" +msgstr "Modèles" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__native_tracking_field +msgid "Native Tracking" +msgstr "Suivi natif" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__o2m_model_ids +msgid "One2many Models" +msgstr "Modèles One2many" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "One2many related models" +msgstr "Modèles One2many présents" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__custom_tracking_id +msgid "Technical Custom Tracking Field" +msgstr "" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Tracked Fields" +msgstr "Champs avec suivi personnalisé" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Tracking" +msgstr "Suivi" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_tracking_model_field +#: model:ir.model.fields,field_description:tracking_manager.field_tracking_model_field__model_id +msgid "Tracking Model Field" +msgstr "Model suivi" diff --git a/tracking_manager/models/__init__.py b/tracking_manager/models/__init__.py new file mode 100644 index 00000000000..3e5ef9b118b --- /dev/null +++ b/tracking_manager/models/__init__.py @@ -0,0 +1,3 @@ +from . import mail_thread +from . import tracking_model +from . import ir_model diff --git a/tracking_manager/models/ir_model.py b/tracking_manager/models/ir_model.py new file mode 100644 index 00000000000..5cc13fb07a0 --- /dev/null +++ b/tracking_manager/models/ir_model.py @@ -0,0 +1,117 @@ +# Copyright (C) 2022 Akretion (). +# @author Kévin Roche +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class IrModel(models.Model): + _inherit = "ir.model" + + o2m_model_ids = fields.One2many( + string="One2many Models", + comodel_name="ir.model", + compute="_compute_o2m_model_ids", + readonly=True, + ) + custom_tracking_field_ids = fields.One2many( + comodel_name="tracking.model.field", + inverse_name="model_id", + string="Custom Tracked Fields", + compute="_compute_custom_tracking_fields", + store=True, + ) + field_count = fields.Integer(compute="_compute_tracked_field_count") + apply_custom_tracking = fields.Boolean( + "Apply custom tracking on fields", + default=False, + help="""Add tracking on all this model fields if they + are not readonly True, neither computed.""", + ) + + @api.depends("field_id", "apply_custom_tracking") + def _compute_custom_tracking_fields(self): + for rec in self: + fields = rec.env["ir.model.fields"].search( + [("model_id.model", "=", rec.model)] + ) + fields_ids = [] + for field in fields: + values = {} + values["tracking_field_id"] = field.id + values["model_id"] = rec.id + fields_ids.append((0, 0, values)) + rec.custom_tracking_field_ids = [(6, 0, [])] + rec.custom_tracking_field_ids = fields_ids + + @api.depends("field_id", "apply_custom_tracking") + def _compute_o2m_model_ids(self): + for rec in self: + o2m_model_list = [] + if rec.apply_custom_tracking: + for field in rec.field_id: + if field.ttype == "one2many": + o2m_relation_id = self.search( + [("model", "=", field.relation)], limit=1 + ) + o2m_model_list.append(o2m_relation_id.id) + rec.o2m_model_ids = [(6, 0, o2m_model_list)] + + @api.depends( + "custom_tracking_field_ids", "custom_tracking_field_ids.custom_tracking" + ) + def _compute_tracked_field_count(self): + for rec in self: + rec.field_count = sum( + rec.custom_tracking_field_ids.mapped("custom_tracking") + ) + + def show_custom_tracked_field(self): + return { + "name": "Custom tracked fields", + "type": "ir.actions.act_window", + "res_id": self.id, + "view_mode": "tree", + "res_model": "tracking.model.field", + "view_id": self.env.ref( + "tracking_manager.custom_tracking_field_view_tree" + ).id, + "target": "current", + "domain": [("id", "in", self.custom_tracking_field_ids.ids)], + } + + def show_o2m_models(self): + return { + "name": "o2m models", + "type": "ir.actions.act_window", + "res_id": self.id, + "view_mode": "tree,form", + "res_model": "ir.model", + "views": [ + (self.env.ref("base.view_model_tree").id, "tree"), + (self.env.ref("base.view_model_form").id, "form"), + ], + "target": "current", + "domain": [("id", "in", self.o2m_model_ids.ids)], + } + + +class IrModelFields(models.Model): + _inherit = "ir.model.fields" + + custom_tracking_id = fields.Many2one( + comodel_name="tracking.model.field", + string="Technical Custom Tracking Field", + compute="_compute_custom_tracking_field_id", + ) + + @api.depends("model_id.apply_custom_tracking") + def _compute_custom_tracking_field_id(self): + for rec in self: + if rec.model_id.apply_custom_tracking: + rec.custom_tracking_id = self.env["tracking.model.field"].search( + [("tracking_field_id", "=", rec.id), ("model_id", "=", rec._name)], + limit=1, + ) + else: + rec.custom_tracking_id = False diff --git a/tracking_manager/models/mail_thread.py b/tracking_manager/models/mail_thread.py new file mode 100644 index 00000000000..37ee8a12532 --- /dev/null +++ b/tracking_manager/models/mail_thread.py @@ -0,0 +1,208 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models, tools + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + @tools.ormcache("self.env.uid", "self.env.su") + def _get_tracked_fields(self): + res = super()._get_tracked_fields() + custom_tracked_fields = self._get_custom_tracked_fields() + if custom_tracked_fields: + return custom_tracked_fields + return res + + def _get_custom_tracked_fields(self): + tracking_model = ( + self.env["ir.model"] + .sudo() + .search( + [("model", "=", self._name), ("apply_custom_tracking", "=", True)], + limit=1, + ) + ) + if tracking_model: + track_fields = tracking_model.custom_tracking_field_ids.filtered( + lambda f: f.custom_tracking + ).mapped("name") + return track_fields and set(self.fields_get(track_fields)) + return + + def _create_tracking_message(self, mode, field_id, record): + return { + "mode": mode, + "name": field_id.field_description, + "record": self._get_tracked_record_name(record), + } + + def _get_tracked_record_name(self, record): + return ( + getattr(record, "display_name", False) + or getattr(record, "name", False) + or f"No name, record :{record}" + ) + + def write(self, vals): + fields = list(vals.keys()) + tracked_fields = self._get_custom_tracked_fields() + m2m_o2m_tracked_message = [] + records = [] + if tracked_fields: + records = [f for f in fields if f in tracked_fields] + for field in records: + del_and_chg_records = self._get_m2m_m2o_tracked_fields( + field, tracked_fields, vals + ) + if del_and_chg_records: + m2m_o2m_tracked_message.extend(del_and_chg_records) + res = super().write(vals) + for field in records: + add_records = self._check_o2m_add_lines(field, tracked_fields, vals) + if add_records: + m2m_o2m_tracked_message.extend(add_records) + if m2m_o2m_tracked_message: + self._post_custom_tracking_message(m2m_o2m_tracked_message) + return res + + def _get_m2m_m2o_tracked_fields(self, field, tracked_fields, vals): + field_id = self.env["ir.model.fields"].search( + [("name", "=", field), ("model_id", "=", self._name)], limit=1 + ) + message = [] + if field in tracked_fields and field_id.ttype in ["one2many", "many2many"]: + if field_id.ttype == "one2many": + message.extend(self._check_o2m_delete_lines(field_id, vals)) + message.extend(self._check_o2m_change_in_lines(field_id, vals)) + if field_id.ttype == "many2many": + message.extend(self._check_m2m_tracking(field_id, vals)) + return message + + def _check_o2m_delete_lines(self, field_id, vals): + lines = [line for line in vals[field_id.name]] + del_message = [] + for line in lines: + if line[0] in [2, 3]: + record = getattr(self, field_id.name).browse(line[-1] or line[1]) + message_line = self._create_tracking_message("Delete", field_id, record) + del_message.append(message_line) + return del_message + + def _check_o2m_change_in_lines(self, field_id, vals): + lines = [line for line in vals[field_id.name] if line[0] in [0, 1]] + chg_message = [] + for line in lines: + if len(line) == 3 and line[0] == 1: + record = getattr(self, field_id.name).browse(line[1]) + line_fields = [f for f in line[2]] + message_line = self._create_tracking_message("Change", field_id, record) + changes = [] + for line_field in line_fields: + line_field_id = self.env["ir.model.fields"].search( + [("name", "=", line_field), ("model", "=", record._name)], + limit=1, + ) + line_model_id = self.env["ir.model"].search( + [("model", "=", record._name)], limit=1 + ) + # if the o2m related model is configured for custom + # tracking (apply_custom_tracking = True), we track + # the changes only for fields with custom_tracking. + if ( + line_model_id.apply_custom_tracking + and not line_field_id.custom_tracking_id.custom_tracking + ): + old = new = False + elif line_field_id.ttype in ["one2many", "many2one", "many2many"]: + old_ids = getattr(record, line_field).ids + if line[2][line_field]: + new_ids = line[2][line_field][0][2] + new_line_ids = set(new_ids) - set(old_ids) + delete_line_ids = set(old_ids) - set(new_ids) + old = ", ".join( + getattr(record, line_field) + .browse(delete_line_ids) + .mapped("name") + ) + new = ", ".join( + getattr(record, line_field) + .browse(new_line_ids) + .mapped("name") + ) + else: + old = getattr(record, line_field) + new = line[2][line_field] + if old != new: + changes.append( + { + "name": line_field, + "old": old, + "new": new, + } + ) + if changes: + message_line["changes"] = changes + chg_message.append(message_line) + return chg_message + + def _check_o2m_add_lines(self, field, tracked_fields, vals): + field_id = self.env["ir.model.fields"].search( + [("name", "=", field), ("model_id", "=", self._name)], limit=1 + ) + if field in tracked_fields and vals.get(field) and field_id.ttype == "one2many": + message = [] + line_ids_in_vals = [ + line[1] + for line in vals[field] + if line[0] != 4 or (line[0] == 4 and not line[-1]) + ] + new_ids = [ + line + for line in [line.id for line in getattr(self, field)] + if line not in line_ids_in_vals + ] + for line in new_ids: + record = getattr(self, field).browse(line) + message_line = self._create_tracking_message("New", field_id, record) + message.append(message_line) + return message + + def _check_m2m_tracking(self, field_id, vals): + m2m_message = [] + new_ids = [line[-1] for line in vals[field_id.name] if line[0] in [0, 4]] + delete_ids = [line[-1] for line in vals[field_id.name] if line[0] in [2, 3]] + new_line_ids = set(new_ids) - set(getattr(self, field_id.name).ids) + for line in new_line_ids: + record = getattr(self, field_id.name).browse(line) + message_line = self._create_tracking_message("New", field_id, record) + m2m_message.append(message_line) + for line in delete_ids: + record = getattr(self, field_id.name).browse(line) + message_line = self._create_tracking_message("Delete", field_id, record) + m2m_message.append(message_line) + return m2m_message + + def _post_custom_tracking_message(self, message): + formated_message = [] + for field in {line["name"] for line in message}: + message_by_field = {"name": field, "message_by_field": []} + modes = {line["mode"] for line in message if line["name"] == field} + for mode in modes: + message_by_mode = {"mode": mode} + for line in message: + if line["name"] == field and line["mode"] == mode: + message_by_mode["record"] = line["record"] + if line.get("changes", False): + message_by_mode["changes"] = line["changes"] + message_by_field["message_by_field"].append(message_by_mode) + formated_message.append(message_by_field) + self.message_post_with_view( + "tracking_manager.track_o2m_m2m_template", + values={ + "lines": formated_message, + }, + subtype_id=self.env.ref("mail.mt_note").id, + ) diff --git a/tracking_manager/models/security/ir.model.access.csv b/tracking_manager/models/security/ir.model.access.csv new file mode 100644 index 00000000000..7a5bf98052d --- /dev/null +++ b/tracking_manager/models/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_tracking_model,tracking.model.user,model_tracking_model,base.group_user,1,1,1,1 +access_tracking_model_field,tracking.model.field.user,model_tracking_model_field,base.group_user,1,1,1,1 diff --git a/tracking_manager/models/tracking_model.py b/tracking_manager/models/tracking_model.py new file mode 100644 index 00000000000..c888939765a --- /dev/null +++ b/tracking_manager/models/tracking_model.py @@ -0,0 +1,52 @@ +# Copyright (C) 2022 Akretion (). +# @author Kévin Roche +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class TrackingModelField(models.Model): + _name = "tracking.model.field" + _description = "Tracking Model Field" + + name = fields.Char(related="tracking_field_id.name") + tracking_field_id = fields.Many2one( + "ir.model.fields", + "Field", + ) + model_id = fields.Many2one( + comodel_name="ir.model", + string="Tracking Model Field", + ) + custom_tracking = fields.Boolean( + string="Custom Tracking", + compute="_compute_custom_tracking", + readonly=False, + store=True, + ) + native_tracking_field = fields.Integer( + string="Native Tracking", related="tracking_field_id.tracking" + ) + type_field = fields.Selection( + string="Kind Field", related="tracking_field_id.ttype" + ) + + @api.depends( + "tracking_field_id", + "tracking_field_id.readonly", + "tracking_field_id.related", + "tracking_field_id.store", + ) + def _compute_custom_tracking(self): + # No tracking on compute / readonly fields + # Custom tracking include native tracking attribute. + for rec in self: + field_id = rec.tracking_field_id + if ( + field_id.readonly + or field_id.related + or (field_id.compute and field_id.store and not field_id.readonly) + ) and not field_id.tracking: + rec.custom_tracking = False + else: + rec.custom_tracking = True diff --git a/tracking_manager/readme/CONTRIBUTORS.rst b/tracking_manager/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..dcae277c8c6 --- /dev/null +++ b/tracking_manager/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Kévin Roche diff --git a/tracking_manager/readme/DESCRIPTION.rst b/tracking_manager/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..dfaf4f60d7b --- /dev/null +++ b/tracking_manager/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module tracks all fields on every model that has a chatter, including one2many and many2many ones. This excludes the computed, readonly, related fields by default. +In addition, line changes of a one2many field are also tracked (e.g. product_uom_qty of an order_line in a sale order). diff --git a/tracking_manager/readme/USAGE.rst b/tracking_manager/readme/USAGE.rst new file mode 100644 index 00000000000..028de94f93f --- /dev/null +++ b/tracking_manager/readme/USAGE.rst @@ -0,0 +1,4 @@ +- In setting > models: select a model +- Check "Apply custom tracking on fields", all the non readonly, related, computed fields will be tracked. +- Button or smart button "Tracked Fields" allow to activate / deactivate custom_tracking on each field +- By default, all sub fields of a one2many field are tracked if they changed. To manage the sub fields, you need to activate "Apply custom tracking on fields" on the one2many model. Button "One2many related models" is a shortcut for these models. diff --git a/tracking_manager/security/ir.model.access.csv b/tracking_manager/security/ir.model.access.csv new file mode 100644 index 00000000000..b2739dd8708 --- /dev/null +++ b/tracking_manager/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_custom_tracking_model_field,tracking.model.field.user,model_tracking_model_field,base.group_user,1,1,1,1 diff --git a/tracking_manager/tests/__init__.py b/tracking_manager/tests/__init__.py new file mode 100644 index 00000000000..3e7e471c73b --- /dev/null +++ b/tracking_manager/tests/__init__.py @@ -0,0 +1 @@ +from . import test_tracking_manager diff --git a/tracking_manager/tests/test_tracking_manager.py b/tracking_manager/tests/test_tracking_manager.py new file mode 100644 index 00000000000..09ca4abcec6 --- /dev/null +++ b/tracking_manager/tests/test_tracking_manager.py @@ -0,0 +1,155 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests.common import SavepointCase + + +class TestTrackingManager(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Using the model res.partner for testing : + # - street field as regular field + # - parent_name as related field + # - category_id as many2many field + # - bank_ids as one2many field with acc_number field to test + # changes. + + cls.partner_1 = cls.env.ref("base.res_partner_1") + cls.partner_1.bank_ids = [(6, 0, cls.env.ref("base.bank_partner_demo").ids)] + cls.partner_model = cls.env["ir.model"].search( + [("model", "=", cls.partner_1._name)], limit=1 + ) + cls.bank_partner_2 = cls.env["res.partner.bank"].create( + { + "acc_number": "1234567890", + "partner_id": cls.partner_1.id, + } + ) + + def test_1_ir_model_config(self): + self.partner_model.apply_custom_tracking = True + # custom_tracking_field_ids + self.assertEqual( + len(self.partner_model.field_id), + len(self.partner_model.custom_tracking_field_ids), + ) + # o2m_model_ids + self.assertEqual( + len(self.partner_model.o2m_model_ids), + len(self.partner_model.field_id.filtered(lambda x: x.ttype == "one2many")), + ) + + def test_2_tracking_model_field_config(self): + self.partner_2 = self.env.ref("base.res_partner_2") + self.partner_1_w_parent = self.env.ref("base.res_partner_address_1") + self.partner_model.apply_custom_tracking = True + # Readonly, related are not tracked + parent_name = self.partner_model.custom_tracking_field_ids.filtered( + lambda x: x.name == "parent_name" + ) + self.assertFalse(parent_name.custom_tracking) + # Other fields are tracked + street = self.partner_model.custom_tracking_field_ids.filtered( + lambda x: x.name == "street" + ) + self.assertTrue(street.custom_tracking) + + def test_3_m2m_add_line(self): + initial_msg = self.partner_1.message_ids + self.partner_1.category_id = [ + (4, self.env.ref("base.res_partner_category_3").id) + ] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg), len(after_msg)) + + self.partner_model.apply_custom_tracking = True + self.partner_1.category_id = [ + (4, self.env.ref("base.res_partner_category_8").id) + ] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertTrue("New" in after_msg[0].body) + + def test_4_m2m_delete_line(self): + initial_msg = self.partner_1.message_ids + self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg), len(after_msg)) + + self.partner_model.apply_custom_tracking = True + self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertTrue("Delete" in after_msg[0].body) + + def test_5_m2m_multi_line(self): + initial_msg = self.partner_1.message_ids + self.partner_model.apply_custom_tracking = True + self.partner_1.category_id = [ + (3, self.partner_1.category_id[0].id), + (4, self.env.ref("base.res_partner_category_8").id), + (4, self.env.ref("base.res_partner_category_11").id), + ] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertEqual(after_msg[0].body.count("New"), 2) + self.assertEqual(after_msg[0].body.count("Delete"), 1) + + def test_6_o2m_add(self): + initial_msg = self.partner_1.message_ids + self.partner_model.apply_custom_tracking = True + self.partner_1.bank_ids = [(4, self.bank_partner_2.id)] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertTrue("New" in after_msg[0].body) + + def test_7_o2m_delete(self): + self.partner_model.apply_custom_tracking = True + initial_msg = self.partner_1.message_ids + self.partner_1.write({"bank_ids": [(3, self.partner_1.bank_ids[0].id)]}) + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertTrue("Delete" in after_msg[0].body) + + def test_8_o2m_change_in_line(self): + self.partner_1.bank_ids = [(6, 0, self.bank_partner_2.id)] + initial_msg = self.partner_1.message_ids + self.partner_model.apply_custom_tracking = True + self.partner_1.write( + { + "bank_ids": [(1, self.partner_1.bank_ids.id, {"acc_number": "123"})], + } + ) + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertTrue("Change" in after_msg[0].body) + # Restrict the tracking of acc_number + bank_model = self.env["ir.model"].search( + [("model", "=", self.bank_partner_2._name)], limit=1 + ) + bank_model.apply_custom_tracking = True + acc_number = bank_model.custom_tracking_field_ids.filtered( + lambda x: x.name == "acc_number" + ) + acc_number.custom_tracking = False + self.partner_1.write( + { + "bank_ids": [(1, self.partner_1.bank_ids.id, {"acc_number": "456"})], + } + ) + after_msg_2 = self.partner_1.message_ids + self.assertEqual(len(after_msg), len(after_msg_2)) + + def test_9_o2m_multi_line(self): + initial_msg = self.partner_1.message_ids + self.partner_model.apply_custom_tracking = True + self.partner_1.bank_ids = [ + (3, self.partner_1.bank_ids[0].id), + (4, self.bank_partner_2.id), + ] + after_msg = self.partner_1.message_ids + self.assertEqual(len(initial_msg) + 1, len(after_msg)) + self.assertEqual(after_msg[0].body.count("New"), 1) + self.assertEqual(after_msg[0].body.count("Delete"), 1) diff --git a/tracking_manager/views/ir_model.xml b/tracking_manager/views/ir_model.xml new file mode 100644 index 00000000000..3506602f1bf --- /dev/null +++ b/tracking_manager/views/ir_model.xml @@ -0,0 +1,67 @@ + + + + + tracking.ir.model form + ir.model + + + + + + + + + + + + + + + ir.model + tracking.ir.model.tree + + + + + + + + + diff --git a/tracking_manager/views/ir_model_fields.xml b/tracking_manager/views/ir_model_fields.xml new file mode 100644 index 00000000000..da550bc7edd --- /dev/null +++ b/tracking_manager/views/ir_model_fields.xml @@ -0,0 +1,19 @@ + + + + + tracking.model.field + tracking.model.field.tree + + + + + + + + + + + diff --git a/tracking_manager/views/message_template.xml b/tracking_manager/views/message_template.xml new file mode 100644 index 00000000000..be92743a760 --- /dev/null +++ b/tracking_manager/views/message_template.xml @@ -0,0 +1,52 @@ + + + + + From 357f61e19e59f3953e1e4fc18da09514a4904d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Fri, 23 Dec 2022 15:52:38 +0100 Subject: [PATCH 02/44] tracking_manager: remove useless file --- tracking_manager/models/security/ir.model.access.csv | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tracking_manager/models/security/ir.model.access.csv diff --git a/tracking_manager/models/security/ir.model.access.csv b/tracking_manager/models/security/ir.model.access.csv deleted file mode 100644 index 7a5bf98052d..00000000000 --- a/tracking_manager/models/security/ir.model.access.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_tracking_model,tracking.model.user,model_tracking_model,base.group_user,1,1,1,1 -access_tracking_model_field,tracking.model.field.user,model_tracking_model_field,base.group_user,1,1,1,1 From 73e9cf569c87449b5445c62aa7d68e880f12c12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Mon, 9 Jan 2023 09:57:21 +0100 Subject: [PATCH 03/44] tracking_manager: refactor code, remove tracking_model - remove tracking_model (less model, less code, less issue) - add automatic rule for default config (this avoid missing tracked field after module installation) --- tracking_manager/__manifest__.py | 5 +- tracking_manager/models/__init__.py | 1 - tracking_manager/models/ir_model.py | 193 ++++++++++-------- tracking_manager/models/mail_thread.py | 9 +- tracking_manager/models/tracking_model.py | 52 ----- tracking_manager/readme/CONTRIBUTORS.rst | 1 + tracking_manager/security/ir.model.access.csv | 2 - .../tests/test_tracking_manager.py | 89 ++++---- tracking_manager/views/ir_model.xml | 65 +++--- tracking_manager/views/ir_model_fields.xml | 34 ++- 10 files changed, 216 insertions(+), 235 deletions(-) delete mode 100644 tracking_manager/models/tracking_model.py delete mode 100644 tracking_manager/security/ir.model.access.csv diff --git a/tracking_manager/__manifest__.py b/tracking_manager/__manifest__.py index 9d4800b0a95..d3820f0ad1f 100644 --- a/tracking_manager/__manifest__.py +++ b/tracking_manager/__manifest__.py @@ -10,7 +10,7 @@ "category": "Tools", "website": "https://github.com/OCA/server-tools", "author": "Akretion, Odoo Community Association (OCA)", - "maintainers": ["Kev-Roche"], + "maintainers": ["Kev-Roche", "sebastienbeau"], "license": "AGPL-3", "application": False, "installable": True, @@ -19,9 +19,8 @@ "mail", ], "data": [ - "views/ir_model.xml", "views/ir_model_fields.xml", + "views/ir_model.xml", "views/message_template.xml", - "security/ir.model.access.csv", ], } diff --git a/tracking_manager/models/__init__.py b/tracking_manager/models/__init__.py index 3e5ef9b118b..430245fe750 100644 --- a/tracking_manager/models/__init__.py +++ b/tracking_manager/models/__init__.py @@ -1,3 +1,2 @@ from . import mail_thread -from . import tracking_model from . import ir_model diff --git a/tracking_manager/models/ir_model.py b/tracking_manager/models/ir_model.py index 5cc13fb07a0..ea1df8b0791 100644 --- a/tracking_manager/models/ir_model.py +++ b/tracking_manager/models/ir_model.py @@ -2,116 +2,129 @@ # @author Kévin Roche # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from ast import literal_eval + from odoo import api, fields, models class IrModel(models.Model): _inherit = "ir.model" - o2m_model_ids = fields.One2many( - string="One2many Models", - comodel_name="ir.model", - compute="_compute_o2m_model_ids", - readonly=True, - ) - custom_tracking_field_ids = fields.One2many( - comodel_name="tracking.model.field", - inverse_name="model_id", - string="Custom Tracked Fields", - compute="_compute_custom_tracking_fields", + active_custom_tracking = fields.Boolean(default=False) + tracked_field_count = fields.Integer(compute="_compute_tracked_field_count") + automatic_custom_tracking = fields.Boolean( + compute="_compute_automatic_custom_tracking", + readonly=False, store=True, - ) - field_count = fields.Integer(compute="_compute_tracked_field_count") - apply_custom_tracking = fields.Boolean( - "Apply custom tracking on fields", default=False, - help="""Add tracking on all this model fields if they - are not readonly True, neither computed.""", + help=("If tick new field will be automatically tracked " "if the domain match"), ) - - @api.depends("field_id", "apply_custom_tracking") - def _compute_custom_tracking_fields(self): - for rec in self: - fields = rec.env["ir.model.fields"].search( - [("model_id.model", "=", rec.model)] - ) - fields_ids = [] - for field in fields: - values = {} - values["tracking_field_id"] = field.id - values["model_id"] = rec.id - fields_ids.append((0, 0, values)) - rec.custom_tracking_field_ids = [(6, 0, [])] - rec.custom_tracking_field_ids = fields_ids - - @api.depends("field_id", "apply_custom_tracking") - def _compute_o2m_model_ids(self): - for rec in self: - o2m_model_list = [] - if rec.apply_custom_tracking: - for field in rec.field_id: - if field.ttype == "one2many": - o2m_relation_id = self.search( - [("model", "=", field.relation)], limit=1 - ) - o2m_model_list.append(o2m_relation_id.id) - rec.o2m_model_ids = [(6, 0, o2m_model_list)] - - @api.depends( - "custom_tracking_field_ids", "custom_tracking_field_ids.custom_tracking" + automatic_custom_tracking_domain = fields.Char( + compute="_compute_automatic_custom_tracking_domain", + store=True, + readonly=False, ) - def _compute_tracked_field_count(self): - for rec in self: - rec.field_count = sum( - rec.custom_tracking_field_ids.mapped("custom_tracking") - ) - def show_custom_tracked_field(self): - return { - "name": "Custom tracked fields", - "type": "ir.actions.act_window", - "res_id": self.id, - "view_mode": "tree", - "res_model": "tracking.model.field", - "view_id": self.env.ref( - "tracking_manager.custom_tracking_field_view_tree" - ).id, - "target": "current", - "domain": [("id", "in", self.custom_tracking_field_ids.ids)], - } + @api.depends("active_custom_tracking") + def _compute_automatic_custom_tracking(self): + for record in self: + record.automatic_custom_tracking = False - def show_o2m_models(self): + def _default_automatic_custom_tracking_domain_rules(self): return { - "name": "o2m models", - "type": "ir.actions.act_window", - "res_id": self.id, - "view_mode": "tree,form", - "res_model": "ir.model", - "views": [ - (self.env.ref("base.view_model_tree").id, "tree"), - (self.env.ref("base.view_model_form").id, "form"), + "product.product": [ + "|", + ("ttype", "!=", "one2many"), + ("name", "in", ["barcode_ids"]), ], - "target": "current", - "domain": [("id", "in", self.o2m_model_ids.ids)], + "sale.order": [ + "|", + ("ttype", "!=", "one2many"), + ("name", "in", ["line_ids"]), + ], + "account.move": [ + "|", + ("ttype", "!=", "one2many"), + ("name", "in", ["invoice_line_ids"]), + ], + "default_automatic_rule": [("ttype", "!=", "one2many")], } + @api.depends("automatic_custom_tracking") + def _compute_automatic_custom_tracking_domain(self): + rules = self._default_automatic_custom_tracking_domain_rules() + for record in self: + record.automatic_custom_tracking_domain = str( + rules.get(record.model) or rules.get("default_automatic_rule") + ) + + def update_custom_tracking(self): + for record in self: + fields = record.field_id.filtered("trackable").filtered_domain( + literal_eval(record.automatic_custom_tracking_domain) + ) + fields.write({"custom_tracking": True}) + untrack_fields = record.field_id - fields + untrack_fields.write({"custom_tracking": False}) + + @api.depends("field_id.custom_tracking") + def _compute_tracked_field_count(self): + for rec in self: + rec.tracked_field_count = len(rec.field_id.filtered("custom_tracking")) + class IrModelFields(models.Model): _inherit = "ir.model.fields" - custom_tracking_id = fields.Many2one( - comodel_name="tracking.model.field", - string="Technical Custom Tracking Field", - compute="_compute_custom_tracking_field_id", + custom_tracking = fields.Boolean( + compute="_compute_custom_tracking", + store=True, + readonly=False, + ) + native_tracking = fields.Boolean( + compute="_compute_native_tracking", + store=True, + ) + trackable = fields.Boolean( + compute="_compute_trackable", + store=True, ) - @api.depends("model_id.apply_custom_tracking") - def _compute_custom_tracking_field_id(self): - for rec in self: - if rec.model_id.apply_custom_tracking: - rec.custom_tracking_id = self.env["tracking.model.field"].search( - [("tracking_field_id", "=", rec.id), ("model_id", "=", rec._name)], - limit=1, - ) + @api.depends("native_tracking") + def _compute_custom_tracking(self): + for record in self: + if record.model_id.automatic_custom_tracking: + domain = literal_eval(record.model_id.automatic_custom_tracking_domain) + record.custom_tracking = bool(record.filtered_domain(domain)) else: - rec.custom_tracking_id = False + record.custom_tracking = record.native_tracking + + @api.depends("tracking") + def _compute_native_tracking(self): + for record in self: + record.native_tracking = bool(record.tracking) + + @api.depends("readonly", "related", "store") + def _compute_trackable(self): + blacklists = [ + "activity_ids", + "message_ids", + "message_last_post", + "message_main_attachment", + ] + for rec in self: + rec.trackable = ( + rec.name not in blacklists + and rec.store + and not rec.readonly + and not rec.related + ) + + def write(self, vals): + custom_tracking = None + if "custom_tracking" in vals: + self.check_access_rights("write") + custom_tracking = vals.pop("custom_tracking") + self._write({"custom_tracking": custom_tracking}) + self.invalidate_cache(["custom_tracking"]) + return super().write(vals) diff --git a/tracking_manager/models/mail_thread.py b/tracking_manager/models/mail_thread.py index 37ee8a12532..f976467ee23 100644 --- a/tracking_manager/models/mail_thread.py +++ b/tracking_manager/models/mail_thread.py @@ -8,6 +8,7 @@ class MailThread(models.AbstractModel): _inherit = "mail.thread" + # TODO invalidate ormcache @tools.ormcache("self.env.uid", "self.env.su") def _get_tracked_fields(self): res = super()._get_tracked_fields() @@ -21,12 +22,12 @@ def _get_custom_tracked_fields(self): self.env["ir.model"] .sudo() .search( - [("model", "=", self._name), ("apply_custom_tracking", "=", True)], + [("model", "=", self._name), ("active_custom_tracking", "=", True)], limit=1, ) ) if tracking_model: - track_fields = tracking_model.custom_tracking_field_ids.filtered( + track_fields = tracking_model.field_id.filtered( lambda f: f.custom_tracking ).mapped("name") return track_fields and set(self.fields_get(track_fields)) @@ -112,8 +113,8 @@ def _check_o2m_change_in_lines(self, field_id, vals): # tracking (apply_custom_tracking = True), we track # the changes only for fields with custom_tracking. if ( - line_model_id.apply_custom_tracking - and not line_field_id.custom_tracking_id.custom_tracking + line_model_id.active_custom_tracking + and not line_field_id.custom_tracking ): old = new = False elif line_field_id.ttype in ["one2many", "many2one", "many2many"]: diff --git a/tracking_manager/models/tracking_model.py b/tracking_manager/models/tracking_model.py deleted file mode 100644 index c888939765a..00000000000 --- a/tracking_manager/models/tracking_model.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2022 Akretion (). -# @author Kévin Roche -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models - - -class TrackingModelField(models.Model): - _name = "tracking.model.field" - _description = "Tracking Model Field" - - name = fields.Char(related="tracking_field_id.name") - tracking_field_id = fields.Many2one( - "ir.model.fields", - "Field", - ) - model_id = fields.Many2one( - comodel_name="ir.model", - string="Tracking Model Field", - ) - custom_tracking = fields.Boolean( - string="Custom Tracking", - compute="_compute_custom_tracking", - readonly=False, - store=True, - ) - native_tracking_field = fields.Integer( - string="Native Tracking", related="tracking_field_id.tracking" - ) - type_field = fields.Selection( - string="Kind Field", related="tracking_field_id.ttype" - ) - - @api.depends( - "tracking_field_id", - "tracking_field_id.readonly", - "tracking_field_id.related", - "tracking_field_id.store", - ) - def _compute_custom_tracking(self): - # No tracking on compute / readonly fields - # Custom tracking include native tracking attribute. - for rec in self: - field_id = rec.tracking_field_id - if ( - field_id.readonly - or field_id.related - or (field_id.compute and field_id.store and not field_id.readonly) - ) and not field_id.tracking: - rec.custom_tracking = False - else: - rec.custom_tracking = True diff --git a/tracking_manager/readme/CONTRIBUTORS.rst b/tracking_manager/readme/CONTRIBUTORS.rst index dcae277c8c6..7ee0b5b5e07 100644 --- a/tracking_manager/readme/CONTRIBUTORS.rst +++ b/tracking_manager/readme/CONTRIBUTORS.rst @@ -1 +1,2 @@ * Kévin Roche +* Sébastien BEAU diff --git a/tracking_manager/security/ir.model.access.csv b/tracking_manager/security/ir.model.access.csv deleted file mode 100644 index b2739dd8708..00000000000 --- a/tracking_manager/security/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_custom_tracking_model_field,tracking.model.field.user,model_tracking_model_field,base.group_user,1,1,1,1 diff --git a/tracking_manager/tests/test_tracking_manager.py b/tracking_manager/tests/test_tracking_manager.py index 09ca4abcec6..e91a9d39c94 100644 --- a/tracking_manager/tests/test_tracking_manager.py +++ b/tracking_manager/tests/test_tracking_manager.py @@ -28,35 +28,40 @@ def setUpClass(cls): } ) - def test_1_ir_model_config(self): - self.partner_model.apply_custom_tracking = True - # custom_tracking_field_ids - self.assertEqual( - len(self.partner_model.field_id), - len(self.partner_model.custom_tracking_field_ids), - ) - # o2m_model_ids - self.assertEqual( - len(self.partner_model.o2m_model_ids), - len(self.partner_model.field_id.filtered(lambda x: x.ttype == "one2many")), - ) + def _active_tracking(self, fields_list): + self.partner_model.active_custom_tracking = True + for field in self._get_fields(fields_list): + field.custom_tracking = True - def test_2_tracking_model_field_config(self): - self.partner_2 = self.env.ref("base.res_partner_2") - self.partner_1_w_parent = self.env.ref("base.res_partner_address_1") - self.partner_model.apply_custom_tracking = True - # Readonly, related are not tracked - parent_name = self.partner_model.custom_tracking_field_ids.filtered( - lambda x: x.name == "parent_name" + def _get_fields(self, fields_list): + return self.env["ir.model.fields"].search( + [ + ("model_id.model", "=", "res.partner"), + ("name", "in", fields_list), + ] ) - self.assertFalse(parent_name.custom_tracking) - # Other fields are tracked - street = self.partner_model.custom_tracking_field_ids.filtered( - lambda x: x.name == "street" - ) - self.assertTrue(street.custom_tracking) - def test_3_m2m_add_line(self): + def test_not_tracked(self): + self.partner_model.active_custom_tracking = True + field = self._get_fields(["category_id"])[0] + self.assertFalse(field.native_tracking) + self.assertFalse(field.custom_tracking) + + def test_native_tracked(self): + self.partner_model.active_custom_tracking = True + field = self._get_fields(["email"])[0] + self.assertTrue(field.native_tracking) + self.assertTrue(field.custom_tracking) + + def test_update_tracked(self): + self.partner_model.active_custom_tracking = True + field = self._get_fields(["category_id"])[0] + self.assertFalse(field.native_tracking) + self.partner_model.automatic_custom_tracking = True + self.partner_model.update_custom_tracking() + self.assertTrue(field.custom_tracking) + + def test_m2m_add_line(self): initial_msg = self.partner_1.message_ids self.partner_1.category_id = [ (4, self.env.ref("base.res_partner_category_3").id) @@ -64,7 +69,7 @@ def test_3_m2m_add_line(self): after_msg = self.partner_1.message_ids self.assertEqual(len(initial_msg), len(after_msg)) - self.partner_model.apply_custom_tracking = True + self._active_tracking(["category_id"]) self.partner_1.category_id = [ (4, self.env.ref("base.res_partner_category_8").id) ] @@ -72,21 +77,21 @@ def test_3_m2m_add_line(self): self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertTrue("New" in after_msg[0].body) - def test_4_m2m_delete_line(self): + def test_m2m_delete_line(self): initial_msg = self.partner_1.message_ids self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)] after_msg = self.partner_1.message_ids self.assertEqual(len(initial_msg), len(after_msg)) - self.partner_model.apply_custom_tracking = True + self._active_tracking(["category_id"]) self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)] after_msg = self.partner_1.message_ids self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertTrue("Delete" in after_msg[0].body) - def test_5_m2m_multi_line(self): + def test_m2m_multi_line(self): initial_msg = self.partner_1.message_ids - self.partner_model.apply_custom_tracking = True + self._active_tracking(["category_id"]) self.partner_1.category_id = [ (3, self.partner_1.category_id[0].id), (4, self.env.ref("base.res_partner_category_8").id), @@ -97,26 +102,26 @@ def test_5_m2m_multi_line(self): self.assertEqual(after_msg[0].body.count("New"), 2) self.assertEqual(after_msg[0].body.count("Delete"), 1) - def test_6_o2m_add(self): + def test_o2m_add(self): initial_msg = self.partner_1.message_ids - self.partner_model.apply_custom_tracking = True + self._active_tracking(["bank_ids"]) self.partner_1.bank_ids = [(4, self.bank_partner_2.id)] after_msg = self.partner_1.message_ids self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertTrue("New" in after_msg[0].body) - def test_7_o2m_delete(self): - self.partner_model.apply_custom_tracking = True + def test_o2m_delete(self): + self._active_tracking(["bank_ids"]) initial_msg = self.partner_1.message_ids self.partner_1.write({"bank_ids": [(3, self.partner_1.bank_ids[0].id)]}) after_msg = self.partner_1.message_ids self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertTrue("Delete" in after_msg[0].body) - def test_8_o2m_change_in_line(self): + def test_o2m_change_in_line(self): self.partner_1.bank_ids = [(6, 0, self.bank_partner_2.id)] initial_msg = self.partner_1.message_ids - self.partner_model.apply_custom_tracking = True + self._active_tracking(["bank_ids"]) self.partner_1.write( { "bank_ids": [(1, self.partner_1.bank_ids.id, {"acc_number": "123"})], @@ -129,10 +134,8 @@ def test_8_o2m_change_in_line(self): bank_model = self.env["ir.model"].search( [("model", "=", self.bank_partner_2._name)], limit=1 ) - bank_model.apply_custom_tracking = True - acc_number = bank_model.custom_tracking_field_ids.filtered( - lambda x: x.name == "acc_number" - ) + bank_model.active_custom_tracking = True + acc_number = bank_model.field_id.filtered(lambda x: x.name == "acc_number") acc_number.custom_tracking = False self.partner_1.write( { @@ -142,9 +145,9 @@ def test_8_o2m_change_in_line(self): after_msg_2 = self.partner_1.message_ids self.assertEqual(len(after_msg), len(after_msg_2)) - def test_9_o2m_multi_line(self): + def test_o2m_multi_line(self): initial_msg = self.partner_1.message_ids - self.partner_model.apply_custom_tracking = True + self._active_tracking(["bank_ids"]) self.partner_1.bank_ids = [ (3, self.partner_1.bank_ids[0].id), (4, self.bank_partner_2.id), diff --git a/tracking_manager/views/ir_model.xml b/tracking_manager/views/ir_model.xml index 3506602f1bf..7cb5cade898 100644 --- a/tracking_manager/views/ir_model.xml +++ b/tracking_manager/views/ir_model.xml @@ -10,40 +10,50 @@ - - - -