Header Ads

Mastering Computed Fields with Smart Triggers || @api.depends

 

How @api.depends work, what is this?

In the world of Odoo development, one of the most elegant and powerful features is the computed field. These fields don't store their values directly in the database (unless specified); instead, their values are dynamically calculated based on other data. But how does Odoo know when to perform these calculations? Enter the unsung hero: the @api.depends decorator.
If you've ever 
constructed a form in Odoo where modifying one field automatically updates another (such as a total automatically recalculating when you alter a quantity), then you've seen @api.depends in use. It's the way your Odoo applications always have consistent and current derived data displayed.
So 
let's get started!


What is @api.depends?
Essentially
, @api.depends(*fields) is a decorator you put right above a Python method of your Odoo model. This method will be assigned to be the "compute" function for a fields.Computed field (or implicitly for fields.Related fields).


Its 
job is straightforward but deep: It tracks dependencies. You are literally saying to Odoo: "This specific computed field's value depends on these particular source fields. If any of those source fields are modified, please re-execute my computation method to obtain the new value."
Imagine
 it as if it were a spreadsheet formula. When you modify a number in cell A1, and cell C1 contains a formula such as =A1+B1, cell C1 will update automatically. In Odoo, @api.depends is the way you declare what "cells" (fields) are included in your "formula."

How Does it Work Under the Hood?

When a field is changed by a user either through the UI or programmatic, Odoo's ORM (Object-Relational Mapper) is always listening.


1
.\tChange Detection: The ORM listens and notices that the value of a field has changed.
2
.\tDependency Lookup: It looks into its internal registry and checks whether any computed fields have specified this changed field in their @api.depends decorator.


3. Queueing for Re-computation: 
When a dependency is detected, Odoo schedules the respective compute method for execution.


4. Calculation & Update: The compute method 
is executedcomputes the new value, and sets it on the computed field.


5. Persistence (Optional): If the computed field is 
specified with store=True, the newly calculated value is saved back into the database too. If store=False (default), the value is computed on-the-fly when needed but not saved persistently.


Why @api.depends is Indispensable?
• Automatic Data Consistency: 

• Automatic Data Consistency: It's the biggest benefit. Your computed data is always up-to-date with its source. No stale totals or outdated statuses anymore because of hand errors or omitted updates.


• 
Less Manual Logic: You don't have to implement complex onchange methods for each and every field which can influence a computation.

 @api.depends takes care of change propagation automatically.


•\tCleaner
 Code: By isolating the computation logic from the field definitions, your model code is more modular and easier to read. The dependencies are explicitly declaredso it is clear why a field is re-calculating.


•\tOptimized
 Performance: Odoo's ORM is intelligent. It queues re-computations only for fields whose dependencies have truly changed, not doing unnecessary calculations.


Real-World Examples in Action

Let us see how @api.depends gives dynamic power to your Odoo models.

Example 1: Simple Calculation on the Same Model

A classic scenario: calculating a total_amount from quantity and price_unit on a sales line.

Python

from odoo import models, fields, api
 
class SaleOrderLine(models.Model):
    _name = 'sale.order.line'
    _description = 'Sales Order Line'
 
    quantity = fields.Float(string="Quantity", default=1.0)
    price_unit = fields.Float(string="Unit Price", default=0.0)
 
    total_amount = fields.Float(
        string="Total",
        compute='_compute_total_amount',
        store=True # Store in DB so it's searchable and reports are fast
    )
 
    @api.depends('quantity', 'price_unit') # If quantity OR price_unit changes, recompute total_amount
    def _compute_total_amount(self):
        for line in self:
            line.total_amount = line.quantity * line.price_unit

In this example, as soon as quantity or price_unit is changed (and the record saved or an onchange triggered), total_amount will update.

Example 2: Aggregation on One2many Records

This is incredibly common for summing up line items to get a grand total on a parent document (like a Sale Order's total from its lines).

Python

from odoo import models, fields, api
 
class SaleOrder(models.Model):
    _name = 'sale.order'
    _description = 'Sales Order'
 
    # ... other fields for the main order ...
    partner_id = fields.Many2one('res.partner', string="Customer")
 
    # One2many relation to the order lines
    order_line_ids = fields.One2many('sale.order.line', 'order_id', string='Order Lines')
 
    # Computed field for the grand total
    amount_total = fields.Float(
        string="Total Amount",
        compute='_compute_amount_total',
        store=True # Store the grand total for reporting/analysis
    )
 
    @api.depends('order_line_ids.total_amount') # <--- KEY SYNTAX HERE!
    def _compute_amount_total(self):
        """ Computes the sum of total_amount from all order lines. """
        for order in self:
            order.amount_total = sum(line.total_amount for line in order.order_line_ids)

Notice the syntax @api.depends('order_line_ids.total_amount'). When depending on a field of a Many2many or One2many, you must specify one2many_field_name.field_on_child_model. Odoo will monitor changes to the total_amount field on any line related to this SaleOrder record.

Example 3: Leveraging Related Fields

While not directly a compute method, fields.Related is a special type of computed field. It simply pulls a value from a linked record. It's implicitly stored if the source field is stored.

Python

from odoo import models, fields
 
class SaleOrder(models.Model):
    _name = 'sale.order'
    _description = 'Sales Order'
 
    partner_id = fields.Many2one('res.partner', string="Customer")
 
    # This field automatically gets the partner's phone number
    partner_phone = fields.Char(
        string="Customer Phone",
        related='partner_id.phone', # Pulls 'phone' from the linked 'res.partner' record
        readonly=True # Related fields are typically read-only
    )

If partner_id.phone changes, partner_phone will automatically reflect the new value. If you had another computed field that depended on partner_phone, you would list it in its @api.depends decorator.

The Pitfall: Overlooked Dependencies 

The most frequent error when using computed fields is omitting all their dependencies from the @api.depends decorator.


What 
goes wrong?
Your computed field will simply not 
get recalculated when the unlisted source field changes. This causes stale data and annoying bugs that can be difficult to track downsince the field may seem to work at times but then fail at others.


Debugging Tip: 
When a computed field is not updating, the first thing to look at is its @api.depends decorator. Make sure all fields the compute method is calculating against (including fields on related models) are included in the list.


The @api.depends decorator is a 
tiny snippet of code that gives you incredible power and reliability in your Odoo modules. Using it, you can create dynamic, solid, and stable applications that instantly maintain data as fresh and accurate as possible

Learn to use it, know its subtleties, and your Odoo development experience will be much smoother.

For odoo services please visit  www.odoie.com

No comments

Powered by Blogger.