Here’s how you can add a chatter to an existing form (that doesn’t have one by default), and track changes on a One2many field — perfect for audit logs and traceable workflows!
✅ Step 1: Add Mail Inherit in Python
from odoo import models, fields, api, _
from markupsafe import Markup
class UoMCategoryExt(models.Model):
_name = 'uom.category'
_inherit = ['uom.category', 'mail.thread', 'mail.activity.mixin']
_description = 'Extended UoM Category'
✅ Step 2: Add Chatter in XML View
Just inherit the view and drop this inside your XML:
<record id="view_uom_category_form_inherit" model="ir.ui.view">
<field name="name">uom.category.form.inherit</field>
<field name="model">uom.category</field>
<field name="inherit_id" ref="uom.product_uom_categ_form_view"/>
<field name="arch" type="xml">
<xpath expr="//form/sheet" position="after">
<chatter/>
</xpath>
</field>
</record>
🔹 This injects the chatter panel (mail.thread) into forms where it wasn’t shown before.
✅ Step 3: Track Changes in One2many Field
Model 1
from odoo import models, fields, api, _
class UoMCategoryExt(models.Model):
_name = 'uom.category'
_inherit = ['uom.category', 'mail.thread', 'mail.activity.mixin']
_description = 'Extended UoM Category'
uom_ids = fields.One2many('uom.uom', 'category_id')
Model 2
class UoMExt(models.Model):
_name = 'uom.uom'
_inherit = ['uom.uom', 'mail.thread', 'mail.activity.mixin']
_description = 'Extended UoM'
category_id = fields.Many2one(
'uom.category', 'Category', required=True, ondelete='restrict', tracking=True,
help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios.")
✅ Step 4: Method to Show tracking in chatter
@api.model
def _get_tracked_fields(self):
"""Define which fields to track for custom messages"""
return ['factor', 'rounding', 'uom_type', 'name', 'category_id']
def write(self, vals):
old_values = {}
tracked_fields = self._get_tracked_fields()
# Store old values before write
for record in self:
old_values[record.id] = {}
for field in tracked_fields:
if field in vals:
old_values[record.id][field] = record[field]
# Perform the write operation
result = super().write(vals)
# Process changes for each record
for record in self:
if record.id in old_values:
changes = []
for field in tracked_fields:
if field in vals:
old_val = old_values[record.id][field]
new_val = record[field]
# Handle different field types for display
if record._fields[field].type == 'many2one':
old_val_display = old_val.name if old_val else _("None")
new_val_display = new_val.name if new_val else _("None")
elif record._fields[field].type == 'selection':
selection_dict = dict(record._fields[field].selection)
old_val_display = selection_dict.get(old_val, old_val)
new_val_display = selection_dict.get(new_val, new_val)
else:
old_val_display = old_val if old_val is not False else _("None")
new_val_display = new_val if new_val is not False else _("None")
# Check if value actually changed
if old_val != new_val:
field_label = record._fields[field].string
changes.append((field_label, old_val_display, new_val_display))
# Create message if there are changes
if changes:
# Build simple message
msg = f"{_('')}"
for field_label, old_val, new_val in changes:
msg += f"• {field_label}: {old_val} → {new_val}"
# Post to category
record.category_id.message_post(
body=f'{record._description}: {record.display_name} {msg}',
subtype_xmlid='mail.mt_comment'
)
return result
This gives you a clean change history, even for complex relational fields.
💡 Visit Developerspro for more real-world Odoo Dev Tips. Let us know in the comments!
Add Chatter to Existing Odoo View + Add Tracking on One2many Fields