Odoo · Arquitectura · Herencia
Odoo ofrece dos mecanismos de herencia que suenan parecidos pero funcionan de forma muy distinta: _inherit extiende un modelo existente; _inherits delega en otro. Elegir mal es una de las fuentes más habituales de bugs y deuda técnica.
Conceptos
Dos palabras, dos mecanismos
_inherit | _inherits | |
|---|---|---|
| Qué hace | Extiende un modelo existente (clásico) | Delega campos y comportamiento en otro modelo (composición) |
| Tabla en BD | Una sola tabla (la del padre) | Dos tablas enlazadas por Many2one |
_name | Opcional (mismo nombre que el padre) | Obligatorio (nombre nuevo del modelo) |
| Ejemplo core | Módulos que añaden campos a sale.order | product.product → product.template |
| Cuándo usarlo | Añadir campos/métodos a un modelo estándar | Crear entidad nueva que «es» otra cosa a la vez |
Regla mnemotécnica:
_inherit= «quiero más del mismo»;_inherits= «quiero un modelo nuevo que reutilice otro por dentro».
_inherit
Extensión clásica con _inherit
Es el patrón más usado en módulos custom. No creas un modelo nuevo: modificas uno existente. Todos los módulos que heredan el mismo modelo encadenan sus cambios gracias a super().
# contact_extension/models/res_partner.py
from odoo import api, fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
birth_date = fields.Date(string='Birth Date')
age = fields.Integer(compute='_compute_age', store=True)
@api.depends('birth_date')
def _compute_age(self):
today = fields.Date.today()
for partner in self:
partner.age = (
today.year - partner.birth_date.year
if partner.birth_date else 0
)
El registro sigue siendo un res.partner. La tabla es res_partner. Las vistas del contacto muestran los nuevos campos si añades un XML con xpath.
Variantes de _inherit
- Solo
_inherit: extiendes el modelo sin cambiar su_name _inherit+_namedistinto: reemplazas/renombras (poco habitual)- Lista de mixins:
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherits
Delegación con _inherits
Creas un modelo nuevo cuyos campos del padre delegado aparecen como si fueran propios. En base de datos hay dos registros vinculados: uno en tu tabla y otro en la del padre.
_inherits es el que usa Odoo en productos, usuarios y otros modelos compuestos.# Ejemplo simplificado al estilo product.product / product.template
class ProductVariant(models.Model):
_name = 'product.variant.custom'
_description = 'Product Variant'
_inherits = {{'product.template': 'template_id'}}
template_id = fields.Many2one(
'product.template', string='Template',
required=True, ondelete='cascade',
)
sku_suffix = fields.Char(string='SKU Suffix')
# Accedes a template_id.name como si fuera self.name
# El ORM delega lectura/escritura al registro padre
Al crear un product.variant.custom, Odoo crea también el product.template asociado. Al borrar el hijo con ondelete='cascade', se elimina el padre.
res.users hereda de res.partner vía _inherits. El usuario es un partner con campos extra de login.
Decisión
¿Cuándo usar cada uno?
Usa _inherit si…
- Añades campos a un modelo estándar
- Sobrescribes métodos (
action_confirm, etc.) - Añades mixins (chatter, actividades)
- No necesitas identidad de modelo separada
Usa _inherits si…
- Tu entidad es conceptualmente «un X con extras»
- Necesitas tabla y permisos propios
- Quieres reutilizar campos del padre sin duplicarlos
- El ciclo de vida del padre está acoplado al hijo
Ejemplo de dominio: un diagnostic.test.subject (perfil del menor) no debería ser un res.partner vía _inherits si buscas privacidad RGPD — mejor modelo separado con relación Many2one. La herencia por delegación une demasiado el ciclo de vida.
Errores
Errores habituales
| Error | Consecuencia | Solución |
|---|---|---|
Usar _inherits para «añadir un campo» | Dos tablas, joins extra, permisos duplicados | Usar _inherit |
Olvidar super() en métodos sobrescritos | Rompe otros módulos en la cadena | Siempre llamar a super() |
ondelete incorrecto en el Many2one padre | Huérfanos o borrados en cascada inesperados | cascade o restrict según negocio |
Confundir _inherit con herencia Python | Expectativas incorrectas sobre MRO | Pensar en cadena de módulos Odoo |
Crear _name nuevo sin necesidad | Vistas, menús y ACL duplicados | Extender con _inherit solo |
⚠️ Trampa frecuente
_inherit = 'sale.order' y uno omite super().action_confirm(), el pedido puede confirmarse sin ejecutar lógica de otros módulos (stock, facturación, integraciones).Práctica
Checklist antes de elegir
- ¿El modelo ya existe en el core o en otro módulo? → probablemente
_inherit - ¿Necesito una entidad nueva con identidad propia en menús/informes? → valora
_inheritso modelo +Many2one - ¿Los datos del padre deben vivir/v morir con el hijo? →
_inheritsconondelete='cascade' - ¿Solo añado campos y lógica? →
_inheritsin dudar - ¿Privacidad o separación de dominio? → modelo independiente + relación, no delegación
Resumen
_inherit extiende; _inherits delega. El 90% de los módulos custom solo necesitan _inherit. Reserva _inherits para composición real — como productos o usuarios — y cuando el modelo hijo tenga sentido de negocio propio. Si dudas, empieza con _inherit o un Many2one explícito: siempre podrás refactorizar con tests que te respalden.