Ir al contenido

Campos calculados en Odoo: store=True, @api.depends y trampas

Ciclos de dependencia, invalidación y campos que no se actualizan
17 de junio de 2026 por
Campos calculados en Odoo: store=True, @api.depends y trampas
Aitor Atencia

Odoo · Campos · ORM

Los campos compute son la herramienta más usada — y más mal configurada — en módulos Odoo. Un @api.depends incompleto o un store=True mal planteado provoca valores obsoletos, búsquedas rotas y tests intermitentes.

Logo Odoo Diagrama de flujo
Un campo compute define un grafo de dependencias que el ORM debe invalidar correctamente.

Tipos

Compute, related y almacenados

TipoDefiniciónSe guarda en BDBuscable / agrupable
fields.Char() normalValor directo
compute sin storeCalculado al leerNoNo
compute + store=TrueCalculado y persistidoSí (con índice si hace falta)
related='campo'Alias de otro campoOpcional (store=True)Si está almacenado

Sin store=True, el valor se recalcula en cada lectura. Con store=True, Odoo lo escribe en PostgreSQL y lo invalida cuando cambian las dependencias declaradas en @api.depends.

Básico

Anatomía de un campo compute

from odoo import api, fields, models

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    line_count = fields.Integer(
        string='Line Count',
        compute='_compute_line_count',
        store=True,
    )

    @api.depends('order_line')
    def _compute_line_count(self):
        for order in self:
            order.line_count = len(order.order_line)

Reglas mínimas:

  • El método compute se llama _compute_<nombre_campo>
  • Asigna todos los registros del recordset (for rec in self)
  • @api.depends debe listar cada campo leído dentro del método
  • Con store=True, añade dependencias de campos en registros relacionados con notación punto

store=True

¿Cuándo almacenar el resultado?

Usa store=True si…

  • Filtras o agrupas por el campo en vistas o search
  • El cálculo es costoso y se lee a menudo
  • Exportas o reportas sobre ese valor
  • Necesitas índice SQL para rendimiento

Evita store=True si…

  • El valor cambia constantemente (fecha/hora actual)
  • Depende de datos externos no rastreables
  • Es un simple related sin lógica extra
  • El campo casi nunca se busca ni agrupa
# Depende de campos del partner vinculado
@api.depends('partner_id', 'partner_id.country_id', 'partner_id.country_id.code')
def _compute_is_export(self):
    for order in self:
        code = order.partner_id.country_id.code
        order.is_export = code and code != 'ES'

depends

@api.depends — la fuente de la verdad

Odoo solo recalcula un campo almacenado cuando cambia un campo listado en depends. Si lees line.price_subtotal pero solo declaras order_line, el total puede quedar desactualizado al cambiar el precio de una línea.

Lees en el computeDeclara en depends
order.order_line'order_line'
line.product_uom_qty'order_line.product_uom_qty'
partner.country_id.code'partner_id.country_id.code'
Campo de otro compute almacenadoIncluye ese campo o sus dependencias
# ❌ Trampa: falta dependencia profunda
@api.depends('order_line')
def _compute_amount_custom(self):
    for order in self:
        order.amount_custom = sum(order.order_line.mapped('price_subtotal'))

# ✅ Correcto
@api.depends('order_line.price_subtotal')
def _compute_amount_custom(self):
    ...

Ciclos

Ciclos de dependencia

Dos campos almacenados que dependen uno del otro generan un ciclo. Odoo puede fallar al instalar el módulo o entrar en recomputes infinitos.

# ❌ Ciclo: total depende de discount, discount depende de total
total = fields.Float(compute='_compute_total', store=True)
discount = fields.Float(compute='_compute_discount', store=True)

@api.depends('discount')
def _compute_total(self): ...

@api.depends('total')
def _compute_discount(self): ...

Solución habitual: un solo campo almacenado «fuente» y el otro sin store, o extraer la lógica a un método privado sin dependencia circular.

Vista formulario Odoo
Un campo obsoleto en formulario suele ser un depends incompleto, no un bug de la vista.

Trampas

Cuatro trampas que ves en producción

SíntomaCausa probableArreglo
Valor correcto en form, mal en listaCompute sin store + caché del navegadorstore=True o forzar recomputo
No se actualiza tras write en relaciónFalta ruta en dependsAñadir line_id.campo
search devuelve registros «viejos»Campo almacenado desactualizadorecords._compute_campo() o fix depends + -u
Tests flakyOrden de creación / flush pendienteflush_recordset() o asserts tras write

Avanzado

inverse, compute_sudo y prefetch

display_total = fields.Monetary(
    compute='_compute_display_total',
    inverse='_inverse_display_total',
    store=True,
)

@api.depends('order_line.price_total')
def _compute_display_total(self):
    for order in self:
        order.display_total = sum(order.order_line.mapped('price_total'))

def _inverse_display_total(self):
    # Distribuir manualmente el total editado en las líneas
    for order in self:
        ...

compute_sudo=True recalcula con permisos de superusuario — útil para campos que leen datos restringidos (p. ej. coste en producto), pero úsalo con criterio: puede exponer datos en vistas si no aplicas groups=.

En bucles sobre muchos registros, evita search dentro del compute. Prefiere mapped, read_group o precargar con depends bien definidos.

Checklist

Antes de hacer merge

  1. ¿Necesito buscar/agrupar por este campo? → store=True
  2. ¿depends cubre todos los campos leídos, incluidos los de relaciones?
  3. ¿Hay ciclo con otro compute almacenado?
  4. ¿El usuario edita el valor? → inverse o campo no compute
  5. ¿Test que cree, modifique relación y verifique el valor recalculado?

Resumen

Los campos compute son potentes cuando @api.depends es exhaustivo y store=True se reserva para campos buscables o costosos. La mayoría de «bugs de vista» son dependencias incompletas o ciclos entre campos almacenados. Declara el grafo completo, testea tras write en relaciones y evita escribir computes sin inverse.

en Odoo
ORM de Odoo en profundidad: search, browse, read y search_read
Rendimiento, cuándo usar cada método y cómo evitar el problema N+1