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