Ir al contenido

Secuencias, referencias XML y env.ref()

Evitar IDs hardcodeados y referencias rotas entre módulos
7 de julio de 2026 por
Secuencias, referencias XML y env.ref()
Atemi, Aitor Atencia

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.

Logo Odoo Logo PostgreSQL
El ID numérico de un registro cambia entre bases de datos; el external ID no.

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)

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()

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"))
NecesitasUsa
Referenciar un registro fijo desde códigoenv.ref("modulo.id")
Generar números correlativos sin colisiónir.sequence + next_by_code()
Comprobar si un dato opcional existe antes de usarloenv.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.

en Odoo
Traducciones i18n: .pot, .po y flujo de trabajo
Strings en inglés en código, exportar/importar y es_ES