Odoo · ORM · Buenas prácticas
Un self.env["ir.model.data"]._xmlid_to_res_id(...) hardcodeado con un ID numérico (browse(37)) funciona en tu base de datos de desarrollo y explota en la del cliente. Los external IDs y env.ref() existen precisamente para evitar ese tipo de sorpresa.
El problema de fondo
Por qué un ID numérico hardcodeado es una bomba de tiempo
El campo id de cualquier registro en PostgreSQL depende del orden en que se insertó ese dato en esa base de datos concreta. El mismo módulo instalado en dos instancias distintas (desarrollo, staging, producción, o la instancia de otro cliente) puede asignar IDs numéricos completamente distintos al mismo dato de referencia.
# Mal: el ID 37 puede no existir, o ser otro registro completamente distinto
categoria = self.env["res.partner.category"].browse(37)
AccessError o comportamientos incorrectos "que solo pasan en producción" cuando un módulo se instala en varias bases de datos.
La solución
External IDs: un identificador estable entre bases de datos
Todo registro creado vía XML o CSV recibe un external ID (modulo.identificador) que sí es estable: se guarda en la tabla ir.model.data y apunta siempre al mismo registro lógico, independientemente del ID numérico que tenga en cada base de datos.
<record id="categoria_vip" model="res.partner.category">
<field name="name">Cliente VIP</field>
</record>
Desde Python, ese registro se recupera con env.ref(), usando el nombre completo modulo.identificador:
# Bien: funciona igual en cualquier base de datos donde el módulo esté instalado
categoria = self.env.ref("mi_modulo.categoria_vip")
Matices importantes
env.ref() con cuidado: raise_if_not_found
Por defecto, env.ref() lanza ValueError si el external ID no existe. Esto es correcto para datos que el módulo garantiza que existen (sus propios datos base), pero es un error si se referencia algo que el usuario pudo haber borrado (por ejemplo, un dato de demo).
# Lanza ValueError si no existe: correcto para datos base del propio módulo
categoria = self.env.ref("mi_modulo.categoria_vip")
# No lanza error, devuelve un recordset vacío: correcto para datos opcionales/demo
plantilla = self.env.ref("mi_modulo.plantilla_demo", raise_if_not_found=False)
if plantilla:
plantilla.enviar()
ref: <field name="group_id" ref="base.group_user"/>. Internamente Odoo resuelve esa referencia exactamente igual que env.ref().
El otro tipo de ID estable
Secuencias (ir.sequence): numeración consistente sin colisiones
Para generar códigos legibles y correlativos (facturas, pedidos, referencias internas), la solución no es un contador manual en Python (que sufre condiciones de carrera entre workers concurrentes), sino ir.sequence, que PostgreSQL gestiona de forma atómica.
<record id="seq_mi_documento" model="ir.sequence">
<field name="name">Mi Documento</field>
<field name="code">mi.modulo.documento</field>
<field name="prefix">DOC/%(year)s/</field>
<field name="padding">5</field>
</record>
# models/mi_documento.py
class MiDocumento(models.Model):
_name = "mi.documento"
name = fields.Char(default=lambda self: self.env["ir.sequence"].next_by_code("mi.modulo.documento"))
| Necesitas | Usa |
|---|---|
| Referenciar un registro fijo desde código | env.ref("modulo.id") |
| Generar números correlativos sin colisión | ir.sequence + next_by_code() |
| Comprobar si un dato opcional existe antes de usarlo | env.ref(..., raise_if_not_found=False) |
Resumen
Los IDs numéricos son un detalle de implementación de cada base de datos; los external IDs y env.ref() son el contrato estable entre módulos y entre entornos. Añade ir.sequence para numeración de negocio y evitarás dos de las clases de bugs más frecuentes al mover un módulo de desarrollo a producción.