Saltar al contenido
Volver al blog
DesarrolloPythonORM

Relaciones entre modelos en Odoo: Many2one, One2many y Many2many sin liarte

Las tres relaciones que conectan tus datos, qué guardan de verdad en la base de datos y los comandos para escribirlas sin romper nada.

COConsultor Odoo29 de mayo de 20264 min de lectura

Forma parte de nuestra guía completa: Desarrollo de módulos en Odoo.

Un modelo aislado en Odoo es poca cosa: una tabla con sus campos y poco más. La potencia aparece cuando los conectas. Una propiedad pertenece a un vendedor, tiene varias ofertas y se etiqueta con varias categorías. Esas tres frases son, exactamente, las tres relaciones de Odoo: Many2one, One2many y Many2many. Suenan a jerga, pero en cuanto entiendes qué guarda cada una en la base de datos, dejan de dar miedo y empiezas a modelar como es debido.

El malentendido que arrastra casi todo el mundo es creer que las tres "guardan datos" igual. No. Y esa diferencia explica el 90% de los errores de rendimiento y de los "¿por qué no se borra esto?".

Many2one: la clave foránea de toda la vida

Es la relación reina, la que más usarás. "Esta propiedad tiene un vendedor." Por debajo es una columna en tu tabla con el id del registro relacionado. Nada exótico: una foreign key.

class Property(models.Model):
    _name = "estate.property"

    salesperson_id = fields.Many2one(
        "res.users", string="Vendedor",
        default=lambda self: self.env.user,
    )
    buyer_id = fields.Many2one("res.partner", string="Comprador")

Lo que mucha gente pasa por alto es ondelete. Por defecto, si borras el vendedor, el campo se pone a vacío (set null). Pero a veces quieres restrict (no me dejes borrar un vendedor que tiene propiedades) o cascade (si se va el padre, que se vayan los hijos). Decidir esto a conciencia evita registros huérfanos y sustos en producción.

One2many: un espejo, no una columna

Aquí está la trampa conceptual más importante de todo Odoo. Un One2many no guarda nada en tu tabla. Es la cara opuesta de un Many2one que vive en el otro modelo. Es, literalmente, una vista calculada: "dame todos los registros de allá que me apuntan a mí".

# En estate.property
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Ofertas")

# En estate.property.offer (¡aquí está la columna de verdad!)
property_id = fields.Many2one("estate.property", required=True, ondelete="cascade")

Por eso un One2many siempre necesita su Many2one inverso: sin esa columna en el otro lado, no hay nada que mirar. El error clásico del principiante es definir el One2many y esperar que funcione solo. No funciona, y el mensaje de error no siempre es obvio.

Many2many: la tabla intermedia que Odoo gestiona por ti

"Una propiedad tiene varias etiquetas, y una etiqueta está en varias propiedades." Eso es un Many2many, y por debajo Odoo crea una tabla intermedia (la relation table) sin que tú la veas.

tag_ids = fields.Many2many("estate.property.tag", string="Etiquetas")

Cómodo, pero con letra pequeña: las consultas sobre Many2many con muchos registros pueden volverse pesadas, y a veces lo que de verdad necesitas es un modelo intermedio explícito (un One2many a una tabla puente) porque quieres guardar datos sobre la relación —una fecha, un estado, un importe—. Cuando veas que tu Many2many "querría tener campos", esa es la señal de que necesitas un modelo intermedio.

Cómo se escriben las relaciones (esto vale oro)

Aquí es donde la documentación pierde a mucha gente. No escribes en un One2many o Many2many como en un campo normal; usas comandos. En Odoo moderno hay helpers legibles:

from odoo.fields import Command

property.write({
    "offer_ids": [
        Command.create({"price": 250000, "partner_id": partner.id}),  # crear y enlazar
        Command.unlink(old_offer.id),                                  # borrar el registro
    ],
    "tag_ids": [
        Command.set([tag_a.id, tag_b.id]),  # reemplazar el conjunto entero
        Command.link(tag_c.id),             # enlazar uno existente
    ],
})

Los verás también como tuplas crípticas —(0, 0, {...}), (4, id), (6, 0, [ids])— en código antiguo; son lo mismo. El día que entiendes que Command.set reemplaza y Command.link añade, dejas de duplicar etiquetas sin querer y de borrar relaciones que creías conservar.

Una vez tienes relaciones, puedes "traer" campos de un modelo a otro sin recalcular nada, con un campo related. Práctico para mostrar el dato del padre en el hijo:

buyer_phone = fields.Char(related="buyer_id.phone", string="Teléfono del comprador")

Es cómodo, pero recuerda lo que vimos sobre campos calculados y store: si vas a filtrar o agrupar por ese related, tendrás que valorar guardarlo. Lo barato sale caro cuando el listado tiene 50.000 filas.

Lo que separa un modelo de datos sano de uno enfermo

En auditorías, el patrón se repite: relaciones que deberían ser Many2one resueltas con un Char ("escribe aquí el nombre del cliente") que destroza la integridad; Many2many donde hacía falta un modelo intermedio; One2many sin ondelete que dejan basura por todas partes. Ninguno de esos errores se ve en la demo. Todos se ven a los dos años, cuando los datos ya están sucios y migrar duele.

Modelar bien las relaciones es, sin exagerar, la decisión técnica que más condiciona la vida de tu Odoo. Si arrancas un desarrollo a medida, esto se diseña antes de escribir la primera vista. Lo abordamos en la anatomía de un módulo y, cuando toca evolucionarlo sin romperlo, en la herencia en Odoo.

¿Tu modelo de datos ya te está dando guerra, o vas a empezar uno nuevo y quieres hacerlo bien a la primera? Cuéntanoslo en una sesión de diagnóstico o mira cómo enfocamos el desarrollo a medida.

#Desarrollo#Python#ORM
Compartir artículo

Comentarios (0)

Sé el primero en comentar.

Inicia sesión para dejar un comentario.

Acceder

Los comentarios se revisan antes de publicarse.

¿Listo para sacarle el máximo partido a Odoo?

Cuéntanos tu reto. En una primera llamada de 30 minutos te diremos cómo Odoo puede ayudarte, sin compromiso.