Skip to Content

16 - Computed Fields (@api.depends) and Inverse Functions

Welcome back! So far, we have been creating fields where the user manually types in the data. But what if you want the system to do the heavy lifting for you?

Think about a standard spreadsheet. If you have a column for "Quantity" and a column for "Unit Price," you don't manually type in the "Total." You write a formula: =Quantity * Price.

In Odoo 19, we do exactly this using Computed Fields. They allow you to calculate values dynamically based on other fields. Let’s learn how to make your models smart!


1. What is a Computed Field?

A computed field looks exactly like a normal basic field, but instead of the user entering data, you attach a Python function to it that calculates its value.

Let's look at a classic example: calculating the total area of a real estate property.

from odoo import models, fields, api

class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'Real Estate Property'

living_area = fields.Integer(string='Living Area (sqm)')
garden_area = fields.Integer(string='Garden Area (sqm)')
# We add the 'compute' attribute and give it the name of our function
total_area = fields.Integer(
string='Total Area (sqm)',
compute='_compute_total_area'
)

def _compute_total_area(self):
# ALWAYS loop through self in compute methods!
for record in self:
record.total_area = record.living_area + record.garden_area

By default, computed fields are Read-Only. The user cannot click on the total_area field and change it; Odoo handles it entirely.


2. The Magic (and Necessity) of @api.depends

If you run the code above, you will notice a huge problem. When you change the living_area, the total_area does not update immediately. You have to refresh the page to see the new total.

Why? Because Odoo doesn't know when it is supposed to run your calculation. You need to tell Odoo exactly which fields this calculation depends on. We do this using a Python decorator called @api.depends.

Let's fix our code:

@api.depends('living_area', 'garden_area')
def _compute_total_area(self):
for record in self:
record.total_area = record.living_area + record.garden_area

Now, the moment a user types a new number into living_area or garden_area on the form view, Odoo instantly triggers the _compute_total_area function and updates the total in real-time!


3. Storing Computed Fields (store=True)

Here is a crucial Odoo secret: By default, computed fields are NOT saved in your PostgreSQL database. Odoo calculates them "on the fly" whenever you open the view. This saves database space, but it creates a massive limitation: You cannot search, group, or filter by a field that isn't in the database.

If you want users to be able to search for "Properties with a Total Area greater than 200 sqm," you must tell Odoo to store it.

total_area = fields.Integer(
string='Total Area (sqm)',
compute='_compute_total_area',
store=True # Now it is saved in the database!
)

Note: When store=True, Odoo only recalculates and updates the database when the fields listed in @api.depends are changed. This makes it very efficient.


4. Inverse Functions: Making Computed Fields Editable

What if you want the best of both worlds? You want Odoo to calculate the field automatically, but you also want the user to be able to manually override it if they need to.

If a user manually types a value into a computed field, what should happen to the fields it depends on? We define this backward logic using an Inverse Function.

Imagine we are selling laptops. We have a price and a discount_percentage. We compute the final_price. But what if a salesperson wants to just type in a flat final_price and have Odoo automatically calculate the discount_percentage backwards?

class SaleItem(models.Model):
_name = 'sale.item'
_description = 'Sale Item'

price = fields.Float(string='Original Price')
discount_percentage = fields.Float(string='Discount (%)')
# We add the 'inverse' attribute
final_price = fields.Float(
string='Final Price',
compute='_compute_final_price',
inverse='_inverse_final_price',
store=True
)

@api.depends('price', 'discount_percentage')
def _compute_final_price(self):
for record in self:
discount_amount = record.price * (record.discount_percentage / 100)
record.final_price = record.price - discount_amount

def _inverse_final_price(self):
"""This runs when the user manually edits 'final_price'"""
for record in self:
if record.price > 0:
# Math to calculate the discount backwards
discount_amount = record.price - record.final_price
record.discount_percentage = (discount_amount / record.price) * 100

Now, the field is editable! If you change the discount, the final price updates. If you type a new final price, the discount updates. It works both ways perfectly.


💡 Developer Pro-Tips for Odoo 19

  1. Always assign a value: In a compute method, you must assign a value to the field for every single record in the loop. Even if a condition isn't met, assign False or 0. If you forget, Odoo 19 will throw a nasty ValueError: Compute method failed to assign screen at your users.

  2. @api.depends on Relational Fields: You can depend on fields inside linked models using dot notation! For example: @api.depends('partner_id.country_id').

  3. Be Careful with store=True: Don't just put store=True on every computed field. If a field calculates the user's age based on their birthdate, do not store it! If you store it, it will be correct today, but wrong next year (because the birthdate didn't change, so @api.depends won't trigger the update).

Homework: Create a simple model for a "Classroom". Add an expected_students integer field and an actual_students integer field. Then, create a computed field called attendance_rate (a Float field) that calculates the percentage. Make sure to handle the scenario where expected_students is zero so you don't get a "divide by zero" error!

Rating
0 0

There are no comments for now.

to be the first to leave a comment.