Ir al contenido

El chatter desde código: message_post, subtipos y a quién le llega

Notas internas, mensajes a seguidores, autor del mensaje y HTML seguro
13 de octubre de 2026 por
El chatter desde código: message_post, subtipos y a quién le llega
ATEMI, Aitor Atencia

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.

Logo Odoo
El subtipo decide quién se entera; el autor decide quién firma.

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

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 campotracking=True en el campo
Dejar constancia para el equipomessage_post() (nota, por defecto)
Avisar a seguidores y clientemessage_post(subtype_xmlid="mail.mt_comment")
Avisar a alguien que no sigue el registropartner_ids=[...]
Enviar un email con plantillamessage_post_with_source() o mail.template
Pedir a alguien que haga algoactivity_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.

en Odoo
Transacciones en Odoo: savepoints, UniqueViolation y logs que sobreviven al rollback
Qué pasa con la transacción cuando capturas una excepción y cómo no dejarla abortada