Campos calculados, onchange y constraints: la lógica que hace que Odoo «piense»
El trío que convierte un formulario en un sistema con criterio. Cuándo usar cada uno, las trampas más comunes y por qué store=True no es una decisión menor.
Forma parte de nuestra guía completa: Desarrollo de módulos en Odoo.
Un formulario que solo guarda lo que tecleas no es gran cosa: eso lo hace cualquier hoja de cálculo. Lo que convierte a Odoo en un sistema de gestión es la capa que hay debajo, la que calcula totales, sugiere valores mientras escribes e impide que se guarde un disparate. Esa capa se sostiene sobre tres piezas que la gente confunde constantemente: campos calculados, onchange y constraints. No hacen lo mismo, no se ejecutan en el mismo momento y elegir mal entre ellas es el origen de la mitad de los "bugs raros" que vemos en auditorías.
El campo calculado: el valor que se deduce solo
Un campo calculado no se rellena: se deriva de otros. El precio total a partir de cantidad y precio unitario, el margen a partir de coste y venta, la antigüedad a partir de una fecha. Lo defines con un compute y le dices de qué depende:
from odoo import models, fields, api
class Property(models.Model):
_inherit = "estate.property"
bedrooms = fields.Integer()
living_area = fields.Float()
total_area = fields.Float(compute="_compute_total_area", store=True)
@api.depends("living_area", "bedrooms")
def _compute_total_area(self):
for record in self:
record.total_area = record.living_area + record.bedrooms * 10
Dos detalles que no son negociables. Primero, el for record in self: un método trabaja siempre sobre un conjunto de registros, no sobre uno. Olvidarlo es el error número uno de quien viene de otros frameworks. Segundo, el @api.depends: es lo que le dice a Odoo cuándo recalcular. Si te dejas una dependencia, el campo se queda obsoleto y nadie entiende por qué.
store=True o no: la decisión que más se equivoca
Aquí está el matiz que separa al que ha leído un tutorial del que ha mantenido un sistema en producción. Un campo calculado puede guardarse en la base de datos (store=True) o calcularse al vuelo cada vez que se lee.
Si no lo guardas, no puedes filtrar ni agrupar por él, ni usarlo en un informe pesado sin pagar el cálculo una y otra vez. Si lo guardas, ganas velocidad y la posibilidad de buscar por él… pero te comprometes a que el @api.depends esté impecable, porque ese valor persiste. Mi regla, después de unos cuantos sustos: guarda solo lo que de verdad vas a filtrar, agrupar o mostrar en listados grandes. Lo demás, al vuelo.
onchange: ayuda al usuario, no valida nada
El onchange reacciona mientras el usuario edita el formulario, antes de guardar. Sirve para sugerir: cambias el cliente y se rellena su dirección, marcas una casilla y aparece un campo. Es pura experiencia de usuario.
@api.onchange("expected_price")
def _onchange_expected_price(self):
if self.expected_price and self.expected_price < 100000:
return {
"warning": {
"title": "Precio bajo",
"message": "¿Seguro? Está por debajo de mercado.",
}
}
Y aquí viene la trampa que cuesta dinero: el onchange solo se dispara en la interfaz. Si los datos entran por una importación, por la API o desde otro módulo, tu onchange ni se entera. Lo he visto demasiadas veces: alguien mete una regla de negocio crítica en un onchange, funciona perfecto en las pruebas a mano, y meses después descubre que la importación nocturna lleva saltándosela desde el principio. El onchange ayuda; no protege.
Constraints: la red de seguridad que sí protege
Lo que protege son las restricciones, y vienen en dos sabores. Las SQL las aplica directamente PostgreSQL, son rapidísimas y perfectas para reglas simples como "esto no puede ser negativo" o "esto es único":
_sql_constraints = [
("check_expected_price", "CHECK(expected_price > 0)",
"El precio esperado debe ser positivo."),
]
Las Python entran cuando la regla necesita lógica de verdad —comparar campos, consultar otros registros, condicionar por estado—:
from odoo.exceptions import ValidationError
@api.constrains("expected_price", "selling_price")
def _check_selling_price(self):
for record in self:
if record.selling_price and record.selling_price < record.expected_price * 0.9:
raise ValidationError(
"El precio de venta no puede ser un 10% inferior al esperado."
)
La diferencia con el onchange es la clave de todo: una constraint se ejecuta siempre, da igual si el dato entró por el formulario, por la API o por una importación masiva. Si una regla tiene que cumplirse sí o sí, vive en una constraint. Punto.
La regla mental que uso
Cuando dudo dónde meter una pieza de lógica, me hago tres preguntas en orden: ¿es un valor derivado de otros? → campo calculado. ¿Es una ayuda visual mientras el usuario teclea? → onchange. ¿Es una regla que no se puede saltar nunca? → constraint. La mayoría de los errores que arreglamos son lógica de negocio que alguien metió en un onchange cuando debía ser una constraint, o campos guardados con un @api.depends incompleto.
Dominar este trío es lo que diferencia un módulo que "funciona en la demo" de uno en el que puedes confiar el día que entran 10.000 registros de golpe. Si te suena el patrón del "bug que solo aparece a veces", probablemente esté aquí. En una revisión de tu código lo localizamos rápido, o míralo dentro de nuestro servicio de desarrollo a medida. Y si quieres la base sólida, empieza por la anatomía de un módulo.
Comentarios (0)
Sé el primero en comentar.
Inicia sesión para dejar un comentario.
AccederLos comentarios se revisan antes de publicarse.
Artículos relacionados
Acciones automatizadas en Odoo: deja que el ERP haga el trabajo aburrido
Acciones de servidor, reglas de automatización y tareas planificadas: las tres piezas para que Odoo reaccione, avise y ejecute solo, sin que nadie se acuerde.
Informes PDF con QWeb en Odoo: del formulario al documento que cierra ventas
QWeb es HTML que se convierte en PDF. Te enseñamos a montar un informe, recorrer registros con t-foreach y por qué t-field te ahorra mil dolores de cabeza.
Herencia en Odoo: la función que decide si tu código sobrevive a las actualizaciones
Extensión clásica, delegación y herencia de vistas. Las tres formas de modificar Odoo sin tocar el core —y por qué heredar en vez de copiar te ahorra cada migración.