Odoo · Mail · Chatter
Escribir en el chatter desde Python es una línea: record.message_post(body=...). Lo difícil es lo que esa línea no dice: si el mensaje es una nota interna o un mensaje a los seguidores, quién aparece como autor, quién recibe un email y si el HTML que pasas se pinta o se escapa. Cada una de esas decisiones tiene un valor por defecto, y no siempre es el que esperas.
Subtipos
Nota interna o mensaje: el valor por defecto es nota
Si llamas a message_post sin subtype_xmlid, Odoo usa mail.mt_note: una nota interna. Se ve en el chatter, pero no notifica a los seguidores (y los usuarios del portal ni siquiera la ven). Para que el mensaje llegue a quienes siguen el registro, hay que pedir explícitamente el subtipo de comentario.
# Nota interna: solo la ve el equipo, nadie recibe email
order.message_post(body=_("Stock checked manually."))
# Mensaje: notifica a los seguidores suscritos a "Discussions"
order.message_post(
body=_("Your order has been shipped."),
subtype_xmlid="mail.mt_comment",
)
# Mensaje a destinatarios concretos, sigan o no el registro
order.message_post(
body=_("Please confirm the delivery address."),
subtype_xmlid="mail.mt_comment",
partner_ids=order.partner_id.ids,
)
mt_comment esperando que le llegue, no le llegará. Usa partner_ids o message_subscribe() explícitamente.
Autor
sudo() no cambia quién firma
El autor por defecto es el partner del usuario del entorno. sudo() eleva los permisos pero conserva el usuario, así que un aviso del sistema escrito en un controlador del portal con sudo() aparecerá firmado por el visitante que hizo la petición. Para mensajes automáticos, indica el autor:
# Aparece firmado por el usuario que disparó la acción, aunque sea sudo()
record.sudo().message_post(body=_("Report received."))
# Aviso del sistema, firmado por OdooBot
record.sudo().message_post(
body=_("Report received."),
author_id=self.env.ref("base.partner_root").id,
)
HTML
Un str se escapa; un Markup, no
Desde hace varias versiones, message_post trata un str como texto plano y lo escapa: tus etiquetas <b> aparecen literalmente en el chatter. Para enviar HTML hay que marcarlo como seguro con Markup, y entonces tú pasas a ser responsable de escapar los datos que interpolas, porque pueden venir de un usuario.
from markupsafe import Markup
# ❌ Se ve "<b>Pedido</b>" literalmente
order.message_post(body=f"<b>Pedido</b> {order.name}")
# ❌ HTML inyectable si client_ref lo escribe el cliente
order.message_post(body=Markup(f"<b>Ref:</b> {order.client_ref}"))
# ✅ Markup.format escapa los argumentos
order.message_post(body=Markup("<b>Ref:</b> {}").format(order.client_ref))
Alternativas
Cuándo no hace falta message_post
Muchos message_post escritos a mano solo anuncian que un campo cambió. Para eso existe tracking=True en la definición del campo: Odoo registra el valor anterior y el nuevo en el chatter, agrupa varios cambios en un solo mensaje y no hay código que mantener.
| Quiero… | Usa |
|---|---|
| Registrar que cambió un campo | tracking=True en el campo |
| Dejar constancia para el equipo | message_post() (nota, por defecto) |
| Avisar a seguidores y cliente | message_post(subtype_xmlid="mail.mt_comment") |
| Avisar a alguien que no sigue el registro | partner_ids=[...] |
| Enviar un email con plantilla | message_post_with_source() o mail.template |
| Pedir a alguien que haga algo | activity_schedule(), no un mensaje |
Resumen
Sin subtipo, message_post crea una nota interna; para notificar, usa mail.mt_comment y, si el destinatario puede no ser seguidor, partner_ids. sudo() no cambia el autor: pásalo tú para los avisos del sistema. Y el HTML va con Markup.format, nunca con f-strings sobre datos del usuario. Más sobre el chatter en mixins imprescindibles.