Odoo · OWL · Frontend
Odoo 19 usa OWL 2 para el backend web. Un componente custom bien registrado puede sustituir widgets XML limitados o pantallas compuestas. Esta guía monta el esqueleto mínimo: JS + XML + assets + acción cliente.
Estructura
Archivos del componente
mi_modulo/
├── static/src/dashboard/
│ ├── mi_panel.js
│ ├── mi_panel.xml
│ └── mi_panel.scss # opcional
├── views/mi_panel_action.xml
└── __manifest__.py
| Archivo | Responsabilidad |
|---|---|
*.js | Clase OWL, servicios, registro en registry |
*.xml | Plantilla QWeb del componente (t-name) |
__manifest__.py | Bloque assets apuntando al JS/XML |
ir.actions.client | Entrada de menú con tag del registry |
JavaScript
Componente OWL mínimo
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component, onWillStart, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
export class MiPanel extends Component {
static template = "mi_modulo.MiPanel";
static props = ["*"];
setup() {
this.orm = useService("orm");
this.state = useState({ loading: true, items: [] });
onWillStart(() => this.loadItems());
}
async loadItems() {
this.state.items = await this.orm.call(
"mi.model", "search_read", [[], ["name", "state"]]
);
this.state.loading = false;
}
}
registry.category("actions").add("mi_modulo_panel", MiPanel);
@odoo-module activa el bundler ES modules. El tag del registry (mi_modulo_panel) debe coincidir con la acción cliente XML.
Plantilla
XML OWL (QWeb)
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mi_modulo.MiPanel">
<div class="o_mi_panel p-3">
<t t-if="state.loading">
<span>Loading…</span>
</t>
<t t-else="">
<ul>
<t t-foreach="state.items" t-as="item" t-key="item.id">
<li><t t-out="item.name"/></li>
</t>
</ul>
</t>
</div>
</t>
</templates>
t-out en lugar de t-esc (deprecado). Para HTML seguro, t-out con Markup() en Python.
Manifest
Assets en __manifest__.py
'assets': {
'web.assets_backend': [
'mi_modulo/static/src/dashboard/mi_panel.js',
'mi_modulo/static/src/dashboard/mi_panel.xml',
'mi_modulo/static/src/dashboard/mi_panel.scss',
],
},
Sin esta declaración, el JS no se carga y verás «Could not get content for…» en consola. Tras cambiar assets: reinicia Odoo o activa modo debug assets.
Acción
Menú y ir.actions.client
<record id="action_mi_panel" model="ir.actions.client">
<field name="name">Mi Panel</field>
<field name="tag">mi_modulo_panel</field>
</record>
<menuitem id="menu_mi_panel"
name="Mi Panel"
action="action_mi_panel"
parent="mi_modulo.menu_root"/>
-u.Depuración
Errores frecuentes
| Síntoma | Causa | Solución |
|---|---|---|
| Menú no abre nada | tag ≠ registry key | Alinear action y .add("key", …) |
| Template not found | static template mal escrito | Debe ser modulo.NombreTemplate |
| ORM AccessError | Método sin permisos | ACL + método con @api.model acotado |
| Cambios JS no aparecen | Cache assets | ?debug=assets o reinicio |
Resumen
Crea JS con @odoo-module, plantilla XML emparejada, declara assets en manifest y expón un ir.actions.client. Usa useService("orm") para datos y mantén la lógica de negocio en Python. OWL complementa el backend; no reemplaza ACL ni validaciones del servidor.