From 00ed6aa0429c358affbfe8df56b5e15efdd329f6 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Thu, 17 Feb 2022 16:19:11 +0000 Subject: [PATCH] [IMP] mail,* : uniformized API for chatter links * Enforce html escaping of record title * Avoid translating html content as much as possible, to reduce translation errors. * Uniformize/Factorize link generation, easing future tasks, code maintenance, ... Enterprise PR: https://github.com/odoo/enterprise/pull/25357 closes odoo/odoo#84866 Related: odoo/enterprise#25357 Signed-off-by: Victor Feyens (vfe) --- addons/account/models/account_move.py | 9 ++++++--- addons/account/models/account_payment.py | 10 ++++++++-- .../wizard/account_automatic_entry_wizard.py | 3 +-- addons/account/wizard/accrued_orders.py | 12 ++++++------ .../wizard/account_debit_note.py | 5 +++-- addons/account_fleet/models/account_move.py | 6 +++--- addons/crm_livechat/models/mail_channel.py | 7 +++++-- .../models/survey_invite.py | 4 ++-- addons/mail/models/mail_channel.py | 7 +++---- addons/mail/models/models.py | 18 ++++++++++++++++++ addons/maintenance/models/maintenance.py | 6 ++++-- addons/mrp/models/mrp_unbuild.py | 6 +++++- addons/payment/models/account_payment.py | 4 ---- addons/payment/models/payment_transaction.py | 2 +- addons/point_of_sale/models/pos_order.py | 5 ++++- addons/purchase/models/account_invoice.py | 8 ++++---- addons/sale/models/sale_order.py | 10 ++++++---- addons/sale_project/models/sale_order_line.py | 4 ++-- addons/stock/models/stock_move.py | 2 +- addons/stock/models/stock_picking.py | 4 ++-- 20 files changed, 84 insertions(+), 48 deletions(-) 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 = _( - '
invited %(new_partner_name)s to the channel
', - 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})