diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index c38dcfaf554..5d03264cf87 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1994,8 +1994,8 @@ class AccountMove(models.Model):
)._get_default_journal().id
copied_am = super().copy(default)
copied_am._message_log(body=_(
- 'This entry has been duplicated from %(title)s',
- id=self.id, title=html_escape(self.display_name)
+ 'This entry has been duplicated from %s',
+ self._get_html_link()
))
if copied_am.is_invoice(include_receipts=True):
@@ -4413,7 +4413,10 @@ class AccountMoveLine(models.Model):
for line in self.filtered(lambda l: l.move_id.id == move_id):
tracking_value_ids = line._mail_track(ref_fields, modified_lines)[1]
if tracking_value_ids:
- msg = f"{html_escape(_('Journal Item'))} #{line.id} {html_escape(_('updated'))}"
+ msg = _(
+ "Journal Item %s updated",
+ line._get_html_link(title=f"#{line.id}")
+ )
line.move_id._message_log(
body=msg,
tracking_value_ids=tracking_value_ids
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 790e620a442..5c28c4d64de 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -895,9 +895,15 @@ class AccountPayment(models.Model):
paired_payment.move_id._post(soft=False)
payment.paired_internal_transfer_payment_id = paired_payment
- body = _('This payment has been created from %s') % (payment.id, payment.name)
+ body = _(
+ "This payment has been created from %s",
+ payment._get_html_link(),
+ )
paired_payment.message_post(body=body)
- body = _('A second payment has been created: %s') % (paired_payment.id, paired_payment.name)
+ body = _(
+ "A second payment has been created: %s",
+ paired_payment._get_html_link(),
+ )
payment.message_post(body=body)
lines = (payment.move_id.line_ids + paired_payment.move_id.line_ids).filtered(
diff --git a/addons/account/wizard/account_automatic_entry_wizard.py b/addons/account/wizard/account_automatic_entry_wizard.py
index 29965770f01..7c5ffd70239 100644
--- a/addons/account/wizard/account_automatic_entry_wizard.py
+++ b/addons/account/wizard/account_automatic_entry_wizard.py
@@ -434,8 +434,7 @@ class AutomaticEntryWizard(models.TransientModel):
return content and '
' or None
def _format_move_link(self, move):
- move_link_format = "{move_name}"
- return move_link_format.format(move_id=move.id, move_name=move.name)
+ return move._get_html_link()
def _format_strings(self, string, move, amount):
return string.format(
diff --git a/addons/account/wizard/accrued_orders.py b/addons/account/wizard/accrued_orders.py
index e2c274fde66..ad7c3fc714c 100644
--- a/addons/account/wizard/accrued_orders.py
+++ b/addons/account/wizard/accrued_orders.py
@@ -214,12 +214,12 @@ class AccruedExpenseRevenue(models.TransientModel):
}])
reverse_move._post()
for order in orders_with_entries:
- body = _('Accrual entry created on %s: %s.\
- And its reverse entry.') % (
- self.date,
- move.id,
- move.name,
- reverse_move.id,
+ body = _(
+ 'Accrual entry created on %(date)s: %(accrual_entry)s.\
+ And its reverse entry: %(reverse_entry)s.',
+ date=self.date,
+ accrual_entry=move._get_html_link(),
+ reverse_entry=reverse_move._get_html_link(),
)
order.message_post(body=body)
return {
diff --git a/addons/account_debit_note/wizard/account_debit_note.py b/addons/account_debit_note/wizard/account_debit_note.py
index b9b47294b34..1c02083f4fc 100644
--- a/addons/account_debit_note/wizard/account_debit_note.py
+++ b/addons/account_debit_note/wizard/account_debit_note.py
@@ -68,8 +68,9 @@ class AccountDebitNote(models.TransientModel):
default_values = self._prepare_default_values(move)
new_move = move.copy(default=default_values)
move_msg = _(
- "This debit note was created from:") + " %s" % (
- move.id, move.name)
+ "This debit note was created from: %s",
+ move._get_html_link(),
+ )
new_move.message_post(body=move_msg)
new_moves |= new_move
diff --git a/addons/account_fleet/models/account_move.py b/addons/account_fleet/models/account_move.py
index ca5b68d363b..7d3a228d87a 100644
--- a/addons/account_fleet/models/account_move.py
+++ b/addons/account_fleet/models/account_move.py
@@ -18,9 +18,9 @@ class AccountMove(models.Model):
posted = super()._post(soft) # We need the move name to be set, but we also need to know which move are posted for the first time.
for line in (not_posted_before & posted).line_ids.filtered(lambda ml: ml.vehicle_id and ml.move_id.move_type == 'in_invoice'):
val = line._prepare_fleet_log_service()
- log = _('Service Vendor Bill: {move_name}').format(
- move_id=line.move_id.id,
- move_name=line.move_id.name,
+ log = _(
+ 'Service Vendor Bill: %s',
+ line.move_id._get_html_link(),
)
val_list.append(val)
log_list.append(log)
diff --git a/addons/crm_livechat/models/mail_channel.py b/addons/crm_livechat/models/mail_channel.py
index ecdf8a119c8..4c5e6ef806f 100644
--- a/addons/crm_livechat/models/mail_channel.py
+++ b/addons/crm_livechat/models/mail_channel.py
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, _
-from odoo.tools import html2plaintext, html_escape
+from odoo.tools import html2plaintext
class MailChannel(models.Model):
@@ -15,7 +15,10 @@ class MailChannel(models.Model):
msg = _('Create a new lead (/lead lead title)')
else:
lead = self._convert_visitor_to_lead(partner, key)
- msg = _('Created a new lead: %s') % (lead.id, html_escape(lead.name))
+ msg = _(
+ 'Created a new lead: %s',
+ lead._get_html_link(),
+ )
self._send_transient_message(partner, msg)
def _convert_visitor_to_lead(self, partner, key):
diff --git a/addons/hr_recruitment_survey/models/survey_invite.py b/addons/hr_recruitment_survey/models/survey_invite.py
index 889aa8612d1..8de974bac90 100644
--- a/addons/hr_recruitment_survey/models/survey_invite.py
+++ b/addons/hr_recruitment_survey/models/survey_invite.py
@@ -20,8 +20,8 @@ class SurveyInvite(models.TransientModel):
})
partner = self.applicant_id.partner_id
- survey_link = '%s' % (survey._name, survey.id, survey.title)
- partner_link = '%s' % (partner._name, partner.id, partner.name)
+ survey_link = survey._get_html_link(title=survey.title)
+ partner_link = partner._get_html_link()
content = _('The survey %(survey_link)s has been sent to %(partner_link)s', survey_link=survey_link, partner_link=partner_link)
body = '%s
' % content
self.applicant_id.message_post(body=body)
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index e0ab0326e50..dc67bb84d10 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -371,9 +371,8 @@ class Channel(models.Model):
notification = _('joined the channel
')
else:
notification = _(
- '',
- new_partner_id=channel_partner.partner_id.id,
- new_partner_name=channel_partner.partner_id.name,
+ 'invited %s to the channel
',
+ channel_partner.partner_id._get_html_link(),
)
channel_partner.channel_id.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment")
members_data.append({
@@ -1241,7 +1240,7 @@ class Channel(models.Model):
def execute_command_who(self, **kwargs):
partner = self.env.user.partner_id
members = [
- f'@{html_escape(p.name)}'
+ p._get_html_link(title=f"@{p.name}")
for p in self.channel_partner_ids[:30] if p != partner
]
if len(members) == 0:
diff --git a/addons/mail/models/models.py b/addons/mail/models/models.py
index 335f144bd2e..6d762f5ff5c 100644
--- a/addons/mail/models/models.py
+++ b/addons/mail/models/models.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml.builder import E
+from markupsafe import Markup
from odoo import api, models, tools, _
@@ -201,3 +202,20 @@ class BaseModel(models.AbstractModel):
return {
'X-Odoo-Objects': "%s-%s" % (self._name, self.id),
}
+
+ # ------------------------------------------------------------
+ # TOOLS
+ # ------------------------------------------------------------
+
+ def _get_html_link(self, title=None):
+ """Generate the record html reference for chatter use.
+
+ :param str title: optional reference title, the record display_name
+ is used if not provided. The title/display_name will be escaped.
+ :returns: generated html reference,
+ in the format title
+ :rtype: str
+ """
+ self.ensure_one()
+ return Markup("%s") % (
+ self._name, self.id, title or self.display_name)
diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py
index 9c7e8504d89..1408785d1c2 100644
--- a/addons/maintenance/models/maintenance.py
+++ b/addons/maintenance/models/maintenance.py
@@ -381,8 +381,10 @@ class MaintenanceRequest(models.Model):
new_user_id=request.user_id.id or request.owner_user_id.id or self.env.uid)
if not updated:
if request.equipment_id:
- note = _('Request planned for %s') % (
- request.equipment_id._name, request.equipment_id.id, request.equipment_id.display_name)
+ note = _(
+ 'Request planned for %s',
+ request.equipment_id._get_html_link()
+ )
else:
note = False
request.activity_schedule(
diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py
index c89c0372b3c..ec6088708f5 100644
--- a/addons/mrp/models/mrp_unbuild.py
+++ b/addons/mrp/models/mrp_unbuild.py
@@ -195,7 +195,11 @@ class MrpUnbuild(models.Model):
consume_moves.mapped('move_line_ids').write({'produce_line_ids': [(6, 0, produced_move_line_ids.ids)]})
if self.mo_id:
unbuild_msg = _(
- "%s %s unbuilt in", self.product_qty, self.product_uom_id.name) + " %s" % (self.id, self.display_name)
+ "%(qty)s %(measure)s unbuilt in %(order)s",
+ qty=self.product_qty,
+ measure=self.product_uom_id.name,
+ order=self._get_html_link(),
+ )
self.mo_id.message_post(
body=unbuild_msg,
subtype_id=self.env.ref('mail.mt_note').id)
diff --git a/addons/payment/models/account_payment.py b/addons/payment/models/account_payment.py
index 678c8f4bd1e..c7fb29fbd45 100644
--- a/addons/payment/models/account_payment.py
+++ b/addons/payment/models/account_payment.py
@@ -172,10 +172,6 @@ class AccountPayment(models.Model):
action['domain'] = [('source_payment_id', '=', self.id)]
return action
- def _get_payment_chatter_link(self):
- self.ensure_one()
- return f'{self.name}'
-
def _create_payment_transaction(self, **extra_create_values):
for payment in self:
if payment.payment_transaction_id:
diff --git a/addons/payment/models/payment_transaction.py b/addons/payment/models/payment_transaction.py
index e213ba4a5ee..593352a59b9 100644
--- a/addons/payment/models/payment_transaction.py
+++ b/addons/payment/models/payment_transaction.py
@@ -1055,7 +1055,7 @@ class PaymentTransaction(models.Model):
if self.payment_id:
message += "
" + _(
"The related payment is posted: %s",
- self.payment_id._get_payment_chatter_link()
+ self.payment_id._get_html_link(),
)
elif self.state == 'error':
message = _(
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 11f45264cec..600e04e02a8 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -493,7 +493,10 @@ class PosOrder(models.Model):
def _create_invoice(self, move_vals):
self.ensure_one()
new_move = self.env['account.move'].sudo().with_company(self.company_id).with_context(default_move_type=move_vals['move_type']).create(move_vals)
- message = _("This invoice has been created from the point of sale session: %s") % (self.id, self.name)
+ message = _(
+ "This invoice has been created from the point of sale session: %s",
+ self._get_html_link(),
+ )
new_move.message_post(body=message)
if self.config_id.cash_rounding:
rounding_applied = float_round(self.amount_paid - self.amount_total,
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index d5087ad8908..1fe3f488a20 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -103,10 +103,10 @@ class AccountMove(models.Model):
for move in moves:
if move.reversed_entry_id:
continue
- purchase = move.line_ids.mapped('purchase_line_id.order_id')
- if not purchase:
+ purchases = move.line_ids.purchase_line_id.order_id
+ if not purchases:
continue
- refs = ["%s" % tuple(name_get) for name_get in purchase.name_get()]
+ refs = [purchase._get_html_link() for purchase in purchases]
message = _("This vendor bill has been created from: %s") % ','.join(refs)
move.message_post(body=message)
return moves
@@ -121,7 +121,7 @@ class AccountMove(models.Model):
continue
diff_purchases = new_purchases - old_purchases[i]
if diff_purchases:
- refs = ["%s" % tuple(name_get) for name_get in diff_purchases.name_get()]
+ refs = [purchase._get_html_link() for purchase in diff_purchases]
message = _("This vendor bill has been modified from: %s") % ','.join(refs)
move.message_post(body=message)
return res
diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py
index f65ac552d78..d6f61419eef 100644
--- a/addons/sale/models/sale_order.py
+++ b/addons/sale/models/sale_order.py
@@ -807,7 +807,10 @@ class SaleOrder(models.Model):
lines_to_recompute.discount = 0.0
lines_to_recompute._compute_discount()
self.show_update_pricelist = False
- self.message_post(body=_("Product prices have been recomputed according to pricelist %s ", self.pricelist_id.display_name))
+ self.message_post(body=_(
+ "Product prices have been recomputed according to pricelist %s.",
+ self.pricelist_id._get_html_link(),
+ ))
# INVOICING #
@@ -1225,9 +1228,8 @@ class SaleOrder(models.Model):
if self:
self.activity_unlink(['sale.mail_act_sale_upsell'])
for order in self:
- ref = "%s"
- order_ref = ref % (order._name, order.id, order.name)
- customer_ref = ref % (order.partner_id._name, order.partner_id.id, order.partner_id.display_name)
+ order_ref = order._get_html_link()
+ customer_ref = order.partner_id._get_html_link()
order.activity_schedule(
'sale.mail_act_sale_upsell',
user_id=order.user_id.id or order.partner_id.user_id.id,
diff --git a/addons/sale_project/models/sale_order_line.py b/addons/sale_project/models/sale_order_line.py
index 06bdf6a19cf..d80708a24bb 100644
--- a/addons/sale_project/models/sale_order_line.py
+++ b/addons/sale_project/models/sale_order_line.py
@@ -84,7 +84,7 @@ class SaleOrderLine(models.Model):
line.sudo()._timesheet_service_generation()
# if the SO line created a task, post a message on the order
if line.task_id:
- msg_body = _("Task Created (%s): %s") % (line.product_id.name, line.task_id.id, line.task_id.name)
+ msg_body = _("Task Created (%s): %s", line.product_id.name, line.task_id._get_html_link())
line.order_id.message_post(body=msg_body)
return lines
@@ -192,7 +192,7 @@ class SaleOrderLine(models.Model):
task = self.env['project.task'].sudo().create(values)
self.write({'task_id': task.id})
# post message on task
- task_msg = _("This task has been created from: %s (%s)") % (self.order_id.id, self.order_id.name, self.product_id.name)
+ task_msg = _("This task has been created from: %s (%s)", self.order_id._get_html_link(), self.product_id.name)
task.message_post(body=task_msg)
return task
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index f99178db75b..5f9316f8c5c 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -629,7 +629,7 @@ class StockMove(models.Model):
if not documents or not doc_orig:
return
- msg = _("The deadline has been automatically updated due to a delay on %s.") % (doc_orig[0]._name, doc_orig[0].id, doc_orig[0].name)
+ msg = _("The deadline has been automatically updated due to a delay on %s.", doc_orig[0]._get_html_link())
msg_subject = _("Deadline updated due to delay on %s", doc_orig[0].name)
# write the message on each document
for doc in documents:
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 3b593ecbd97..2707a9bccc4 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -1166,8 +1166,8 @@ class Picking(models.Model):
'backorder_id': picking.id
})
picking.message_post(
- body=_('The backorder %s has been created.') % (
- backorder_picking.id, backorder_picking.name))
+ body=_('The backorder %s has been created.', backorder_picking._get_html_link())
+ )
moves_to_backorder.write({'picking_id': backorder_picking.id})
moves_to_backorder.move_line_ids.package_level_id.write({'picking_id':backorder_picking.id})
moves_to_backorder.mapped('move_line_ids').write({'picking_id': backorder_picking.id})