From 6045e69737996f0b22f03e1cd148e738349f4da7 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Mon, 25 May 2026 11:23:32 +0000 Subject: [PATCH 1/2] [FIX] tracking_manager fix none object at _tm_notify_owner --- tracking_manager/README.rst | 2 +- tracking_manager/__manifest__.py | 2 +- tracking_manager/i18n/es.po | 230 ++++++++++++++++++ .../pre-fix-none-trackable-field.py | 12 + .../migrations/14.0.1.1.1/post-migration.py | 21 ++ tracking_manager/models/ir_model.py | 40 +-- tracking_manager/models/models.py | 66 ++++- .../static/description/index.html | 13 +- tracking_manager/tools.py | 9 +- 9 files changed, 365 insertions(+), 30 deletions(-) create mode 100644 tracking_manager/i18n/es.po create mode 100755 tracking_manager/migrations/14.0.1.1.0/pre-fix-none-trackable-field.py create mode 100644 tracking_manager/migrations/14.0.1.1.1/post-migration.py diff --git a/tracking_manager/README.rst b/tracking_manager/README.rst index 4d1b267a531..761de014a81 100644 --- a/tracking_manager/README.rst +++ b/tracking_manager/README.rst @@ -7,7 +7,7 @@ Tracking Manager !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:6c896338ee53318978abf97b77d0ed909ea385ea96003de5630c67ecc459521b + !! source digest: sha256:21c9f7146785f3f48b472fed05f2ebd5e6d330b421bbb454300c3dfde16b0359 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/tracking_manager/__manifest__.py b/tracking_manager/__manifest__.py index af21ccdc01c..114ad5d0cc0 100644 --- a/tracking_manager/__manifest__.py +++ b/tracking_manager/__manifest__.py @@ -6,7 +6,7 @@ "name": "Tracking Manager", "summary": """This module tracks all fields of a model, including one2many and many2many ones.""", - "version": "14.0.1.0.2", + "version": "14.0.1.3.0", "category": "Tools", "website": "https://github.com/OCA/server-tools", "author": "Akretion, Odoo Community Association (OCA)", diff --git a/tracking_manager/i18n/es.po b/tracking_manager/i18n/es.po new file mode 100644 index 00000000000..6fc1de96e1c --- /dev/null +++ b/tracking_manager/i18n/es.po @@ -0,0 +1,230 @@ +# 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" +"PO-Revision-Date: 2024-04-20 16:39+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "Change :" +msgstr "Cambio :" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "Delete :" +msgstr "Eliminar:" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "New :" +msgstr " Nuevo: " + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Active" +msgstr "Activo" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__active_custom_tracking +msgid "Active Custom Tracking" +msgstr "Seguimiento Personalizado Activo" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__assigned_attachment_ids +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__assigned_attachment_ids +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__assigned_attachment_ids +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__assigned_attachment_ids +msgid "Assigned Attachments" +msgstr "Archivos Adjuntos Asignados" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__automatic_custom_tracking +msgid "Automatic Custom Tracking" +msgstr "Seguimiento Automático Personalizado" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__automatic_custom_tracking_domain +msgid "Automatic Custom Tracking Domain" +msgstr "Dominio de Seguimiento Automático Personalizado" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Automatic configuration" +msgstr "Configuración automática" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_base +msgid "Base" +msgstr "Base" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.track_o2m_m2m_template +msgid "Changed" +msgstr "Cambiado" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__changeset_change_ids +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__changeset_change_ids +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__changeset_change_ids +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__changeset_change_ids +msgid "Changeset Changes" +msgstr "Cambios en el Conjunto de Modificaciones" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__changeset_ids +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__changeset_ids +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__changeset_ids +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__changeset_ids +msgid "Changesets" +msgstr "Conjuntos de Cambios" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__count_pending_changeset_changes +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__count_pending_changeset_changes +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__count_pending_changeset_changes +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__count_pending_changeset_changes +msgid "Count Pending Changeset Changes" +msgstr "Recuento de Cambios Pendientes" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__count_pending_changesets +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__count_pending_changesets +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__count_pending_changesets +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__count_pending_changesets +msgid "Count Pending Changesets" +msgstr "Recuento de Cambios Pendientes" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__custom_tracking +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Custom Tracking" +msgstr "Seguimiento Personalizado" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_track_fields_search +msgid "Custom Tracking OFF" +msgstr "Seguimiento Personalizado OFF" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_track_fields_search +msgid "Custom Tracking ON" +msgstr "Seguimiento Personalizado ON" + +#. 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_mail_tracking_value__display_name +msgid "Display Name" +msgstr "Nombre para Mostrar" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Domain" +msgstr "Dominio" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de Correo Electrónico" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_ir_model_fields +msgid "Fields" +msgstr "Campos" + +#. 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_mail_tracking_value__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: tracking_manager +#: model:ir.model.fields,help:tracking_manager.field_ir_model__automatic_custom_tracking +msgid "If tick new field will be automatically tracked if the domain match" +msgstr "" +"Si se marca el nuevo campo se rastreará automáticamente si el dominio " +"coincide" + +#. 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_mail_tracking_value____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_mail_tracking_value +msgid "Mail Tracking Value" +msgstr "Valor de Seguimiento del Correo" + +#. module: tracking_manager +#: model:ir.model,name:tracking_manager.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__native_tracking +msgid "Native Tracking" +msgstr "Seguimiento Nativo" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__smart_search +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__smart_search +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__smart_search +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__smart_search +msgid "Smart Search" +msgstr "Búsqueda Inteligente" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__trackable +msgid "Trackable" +msgstr "Rastreable" + +#. module: tracking_manager +#: model:ir.actions.act_window,name:tracking_manager.ir_model_fields_action +msgid "Trackable Fields" +msgstr "Campos Rastreables" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__tracked_field_count +msgid "Tracked Field Count" +msgstr "Recuento de Campos Rastreados" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Tracked Fields" +msgstr "Campos Rastreados" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Update" +msgstr "Actualizar" + +#. module: tracking_manager +#: model_terms:ir.ui.view,arch_db:tracking_manager.view_model_form +msgid "Update fields configuration" +msgstr "Actualizar configuración de campos" + +#. module: tracking_manager +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model__user_can_see_changeset +#: model:ir.model.fields,field_description:tracking_manager.field_ir_model_fields__user_can_see_changeset +#: model:ir.model.fields,field_description:tracking_manager.field_mail_thread__user_can_see_changeset +#: model:ir.model.fields,field_description:tracking_manager.field_mail_tracking_value__user_can_see_changeset +msgid "User Can See Changeset" +msgstr "Usuario Puede ver Conjunto de Cambios" diff --git a/tracking_manager/migrations/14.0.1.1.0/pre-fix-none-trackable-field.py b/tracking_manager/migrations/14.0.1.1.0/pre-fix-none-trackable-field.py new file mode 100755 index 00000000000..8be153c0826 --- /dev/null +++ b/tracking_manager/migrations/14.0.1.1.0/pre-fix-none-trackable-field.py @@ -0,0 +1,12 @@ +#!/usr/bin/python3 +# pylint: disable=print-used + + +def migrate(cr, version): + cr.execute( + """ + UPDATE ir_model_fields + SET custom_tracking=False + WHERE trackable=False + """ + ) diff --git a/tracking_manager/migrations/14.0.1.1.1/post-migration.py b/tracking_manager/migrations/14.0.1.1.1/post-migration.py new file mode 100644 index 00000000000..7ebab64d1e9 --- /dev/null +++ b/tracking_manager/migrations/14.0.1.1.1/post-migration.py @@ -0,0 +1,21 @@ +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + + openupgrade.logged_query( + env.cr, + """ + UPDATE ir_model_fields SET trackable = false; + UPDATE ir_model_fields SET trackable = true + WHERE name NOT IN ( + 'activity_ids', + 'message_ids', + 'message_last_post', + 'message_main_attachment', + 'message_main_attachement_id' + ) + AND store AND related IS NULL; + """, + ) diff --git a/tracking_manager/models/ir_model.py b/tracking_manager/models/ir_model.py index 0fb0928209f..226b8924a59 100644 --- a/tracking_manager/models/ir_model.py +++ b/tracking_manager/models/ir_model.py @@ -100,21 +100,27 @@ def _compute_automatic_custom_tracking(self): def _default_automatic_custom_tracking_domain_rules(self): return { "product.product": [ + ("readonly", "=", False), "|", ("ttype", "!=", "one2many"), ("name", "in", ["barcode_ids"]), ], "sale.order": [ + ("readonly", "=", False), "|", ("ttype", "!=", "one2many"), ("name", "in", ["order_line"]), ], "account.move": [ + ("readonly", "=", False), "|", ("ttype", "!=", "one2many"), ("name", "in", ["invoice_line_ids"]), ], - "default_automatic_rule": [("ttype", "!=", "one2many")], + "default_automatic_rule": [ + ("ttype", "!=", "one2many"), + ("readonly", "=", False), + ], } @api.depends("automatic_custom_tracking") @@ -162,12 +168,12 @@ class IrModelFields(models.Model): store=True, ) - @api.depends("native_tracking") + @api.depends("native_tracking", "trackable") 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)) + record.custom_tracking = record.filtered_domain(domain).trackable else: record.custom_tracking = record.native_tracking @@ -176,22 +182,24 @@ def _compute_native_tracking(self): for record in self: record.native_tracking = bool(record.tracking) - @api.depends("readonly", "related", "store") + def _get_trackable_blacklist_models(self): + return [ + "mail.activity.mixin", + "mail.alias.mixin", + "mail.render.mixin", + "mail.thread", + "mail.thread.blacklist", + "mail.thread.cc", + ] + + @api.depends("related") def _compute_trackable(self): - blacklists = [ - "activity_ids", - "message_ids", - "message_last_post", - "message_main_attachment", - "message_main_attachement_id", + blacklisted_models = self._get_trackable_blacklist_models() + blacklisted_fields = [ + fname for model in blacklisted_models for fname in self.env[model]._fields ] for rec in self: - rec.trackable = ( - rec.name not in blacklists - and rec.store - and not rec.readonly - and not rec.related - ) + rec.trackable = rec.name not in blacklisted_fields and not rec.related def write(self, vals): custom_tracking = None diff --git a/tracking_manager/models/models.py b/tracking_manager/models/models.py index a6ecbd779c0..38470a1f424 100644 --- a/tracking_manager/models/models.py +++ b/tracking_manager/models/models.py @@ -48,6 +48,12 @@ def _tm_notify_owner(self, mode, changes=None): ) for field_name, owner_field_name in self._tm_get_fields_to_notify(): owner = self[field_name] + + # Robustness fix: Handle polymorphic fields (like res_id on mail.message) + # which return an integer instead of a BaseModel Recordset. + if not isinstance(owner, models.BaseModel) or not owner: + continue + data[owner._name][owner.id][owner_field_name].append( { "mode": mode, @@ -64,16 +70,64 @@ def _tm_get_changes(self, values): changes = [] for field_name, before in values.items(): field = self._fields[field_name] - if before != self[field_name]: + after = self[field_name] + + if before != after: if field.type == "many2many": - old = format_m2m(before) - new = format_m2m(self[field_name]) + before_records = before + after_records = after + + # Specific filters for product M2M fields to avoid false positives + # caused by the UI sending default/inherited values automatically. + if self._name in ("product.template", "product.product"): + if field_name == "route_ids": + # Ignore routes inherited from categories and warehouses + ignore_routes = self.env["stock.location.route"] + categ = ( + self.categ_id if hasattr(self, "categ_id") else False + ) + while categ: + ignore_routes |= categ.route_ids + categ = getattr(categ, "parent_id", False) + for wh in self.env["stock.warehouse"].search([]): + ignore_routes |= wh.route_ids + + before_records -= ignore_routes + after_records -= ignore_routes + + elif field_name == "taxes_id": + # Ignore company default sale taxes + company = self.company_id or self.env.company + if hasattr(company, "account_sale_tax_id"): + before_records -= company.account_sale_tax_id + after_records -= company.account_sale_tax_id + + elif field_name == "supplier_taxes_id": + # Ignore company default purchase taxes + company = self.company_id or self.env.company + if hasattr(company, "account_purchase_tax_id"): + before_records -= company.account_purchase_tax_id + after_records -= company.account_purchase_tax_id + + # If the manually selected records are identical, skip tracking + if set(before_records.ids) == set(after_records.ids): + continue + + old = format_m2m(before_records) + new = format_m2m(after_records) + + # Final safety check on formatted strings + if old == new: + continue + elif field.type == "many2one": - old = before.display_name - new = self[field_name]["display_name"] + # Safe extraction to prevent crashes if the relation is empty + old = before.display_name if before else "" + new = after.display_name if after else "" else: old = before - new = self[field_name] + new = after + changes.append( { "name": self._tm_get_field_description(field_name), diff --git a/tracking_manager/static/description/index.html b/tracking_manager/static/description/index.html index cc9506e5d22..f7879059f82 100644 --- a/tracking_manager/static/description/index.html +++ b/tracking_manager/static/description/index.html @@ -9,10 +9,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +276,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +302,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -367,7 +368,7 @@

