Odoo · Wizards · UX
Un wizard mal diseñado se nota enseguida: pierde el estado entre pasos, no valida hasta el final, o deja registros a medias si el usuario cierra la ventana a mitad de camino. TransientModel resuelve el ciclo de vida de los datos temporales, pero el diseño del flujo sigue siendo responsabilidad tuya.
La base
Por qué TransientModel y no Model
Un wizard no necesita persistir datos de negocio: solo recoge información temporal para ejecutar una acción. TransientModel hereda de Model pero sus registros se autoeliminan mediante un cron (vacuum), evitando que la tabla crezca sin control con cada apertura del asistente.
# wizards/export_wizard.py
class ExportWizard(models.TransientModel):
_name = "mi_modulo.export.wizard"
_description = "Asistente de exportación"
date_from = fields.Date(required=True)
date_to = fields.Date(required=True)
partner_ids = fields.Many2many("res.partner", string="Clientes")
Many2one desde un modelo persistente hacia un wizard es un error de diseño casi seguro: ese registro desaparecerá en cuestión de horas y dejará el enlace roto.
Flujos multi-paso
Mantener el estado entre pantallas con state
Cuando el wizard tiene varios pasos, el patrón habitual es un campo Selection que actúa como máquina de estados, combinado con una vista form que muestra secciones distintas según el paso activo mediante invisible.
state = fields.Selection([
("step1", "Selección de rango"),
("step2", "Revisión"),
("done", "Confirmado"),
], default="step1")
def action_next(self):
self.ensure_one()
self.state = "step2"
return self._reopen_wizard()
def _reopen_wizard(self):
return {
"type": "ir.actions.act_window",
"res_model": self._name,
"res_id": self.id,
"view_mode": "form",
"target": "new",
}
Cada botón del wizard invoca un método que valida el paso actual antes de avanzar, en vez de dejar toda la validación para el botón final.
El paso final
Devolver la acción correcta al cerrar
El método que ejecuta la acción de negocio debe devolver explícitamente qué hacer después: cerrar la ventana, abrir el registro creado, o mostrar una notificación. Dejar que Odoo decida por defecto suele producir una experiencia confusa.
def action_confirm(self):
self.ensure_one()
doc = self.env["mi_modulo.documento"].create(self._prepare_values())
return {
"type": "ir.actions.act_window",
"res_model": "mi_modulo.documento",
"res_id": doc.id,
"view_mode": "form",
"target": "current",
}
{"type": "ir.actions.act_window_close"}. Es más explícito que devolver False o None.
Contexto
Pasar datos desde el modelo origen con default_get
Cuando el wizard se lanza desde un botón de un formulario (factura, pedido, tarea), conviene precargar campos usando el contexto de la acción en vez de pedirle al usuario que repita información que ya está en pantalla.
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
active_ids = self.env.context.get("active_ids", [])
if "partner_ids" in fields_list and active_ids:
res["partner_ids"] = [(6, 0, active_ids)]
return res
| Necesitas | Usa |
|---|---|
| Recoger datos temporales sin persistirlos | TransientModel |
| Varios pasos en el mismo asistente | Campo state + reapertura del wizard |
| Precargar datos del registro origen | default_get() + active_id/active_ids |
| Abrir el resultado tras confirmar | ir.actions.act_window con res_id |
Resumen
TransientModel te libera de gestionar la limpieza de datos temporales, pero un buen wizard sigue exigiendo diseño: validar por paso, no acoplar modelos persistentes al wizard, y devolver siempre una acción explícita al terminar.