Tracking Manager

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:6c896338ee53318978abf97b77d0ed909ea385ea96003de5630c67ecc459521b +!! source digest: sha256:21c9f7146785f3f48b472fed05f2ebd5e6d330b421bbb454300c3dfde16b0359 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

This module allows to track all fields on every model that has a chatter, including one2many and many2many ones. This excludes the computed, readonly, related fields by default. @@ -429,7 +430,9 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/tracking_manager/tools.py b/tracking_manager/tools.py index 1e1794d8061..a475f35269a 100644 --- a/tracking_manager/tools.py +++ b/tracking_manager/tools.py @@ -4,4 +4,11 @@ def format_m2m(records): - return "; ".join(records.mapped("display_name")) + # In some cases (I.E. account_asset/models/account_asset.py), _message_track + # is called with a dict of None values as initial_values. + # As a result, create_tracking_values would be called with None as initial_value + # and this format_m2m would be called with None as an argument. + # In such case, return "None" + if records: + return "; ".join(records.mapped("display_name")) + return "" From 65e200e6042f9b1a1e91c0b0e02c49b4ff4d846d Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Mon, 25 May 2026 11:39:27 +0000 Subject: [PATCH 2/2] [FIX] tracking_manager: Remove editable='bottom' attribute --- tracking_manager/views/ir_model_fields.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking_manager/views/ir_model_fields.xml b/tracking_manager/views/ir_model_fields.xml index 048d68b2028..fa638b24f4f 100644 --- a/tracking_manager/views/ir_model_fields.xml +++ b/tracking_manager/views/ir_model_fields.xml @@ -19,7 +19,7 @@ ir.model.fields - +