diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index d93340606df..6585b3ac91b 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -7,6 +7,7 @@ from dateutil.relativedelta import relativedelta
from hashlib import sha256
from json import dumps
import logging
+from markupsafe import Markup, escape
from psycopg2 import OperationalError
import re
from textwrap import shorten
@@ -2232,12 +2233,10 @@ class AccountMove(models.Model):
default['partner_id'] = False
copied_am = super().copy(default)
message_origin = '' if not copied_am.auto_post_origin_id else \
- ' ' + _('This recurring entry originated from %s', copied_am.auto_post_origin_id._get_html_link())
- copied_am._message_log(body=_(
- 'This entry has been duplicated from %s%s',
- self._get_html_link(),
- message_origin,
- ))
+ (Markup(' ') + _('This recurring entry originated from %s')) % copied_am.auto_post_origin_id._get_html_link()
+ copied_am._message_log(body=
+ (escape(_('This entry has been duplicated from %s')) % self._get_html_link()) + message_origin,
+ )
return copied_am
diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py
index b108b0536d3..b95c8a935ca 100644
--- a/addons/account/models/account_move_line.py
+++ b/addons/account/models/account_move_line.py
@@ -3,6 +3,7 @@ from collections import defaultdict
from contextlib import contextmanager
from datetime import date, timedelta
from functools import lru_cache
+from markupsafe import escape
from odoo import api, fields, models, Command, _
from odoo.exceptions import ValidationError, UserError
@@ -1448,10 +1449,7 @@ 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 = _(
- "Journal Item %s updated",
- line._get_html_link(title=f"#{line.id}")
- )
+ msg = escape(_("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 960de318d93..c91d49dbfd7 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+from markupsafe import escape
+
from odoo import models, fields, api, _, Command
from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import format_date, formatLang
@@ -896,16 +898,9 @@ 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._get_html_link(),
- )
+ body = escape(_("This payment has been created from:")) + payment._get_html_link()
paired_payment.message_post(body=body)
- body = _(
- "A second payment has been created: %s",
- paired_payment._get_html_link(),
- )
+ body = escape(_("A second payment has been created:")) + 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/models/chart_template.py b/addons/account/models/chart_template.py
index 2b95a3d3e75..31b955e243d 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
import ast
-from collections import defaultdict
import csv
+from collections import defaultdict
from functools import wraps
from inspect import getmembers
+
import logging
import re
diff --git a/addons/account/models/res_partner_bank.py b/addons/account/models/res_partner_bank.py
index 94e78f15b45..5081e530f4b 100644
--- a/addons/account/models/res_partner_bank.py
+++ b/addons/account/models/res_partner_bank.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import base64
from collections import defaultdict
+from markupsafe import escape
import werkzeug
import werkzeug.exceptions
@@ -241,7 +242,7 @@ class ResPartnerBank(models.Model):
# EXTENDS base res.partner.bank
res = super().create(vals_list)
for account in res:
- msg = _("Bank Account %s created", account._get_html_link(title=f"#{account.id}"))
+ msg = escape(_("Bank Account %s created")) % account._get_html_link(title=f"#{account.id}")
account.partner_id._message_log(body=msg)
return res
@@ -269,7 +270,7 @@ class ResPartnerBank(models.Model):
for account, initial_values in account_initial_values.items():
tracking_value_ids = account._mail_track(fields_definition, initial_values)[1]
if tracking_value_ids:
- msg = _("Bank Account %s updated", account._get_html_link(title=f"#{account.id}"))
+ msg = escape(_("Bank Account %s updated")) % account._get_html_link(title=f"#{account.id}")
account.partner_id._message_log(body=msg, tracking_value_ids=tracking_value_ids)
if 'partner_id' in initial_values: # notify previous partner as well
initial_values['partner_id']._message_log(body=msg, tracking_value_ids=tracking_value_ids)
@@ -278,7 +279,7 @@ class ResPartnerBank(models.Model):
def unlink(self):
# EXTENDS base res.partner.bank
for account in self:
- msg = _("Bank Account %s with number %s deleted", account._get_html_link(title=f"#{account.id}"), account.acc_number)
+ msg = escape(_("Bank Account %s with number %s deleted")) % (account._get_html_link(title=f"#{account.id}"), account.acc_number)
account.partner_id._message_log(body=msg)
return super().unlink()
diff --git a/addons/account/wizard/account_automatic_entry_wizard.py b/addons/account/wizard/account_automatic_entry_wizard.py
index 0d1a221692a..d4bc48a0fc4 100644
--- a/addons/account/wizard/account_automatic_entry_wizard.py
+++ b/addons/account/wizard/account_automatic_entry_wizard.py
@@ -2,9 +2,11 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import format_date, formatLang
+from odoo.tools.float_utils import float_repr
from collections import defaultdict
from itertools import groupby
+from markupsafe import Markup, escape
import json
class AutomaticEntryWizard(models.TransientModel):
@@ -249,7 +251,7 @@ class AutomaticEntryWizard(models.TransientModel):
'analytic_distribution': aml.analytic_distribution,
}),
(0, 0, {
- 'name': self._format_strings(_('{percent:0.2f}% recognized on {new_date}'), aml.move_id),
+ 'name': self._format_strings(_('{percent}% recognized on {new_date}'), aml.move_id),
'debit': reported_credit,
'credit': reported_debit,
'amount_currency': -reported_amount_currency,
@@ -271,7 +273,7 @@ class AutomaticEntryWizard(models.TransientModel):
'analytic_distribution': aml.analytic_distribution,
}),
(0, 0, {
- 'name': self._format_strings(_('{percent:0.2f}% to recognize on {new_date}'), aml.move_id),
+ 'name': self._format_strings(_('{percent}% to recognize on {new_date}'), aml.move_id),
'debit': reported_debit,
'credit': reported_credit,
'amount_currency': reported_amount_currency,
@@ -350,17 +352,30 @@ class AutomaticEntryWizard(models.TransientModel):
accrual_move_lines = accrual_move.mapped('line_ids').filtered(lambda line: line.account_id == accrual_account)[accrual_move_offsets[accrual_move]:accrual_move_offsets[accrual_move]+2]
accrual_move_offsets[accrual_move] += 2
(accrual_move_lines + destination_move_lines).filtered(lambda line: not line.currency_id.is_zero(line.balance)).reconcile()
- move.message_post(body=self._format_strings(_('Adjusting Entries have been created for this invoice:
%(link1)s cancelling '
- '{percent:.2f}%% of {amount}
%(link0)s postponing it to {new_date}
',
- link0=self._format_move_link(destination_move),
- link1=self._format_move_link(accrual_move),
- ), move, amount))
- destination_messages += [self._format_strings(_('Adjusting Entry {link}: {percent:.2f}% of {amount} recognized from {date}'), move, amount)]
- accrual_move_messages[accrual_move] += [self._format_strings(_('Adjusting Entry for {link}: {percent:.2f}% of {amount} recognized on {new_date}'), move, amount)]
+ body = Markup("%(title)s
%(link1)s %(second)s
%(link2)s %(third)s
") % {
+ 'title': _("Adjusting Entries have been created for this invoice:"),
+ 'link1': self._format_move_link(accrual_move),
+ 'second': self._format_strings(_("cancelling {percent}%% of {amount}"), move, amount),
+ 'link2': self._format_move_link(destination_move),
+ 'third': self._format_strings(_("postponing it to {new_date}"), move, amount),
+ }
+ move.message_post(body=body)
+ destination_messages += [
+ self._format_strings(
+ escape(_("Adjusting Entry {link} {percent}%% of {amount} recognized from {date}")),
+ move, amount,
+ )
+ ]
+ accrual_move_messages[accrual_move] += [
+ self._format_strings(
+ escape(_("Adjusting Entry {link} {percent}%% of {amount} recognized on {new_date}")),
+ move, amount,
+ )
+ ]
- destination_move.message_post(body=' \n'.join(destination_messages))
+ destination_move.message_post(body=Markup(' \n').join(destination_messages))
for accrual_move, messages in accrual_move_messages.items():
- accrual_move.message_post(body=' \n'.join(messages))
+ accrual_move.message_post(body=Markup(' \n').join(messages))
# open the generated entries
action = {
@@ -418,31 +433,41 @@ class AutomaticEntryWizard(models.TransientModel):
# Transfer utils
def _format_new_transfer_move_log(self, acc_transfer_per_move):
- format = _("
{amount} ({debit_credit}) from {link}, %(account_source_name)s
")
- rslt = _("This entry transfers the following amounts to %(destination)s
", destination=self.destination_account_id.display_name)
- for move, balances_per_account in acc_transfer_per_move.items():
- for account, balance in balances_per_account.items():
- if account != self.destination_account_id: # Otherwise, logging it here is confusing for the user
- rslt += self._format_strings(format, move, balance) % {'account_source_name': account.display_name}
-
- rslt += '
'
+ transfer_format = Markup("
%s, %%(account_source_name)s
") % \
+ _("{amount} ({debit_credit}) from {link}")
+ rslt = Markup(_("This entry transfers the following amounts to %(destination)s") + "
%(transfer_logs)s
") % {
+ 'destination': Markup("%s") % self.destination_account_id.display_name,
+ 'transfer_logs': Markup().join([
+ self._format_strings(transfer_format, move, balance) % {'account_source_name': account.display_name}
+ for move, balances_per_account in acc_transfer_per_move.items()
+ for account, balance in balances_per_account.items()
+ if account != self.destination_account_id # Otherwise, logging it here is confusing for the user
+ ])
+ }
return rslt
def _format_transfer_source_log(self, balances_per_account, transfer_move):
- transfer_format = _("
{amount} ({debit_credit}) from %s were transferred to {account_target_name} by {link}
")
- content = ''
- for account, balance in balances_per_account.items():
- if account != self.destination_account_id:
- content += self._format_strings(transfer_format, transfer_move, balance) % account.display_name
- return content and '
' + content + '
' or None
+ if not balances_per_account:
+ return None
+
+ transfer_format = Markup(
+ _("{amount} ({debit_credit}) from %s were transferred to {account_target_name} by {link}")
+ )
+
+ return Markup("
%s
") % Markup().join([
+ Markup("
%s
") % \
+ self._format_strings(transfer_format, transfer_move, balance) % (Markup("%s") % account.display_name)
+ for account, balance in balances_per_account.items()
+ if account != self.destination_account_id
+ ])
def _format_move_link(self, move):
return move._get_html_link()
def _format_strings(self, string, move, amount=None):
return string.format(
- label=move.name or 'Adjusting Entry',
- percent=self.percentage,
+ label=move.name or _('Adjusting Entry'),
+ percent=float_repr(self.percentage, 2),
name=move.name,
id=move.id,
amount=formatLang(self.env, abs(amount), currency_obj=self.company_id.currency_id) if amount else '',
diff --git a/addons/account/wizard/accrued_orders.py b/addons/account/wizard/accrued_orders.py
index ca0f258b90e..d61c6048114 100644
--- a/addons/account/wizard/accrued_orders.py
+++ b/addons/account/wizard/accrued_orders.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
+from markupsafe import escape
import json
from odoo import models, fields, api, _, Command
from odoo.tools import format_date
@@ -217,13 +218,13 @@ class AccruedExpenseRevenue(models.TransientModel):
}])
reverse_move._post()
for order in orders_with_entries:
- body = _(
+ body = escape(_(
'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(),
- )
+ 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 {
'name': _('Accrual Moves'),
diff --git a/addons/account_debit_note/wizard/account_debit_note.py b/addons/account_debit_note/wizard/account_debit_note.py
index b03addb8d1b..3982de5d622 100644
--- a/addons/account_debit_note/wizard/account_debit_note.py
+++ b/addons/account_debit_note/wizard/account_debit_note.py
@@ -2,7 +2,7 @@
from odoo import models, fields, api
from odoo.tools.translate import _
from odoo.exceptions import UserError
-
+from markupsafe import escape
class AccountDebitNote(models.TransientModel):
"""
@@ -71,10 +71,7 @@ class AccountDebitNote(models.TransientModel):
for move in self.move_ids.with_context(include_business_fields=True): #copy sale/purchase links
default_values = self._prepare_default_values(move)
new_move = move.copy(default=default_values)
- move_msg = _(
- "This debit note was created from: %s",
- move._get_html_link(),
- )
+ move_msg = escape(_("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_edi_ubl_cii/models/account_edi_common.py b/addons/account_edi_ubl_cii/models/account_edi_common.py
index 7db5988c2ea..2de56f97579 100644
--- a/addons/account_edi_ubl_cii/models/account_edi_common.py
+++ b/addons/account_edi_ubl_cii/models/account_edi_common.py
@@ -5,6 +5,7 @@ from odoo.tools import float_repr
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_round
+from markupsafe import Markup
from zeep import Client
# -------------------------------------------------------------------------
@@ -285,13 +286,14 @@ class AccountEdiCommon(models.AbstractModel):
invoice.move_type = move_type
logs = self._import_fill_invoice_form(invoice, tree, qty_factor)
if invoice:
+ body = Markup("%s") % \
+ _("Format used to import the invoice: %s",
+ self.env['ir.model']._get(self._name).name)
+
if logs:
- body = _(
- "Format used to import the invoice: %s
%s
",
- str(self._description), "
".join(logs)
- )
- else:
- body = _("Format used to import the invoice: %s", str(self._description))
+ body += Markup("
%s
") % \
+ Markup().join(Markup("
%s
") % l for l in logs)
+
invoice.message_post(body=body)
# For UBL, we should override the computed tax amount if it is less than 0.05 different of the one in the xml.
@@ -656,22 +658,24 @@ class AccountEdiCommon(models.AbstractModel):
for item in response['Result']:
if item['artifactPath']:
report.append(
- "
" + item['artifactPath'] + "
")
+ Markup("
%s
") % item['artifactPath'])
for detail in item['Item']:
if detail['errorLevel'] == 'WARN':
errors_cnt += 1
report.append(
- "
") % detail['errorText'])
if errors_cnt == 0:
- invoice.message_post(body=f"ECOSIO: All clear for format {ecosio_format}!")
+ invoice.message_post(body=Markup("ECOSIO: All clear for format %s!") % ecosio_format)
else:
invoice.message_post(
- body=f"ECOSIO ERRORS/WARNINGS for format {ecosio_format}:
"
- + "\n".join(report) + "
"
+ body=Markup("ECOSIO ERRORS/WARNINGS for format %s:
%s
") % (
+ ecosio_format,
+ Markup().join(report)
+ )
)
return response
diff --git a/addons/account_fleet/models/account_move.py b/addons/account_fleet/models/account_move.py
index d0f12d08da7..e4a2314c136 100644
--- a/addons/account_fleet/models/account_move.py
+++ b/addons/account_fleet/models/account_move.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import escape
from odoo import models, fields, api, _
@@ -18,10 +19,7 @@ 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: %s',
- line.move_id._get_html_link(),
- )
+ log = escape(_('Service Vendor Bill: %s')) % line.move_id._get_html_link()
val_list.append(val)
log_list.append(log)
log_service_ids = self.env['fleet.vehicle.log.services'].create(val_list)
diff --git a/addons/account_payment/models/payment_transaction.py b/addons/account_payment/models/payment_transaction.py
index 776973de8a3..da6e68093e2 100644
--- a/addons/account_payment/models/payment_transaction.py
+++ b/addons/account_payment/models/payment_transaction.py
@@ -1,5 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import escape
from odoo import api, fields, models, SUPERUSER_ID, _
@@ -214,8 +215,7 @@ class PaymentTransaction(models.Model):
"""
super()._finalize_post_processing()
for tx in self.filtered('payment_id'):
- message = _(
- "The payment related to the transaction with reference %(ref)s has been posted: "
- "%(link)s", ref=tx.reference, link=tx.payment_id._get_html_link()
- )
+ message = escape(_("The payment related to the transaction with reference %(ref)s has been posted: %(link)s")) % {
+ 'ref':tx.reference, 'link':tx.payment_id._get_html_link()
+ }
tx._log_message_on_linked_documents(message)
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index e57f58cf522..85dba55598c 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -7,7 +7,7 @@ import threading
from ast import literal_eval
from collections import OrderedDict, defaultdict
from datetime import date, datetime, timedelta
-from markupsafe import Markup
+from markupsafe import Markup, escape
from psycopg2 import sql
from odoo import api, fields, models, tools, SUPERUSER_ID
@@ -1306,15 +1306,16 @@ class Lead(models.Model):
duration = _('unknown')
else:
duration = self.env['ir.qweb.field.duration'].value_to_html(meeting.duration, {'unit': 'hour'})
- meet_date = fields.Datetime.from_string(meeting.start)
- meeting_usertime = fields.Datetime.to_string(fields.Datetime.context_timestamp(self, meet_date))
- message = "
") % (log_action, html_message)
self._message_log_batch(bodies=dict((team.id, log_message) for team in self))
return {
diff --git a/addons/crm/wizard/crm_lead_lost.py b/addons/crm/wizard/crm_lead_lost.py
index 149ce8336cb..694483dd90c 100644
--- a/addons/crm/wizard/crm_lead_lost.py
+++ b/addons/crm/wizard/crm_lead_lost.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
from odoo import fields, models, _
from odoo.tools.mail import is_html_empty
@@ -19,7 +20,7 @@ class CrmLeadLost(models.TransientModel):
leads = self.env['crm.lead'].browse(self.env.context.get('active_ids'))
if not is_html_empty(self.lost_feedback):
leads._track_set_log_message(
- '
%s:
%s
' % (
+ Markup('
%s:
%s
') % (
_('Lost Comment'),
self.lost_feedback
)
diff --git a/addons/crm_livechat/models/mail_channel.py b/addons/crm_livechat/models/mail_channel.py
index 51d3e905d9a..46c4604d65e 100644
--- a/addons/crm_livechat/models/mail_channel.py
+++ b/addons/crm_livechat/models/mail_channel.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import escape
from odoo import models, _
from odoo.tools import html2plaintext
@@ -15,10 +16,7 @@ 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._get_html_link(),
- )
+ msg = escape(_('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/event_booth_sale/models/event_booth_registration.py b/addons/event_booth_sale/models/event_booth_registration.py
index 60c1c949e35..4d7c6995517 100644
--- a/addons/event_booth_sale/models/event_booth_registration.py
+++ b/addons/event_booth_sale/models/event_booth_registration.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
from odoo import api, fields, models, _
@@ -61,9 +62,9 @@ class EventBoothRegistration(models.Model):
self._cancel_pending_registrations()
def _cancel_pending_registrations(self):
- body = '
%(message)s:
%(booth_names)s
' % {
+ body = Markup('
%(message)s:
%(booth_names)s
') % {
'message': _('Your order has been cancelled because the following booths have been reserved'),
- 'booth_names': ''.join('
%s
' % booth.display_name for booth in self.event_booth_id)
+ 'booth_names': Markup().join(Markup('
%s
') % booth.display_name for booth in self.event_booth_id)
}
other_registrations = self.search([
('event_booth_id', 'in', self.event_booth_id.ids),
diff --git a/addons/gamification/models/gamification_challenge.py b/addons/gamification/models/gamification_challenge.py
index b9f6ead1015..1c4fd0dcd38 100644
--- a/addons/gamification/models/gamification_challenge.py
+++ b/addons/gamification/models/gamification_challenge.py
@@ -6,6 +6,7 @@ import logging
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta, MO
+from markupsafe import Markup
from odoo import api, models, fields, _, exceptions
from odoo.tools import ustr
@@ -694,21 +695,21 @@ class Challenge(models.Model):
if rewarded_users:
user_names = rewarded_users.name_get()
- message_body += _(
- " Reward (badge %(badge_name)s) for every succeeding user was sent to %(users)s.",
+ message_body += Markup(" ") + _(
+ "Reward (badge %(badge_name)s) for every succeeding user was sent to %(users)s.",
badge_name=challenge.reward_id.name,
users=", ".join(name for (user_id, name) in user_names)
)
else:
- message_body += _(" Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")
+ message_body += Markup(" ") + _("Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")
# reward bests
- reward_message = _(" %(rank)d. %(user_name)s - %(reward_name)s")
+ reward_message = Markup(" %(rank)d. %(user_name)s - %(reward_name)s")
if challenge.reward_first_id:
(first_user, second_user, third_user) = challenge._get_topN_users(MAX_VISIBILITY_RANKING)
if first_user:
challenge._reward_user(first_user, challenge.reward_first_id)
- message_body += _(" Special rewards were sent to the top competing users. The ranking for this challenge is:")
+ message_body += Markup(" ") + _("Special rewards were sent to the top competing users. The ranking for this challenge is:")
message_body += reward_message % {
'rank': 1,
'user_name': first_user.name,
diff --git a/addons/google_calendar/models/google_sync.py b/addons/google_calendar/models/google_sync.py
index da97e3151a3..17c8b7dbbe6 100644
--- a/addons/google_calendar/models/google_sync.py
+++ b/addons/google_calendar/models/google_sync.py
@@ -7,6 +7,7 @@ from functools import wraps
from requests import HTTPError
import pytz
from dateutil.parser import parse
+from markupsafe import Markup
from odoo import api, fields, models, registry, _
from odoo.tools import ormcache_context, email_normalize
@@ -210,10 +211,9 @@ class GoogleSync(models.AbstractModel):
'reason': reason}
_logger.error(error_log)
- body = _(
- "The following event could not be synced with Google Calendar. "
- "It will not be synced as long at it is not updated."
- "%(reason)s", reason=reason)
+ body = _("The following event could not be synced with Google Calendar.") + Markup(" ") + \
+ _("It will not be synced as long at it is not updated.") + Markup(" ") + \
+ reason
if event:
event.message_post(
diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py
index 28b4ee3d217..9a03ec1ed09 100644
--- a/addons/hr/models/hr_employee.py
+++ b/addons/hr/models/hr_employee.py
@@ -8,6 +8,7 @@ from random import choice
from string import digits
from werkzeug.urls import url_encode
from dateutil.relativedelta import relativedelta
+from markupsafe import Markup
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, AccessError
@@ -367,10 +368,9 @@ class HrEmployeePrivate(models.Model):
'active_model': 'hr.employee',
'menu_id': hr_root_menu.id,
})
- onboarding_notes_bodies[employee.id] = _(
+ onboarding_notes_bodies[employee.id] = Markup(_(
'Congratulations! May I recommend you to setup an onboarding plan?',
- url,
- )
+ )) % url
employees._message_log_batch(onboarding_notes_bodies)
return employees
diff --git a/addons/hr/wizard/hr_plan_wizard.py b/addons/hr/wizard/hr_plan_wizard.py
index 82ce4f4e1fc..c15063d05a4 100644
--- a/addons/hr/wizard/hr_plan_wizard.py
+++ b/addons/hr/wizard/hr_plan_wizard.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
+
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
@@ -94,10 +96,10 @@ class HrPlanWizard(models.TransientModel):
activities.add(activity)
if activities:
- body += '
'
+ body += Markup('
')
for activity in activities:
- body += '
%s
' % activity
- body += '
'
+ body += Markup('
%s
') % activity
+ body += Markup('
')
employee.message_post(body=body)
if len(self.employee_ids) == 1:
diff --git a/addons/hr_expense/models/account_move.py b/addons/hr_expense/models/account_move.py
index 45f40b5edfc..875c5726e26 100644
--- a/addons/hr_expense/models/account_move.py
+++ b/addons/hr_expense/models/account_move.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
+from markupsafe import escape
from odoo import models, fields, api, _
from odoo.tools.misc import frozendict
@@ -39,7 +40,7 @@ class AccountMove(models.Model):
def _creation_message(self):
if self.expense_sheet_id:
- return _("Expense entry created from: %s", self.expense_sheet_id._get_html_link())
+ return escape(_("Expense entry created from: %s")) % self.expense_sheet_id._get_html_link()
return super()._creation_message()
@api.depends('expense_sheet_id')
diff --git a/addons/hr_expense/models/account_payment.py b/addons/hr_expense/models/account_payment.py
index a16a8af66e5..6f338878d79 100644
--- a/addons/hr_expense/models/account_payment.py
+++ b/addons/hr_expense/models/account_payment.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import escape
from odoo import models, _
from odoo.exceptions import UserError
@@ -38,5 +39,5 @@ class AccountPayment(models.Model):
# EXTENDS mail
self.ensure_one()
if self.move_id.expense_sheet_id:
- return _("Payment created for: %s", self.move_id.expense_sheet_id._get_html_link())
+ return escape(_("Payment created for: %s")) % self.move_id.expense_sheet_id._get_html_link()
return super()._creation_message()
diff --git a/addons/hr_recruitment_survey/wizard/survey_invite.py b/addons/hr_recruitment_survey/wizard/survey_invite.py
index c24d2d151c6..d55fe391a02 100644
--- a/addons/hr_recruitment_survey/wizard/survey_invite.py
+++ b/addons/hr_recruitment_survey/wizard/survey_invite.py
@@ -1,5 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup, escape
+
from odoo import fields, models, _
from odoo.tools.misc import clean_context
@@ -36,7 +38,10 @@ class SurveyInvite(models.TransientModel):
partner = self.applicant_id.partner_id
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
+ content = escape(_('The survey %(survey_link)s has been sent to %(partner_link)s')) % {
+ 'survey_link': survey_link,
+ 'partner_link': partner_link,
+ }
+ body = Markup('
%s
') % content
self.applicant_id.message_post(body=body)
return super().action_invite()
diff --git a/addons/hr_skills_slides/models/slide_channel.py b/addons/hr_skills_slides/models/slide_channel.py
index 48f56b240cf..ecfdb40ed93 100644
--- a/addons/hr_skills_slides/models/slide_channel.py
+++ b/addons/hr_skills_slides/models/slide_channel.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup, escape
+
from odoo import fields, models, _
from odoo.tools import html2plaintext
@@ -48,9 +50,11 @@ class SlideChannelPartner(models.Model):
super()._send_completed_mail()
for scp in self:
if self.env.user.employee_ids:
- msg = _('The employee has completed the course %(course)s',
- link=scp.channel_id.website_url,
- course=scp.channel_id.name)
+ msg = escape(_('The employee has completed the course %s')) % \
+ Markup('%(course)s') % {
+ 'link': scp.channel_id.website_url,
+ 'course': scp.channel_id.name,
+ }
self.env.user.employee_id.message_post(body=msg)
class Channel(models.Model):
@@ -60,7 +64,10 @@ class Channel(models.Model):
res = super()._action_add_members(target_partners)
for channel in self:
channel._message_employee_chatter(
- _('The employee subscribed to the course %(course)s', link=channel.website_url, course=channel.name),
+ Markup(_('The employee subscribed to the course %(course)s')) % {
+ 'link': channel.website_url,
+ 'course': channel.name,
+ },
target_partners)
return res
@@ -71,7 +78,10 @@ class Channel(models.Model):
for channel in self:
channel._message_employee_chatter(
- _('The employee left the course %(course)s', link=channel.website_url, course=channel.name),
+ Markup(_('The employee left the course %(course)s')) % {
+ 'link': channel.website_url,
+ 'course': channel.name,
+ },
partners)
return res
diff --git a/addons/im_livechat/tests/test_message.py b/addons/im_livechat/tests/test_message.py
index 16dd79de8e4..57c2d560946 100644
--- a/addons/im_livechat/tests/test_message.py
+++ b/addons/im_livechat/tests/test_message.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
+
from odoo import Command
from odoo.tests.common import users, tagged, TransactionCase
@@ -39,7 +41,7 @@ class TestImLivechatMessage(TransactionCase):
})
message = channel_livechat_1.message_post(
author_id=record_rating.partner_id.id,
- body="%s"
+ body=Markup("%s")
% (record_rating.rating_image_url, record_rating.rating, record_rating.feedback),
rating_id=record_rating.id,
)
diff --git a/addons/im_livechat_mail_bot/models/mail_bot.py b/addons/im_livechat_mail_bot/models/mail_bot.py
index a17e1cac292..f892f24a645 100644
--- a/addons/im_livechat_mail_bot/models/mail_bot.py
+++ b/addons/im_livechat_mail_bot/models/mail_bot.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup, escape
from odoo import models, _
@@ -13,13 +14,14 @@ class MailBot(models.AbstractModel):
if odoobot_state == "onboarding_attachement" and values.get("attachment_ids"):
self.env.user.odoobot_failed = False
self.env.user.odoobot_state = "onboarding_canned"
- return _("That's me! 🎉 Try typing : to use canned responses.")
+ return Markup(_("That's me! 🎉 Try typing %s to use canned responses.", ":"))
elif odoobot_state == "onboarding_canned" and values.get("canned_response_ids"):
self.env.user.odoobot_failed = False
self.env.user.odoobot_state = "idle"
- return _("Good, you can customize canned responses in the live chat application.
It's the end of this overview, enjoy discovering Odoo!")
+ return Markup(_("Good, you can customize canned responses in the live chat application.
') % _("created %(link)s")) % {
+ 'link': Markup('#%s') % (new_channel.id, new_channel.name)
+ }
new_channel.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment")
channel_info = new_channel.channel_info()[0]
self.env['bus.bus']._sendone(self.env.user.partner_id, 'mail.channel/legacy_insert', channel_info)
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 219294838c8..99cd2c801bf 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -23,7 +23,7 @@ from email import message_from_string
from lxml import etree
from werkzeug import urls
from xmlrpc import client as xmlrpclib
-from markupsafe import Markup
+from markupsafe import Markup, escape
from odoo import _, api, exceptions, fields, models, tools, registry, SUPERUSER_ID, Command
from odoo.exceptions import MissingError, AccessError
@@ -1353,7 +1353,7 @@ class MailThread(models.AbstractModel):
for node in to_remove:
node.getparent().remove(node)
if postprocessed:
- body = etree.tostring(root, pretty_print=False, encoding='unicode')
+ body = Markup(etree.tostring(root, pretty_print=False, encoding='unicode'))
return {'body': body, 'attachments': attachments}
def _message_parse_extract_payload(self, message, message_dict, save_original=False):
@@ -2023,7 +2023,7 @@ class MailThread(models.AbstractModel):
'model': self._name,
'res_id': self.id,
# content
- 'body': body,
+ 'body': escape(body), # escape if text, keep if markup
'message_type': message_type,
'parent_id': self._message_compute_parent_id(parent_id),
'subject': subject or False,
@@ -2485,7 +2485,7 @@ class MailThread(models.AbstractModel):
'record_name': False,
'res_id': self.id if self else res_id,
# content
- 'body': body,
+ 'body': escape(body), # escape if text, keep if markup
'is_internal': True,
'message_type': 'user_notification',
'subject': subject,
@@ -2601,7 +2601,7 @@ class MailThread(models.AbstractModel):
values_list = [dict(base_message_values,
res_id=record.id,
- body=bodies.get(record.id, ''))
+ body=escape(bodies.get(record.id, '')))
for record in self]
return self.sudo()._message_create(values_list)
diff --git a/addons/mail/tests/test_res_partner.py b/addons/mail/tests/test_res_partner.py
index 0590df65092..9096320df00 100644
--- a/addons/mail/tests/test_res_partner.py
+++ b/addons/mail/tests/test_res_partner.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from contextlib import contextmanager
+from markupsafe import Markup
from unittest.mock import patch
from uuid import uuid4
@@ -407,7 +408,7 @@ class TestPartner(MailCommon):
p1.message_subscribe(partner_ids=p3.ids)
p1_act1 = p1.activity_schedule(act_type_xmlid='mail.mail_activity_data_todo')
p1_msg1 = p1.message_post(
- body='
Log on P1
',
+ body=Markup('
Log on P1
'),
subtype_id=self.env.ref('mail.mt_comment').id
)
self.assertEqual(p1.activity_ids, p1_act1)
diff --git a/addons/mail_bot/models/mail_bot.py b/addons/mail_bot/models/mail_bot.py
index 50ad2db6154..97344eb242c 100644
--- a/addons/mail_bot/models/mail_bot.py
+++ b/addons/mail_bot/models/mail_bot.py
@@ -4,9 +4,9 @@
import itertools
import random
+from markupsafe import Markup
from odoo import models, _
-
class MailBot(models.AbstractModel):
_name = 'mail.bot'
_description = 'Mail Bot'
@@ -40,19 +40,19 @@ class MailBot(models.AbstractModel):
if odoobot_state == 'onboarding_emoji' and self._body_contains_emoji(body):
self.env.user.odoobot_state = "onboarding_command"
self.env.user.odoobot_failed = False
- return _("Great! 👍 To access special commands, start your sentence with/. Try getting help.")
+ return Markup(_("Great! 👍 To access special commands, start your sentence with/. Try getting help."))
elif odoobot_state == 'onboarding_command' and command == 'help':
self.env.user.odoobot_state = "onboarding_ping"
self.env.user.odoobot_failed = False
- return _("Wow you are a natural! Ping someone with @username to grab their attention. Try to ping me using@OdooBot in a sentence.")
+ return Markup(_("Wow you are a natural! Ping someone with @username to grab their attention. Try to ping me using@OdooBot in a sentence."))
elif odoobot_state == 'onboarding_ping' and self._is_bot_pinged(values):
self.env.user.odoobot_state = "onboarding_attachement"
self.env.user.odoobot_failed = False
- return _("Yep, I am here! 🎉 Now, try sending an attachment, like a picture of your cute dog...")
+ return Markup(_("Yep, I am here! 🎉 Now, try sending an attachment, like a picture of your cute dog..."))
elif odoobot_state == 'onboarding_attachement' and values.get("attachment_ids"):
self.env.user.odoobot_state = "idle"
self.env.user.odoobot_failed = False
- return _("I am a simple bot, but if that's a dog, he is the cutest 😊 Congratulations, you finished this tour. You can now close this chat window. Enjoy discovering Odoo.")
+ return Markup(_("I am a simple bot, but if that's a dog, he is the cutest 😊 Congratulations, you finished this tour. You can now close this chat window. Enjoy discovering Odoo."))
elif odoobot_state in (False, "idle", "not_initialized") and (_('start the tour') in body.lower()):
self.env.user.odoobot_state = "onboarding_emoji"
return _("To start, try to send me an emoji :)")
@@ -63,28 +63,28 @@ class MailBot(models.AbstractModel):
return _("That's not nice! I'm a bot but I have feelings... 💔")
# help message
elif self._is_help_requested(body) or odoobot_state == 'idle':
- return _("Unfortunately, I'm just a bot 😞 I don't understand! If you need help discovering our product, please check "
+ return Markup(_("Unfortunately, I'm just a bot 😞 I don't understand! If you need help discovering our product, please check "
"our documentation or "
- "our videos.")
+ "our videos."))
else:
# repeat question
if odoobot_state == 'onboarding_emoji':
self.env.user.odoobot_failed = True
- return _("Not exactly. To continue the tour, send an emoji: type:) and press enter.")
+ return Markup(_("Not exactly. To continue the tour, send an emoji: type:) and press enter."))
elif odoobot_state == 'onboarding_attachement':
self.env.user.odoobot_failed = True
- return _("To send an attachment, click on the icon and select a file.")
+ return Markup(_("To send an attachment, click on the icon and select a file."))
elif odoobot_state == 'onboarding_command':
self.env.user.odoobot_failed = True
- return _("Not sure what you are doing. Please, type / and wait for the propositions. Select help and press enter")
+ return Markup(_("Not sure what you are doing. Please, type / and wait for the propositions. Select help and press enter"))
elif odoobot_state == 'onboarding_ping':
self.env.user.odoobot_failed = True
- return _("Sorry, I am not listening. To get someone's attention, ping him. Write @OdooBot and select me.")
+ return Markup(_("Sorry, I am not listening. To get someone's attention, ping him. Write @OdooBot and select me."))
return random.choice([
- _("I'm not smart enough to answer your question. To follow my guide, ask: start the tour."),
+ Markup(_("I'm not smart enough to answer your question. To follow my guide, ask: start the tour.")),
_("Hmmm..."),
_("I'm afraid I don't understand. Sorry!"),
- _("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language... I can show you features if you write: start the tour.")
+ Markup(_("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language... I can show you features if you write: start the tour."))
])
return False
diff --git a/addons/mail_bot/models/res_users.py b/addons/mail_bot/models/res_users.py
index b9bb935ee71..293c2aa69ec 100644
--- a/addons/mail_bot/models/res_users.py
+++ b/addons/mail_bot/models/res_users.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
+
from odoo import models, fields, _
class Users(models.Model):
@@ -32,7 +34,11 @@ class Users(models.Model):
odoobot_id = self.env['ir.model.data']._xmlid_to_res_id("base.partner_root")
channel_info = self.env['mail.channel'].channel_get([odoobot_id, self.partner_id.id])
channel = self.env['mail.channel'].browse(channel_info['id'])
- message = _("Hello, Odoo's chat helps employees collaborate efficiently. I'm here to help you discover its features. Try to send me an emoji:)")
+ message = Markup("%s %s %s:)") % (
+ _("Hello,"),
+ _("Odoo's chat helps employees collaborate efficiently. I'm here to help you discover its features."),
+ _("Try to send me an emoji")
+ )
channel.sudo().message_post(body=message, author_id=odoobot_id, message_type="comment", subtype_xmlid="mail.mt_comment")
self.sudo().odoobot_state = 'onboarding_emoji'
return channel
diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py
index 3932e348300..2d5cce39fb5 100644
--- a/addons/maintenance/models/maintenance.py
+++ b/addons/maintenance/models/maintenance.py
@@ -3,6 +3,7 @@
import ast
from datetime import date, datetime, timedelta
+from markupsafe import escape
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError
@@ -386,10 +387,7 @@ 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._get_html_link()
- )
+ note = escape(_('Request planned for %s')) % request.equipment_id._get_html_link()
else:
note = False
request.activity_schedule(
diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py
index 91b43a264a4..2ac07c3c815 100644
--- a/addons/mass_mailing/models/mailing.py
+++ b/addons/mass_mailing/models/mailing.py
@@ -780,8 +780,8 @@ class MassMailing(models.Model):
])
opt_out_records.write({'opt_out': value})
- message = _('The recipient unsubscribed from %s mailing list(s)') \
- if value else _('The recipient subscribed to %s mailing list(s)')
+ message = Markup(_('The recipient unsubscribed from %s mailing list(s)')) \
+ if value else Markup(_('The recipient subscribed to %s mailing list(s)'))
for record in records:
# filter the list_id by record
record_lists = opt_out_records.filtered(lambda rec: rec.contact_id.id == record.id)
diff --git a/addons/mass_mailing/wizard/mailing_mailing_test.py b/addons/mass_mailing/wizard/mailing_mailing_test.py
index b00684fb8c6..5df68d198bd 100644
--- a/addons/mass_mailing/wizard/mailing_mailing_test.py
+++ b/addons/mass_mailing/wizard/mailing_mailing_test.py
@@ -77,17 +77,16 @@ class TestMassMailing(models.TransientModel):
_('Test mailing successfully sent to %s', mail_sudo.email_to))
elif mail_sudo.state == 'exception':
notification_messages.append(
- _('Test mailing could not be sent to %s: %s',
- mail_sudo.email_to,
- mail_sudo.failure_reason)
+ _('Test mailing could not be sent to %s:', mail_sudo.email_to) +
+ (Markup(" ") + mail_sudo.failure_reason)
)
# manually delete the emails since we passed 'auto_delete: False'
mails_sudo.unlink()
if notification_messages:
- self.mass_mailing_id._message_log(body='
%s
' % ''.join(
- ['
%s
' % notification_message for notification_message in notification_messages]
+ self.mass_mailing_id._message_log(body=Markup('
%s
') % ''.join(
+ [Markup('
%s
') % notification_message for notification_message in notification_messages]
))
return True
diff --git a/addons/mass_mailing_sms/wizard/mailing_sms_test.py b/addons/mass_mailing_sms/wizard/mailing_sms_test.py
index b013ce33df7..44dad036712 100644
--- a/addons/mass_mailing_sms/wizard/mailing_sms_test.py
+++ b/addons/mass_mailing_sms/wizard/mailing_sms_test.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
from odoo import fields, models, _
from odoo.addons.phone_validation.tools import phone_validation
@@ -59,8 +60,8 @@ class MassSMSTest(models.TransientModel):
)
if notification_messages:
- self.mailing_id._message_log(body='
%s
' % ''.join(
- ['
%s
' % notification_message for notification_message in notification_messages]
+ self.mailing_id._message_log(body=Markup('
%s
') % ''.join(
+ [Markup('
%s
') % notification_message for notification_message in notification_messages]
))
return True
diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py
index 30d62e99607..494c398d27b 100644
--- a/addons/mrp/models/mrp_unbuild.py
+++ b/addons/mrp/models/mrp_unbuild.py
@@ -7,7 +7,7 @@ from odoo.tools import float_compare, float_round
from odoo.osv import expression
from collections import defaultdict
-
+from markupsafe import escape
class MrpUnbuild(models.Model):
_name = "mrp.unbuild"
@@ -202,12 +202,11 @@ class MrpUnbuild(models.Model):
produced_move_line_ids = produce_moves.mapped('move_line_ids').filtered(lambda ml: ml.qty_done > 0)
consume_moves.mapped('move_line_ids').write({'produce_line_ids': [(6, 0, produced_move_line_ids.ids)]})
if self.mo_id:
- unbuild_msg = _(
- "%(qty)s %(measure)s unbuilt in %(order)s",
- qty=self.product_qty,
- measure=self.product_uom_id.name,
- order=self._get_html_link(),
- )
+ unbuild_msg = escape(_("%(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_xmlid='mail.mt_note',
diff --git a/addons/payment/models/payment_transaction.py b/addons/payment/models/payment_transaction.py
index 477d317f85d..ec18267b583 100644
--- a/addons/payment/models/payment_transaction.py
+++ b/addons/payment/models/payment_transaction.py
@@ -8,6 +8,7 @@ from datetime import datetime
import psycopg2
from dateutil import relativedelta
+from markupsafe import Markup
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
@@ -1109,7 +1110,7 @@ class PaymentTransaction(models.Model):
ref=self.reference, amount=formatted_amount, provider_name=self.provider_id.name
)
if self.state_message:
- message += " " + _("Error: %s", self.state_message)
+ message += Markup(" ") + _("Error: %s", self.state_message)
else:
message = _(
("The transaction with reference %(ref)s for %(amount)s is canceled "
@@ -1119,7 +1120,7 @@ class PaymentTransaction(models.Model):
provider_name=self.provider_id.name
)
if self.state_message:
- message += " " + _("Reason: %s", self.state_message)
+ message += Markup(" ") + _("Reason: %s", self.state_message)
return message
def _get_last(self):
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 06f7d92c4ad..746f4843834 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -6,6 +6,7 @@ from markupsafe import Markup
from functools import partial
from itertools import groupby
from collections import defaultdict
+from markupsafe import escape
import psycopg2
import pytz
@@ -501,10 +502,9 @@ 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._get_html_link(),
- )
+ message = escape(_("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/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 851d712cf21..fef7720f3f9 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -4,6 +4,7 @@
from collections import defaultdict
from datetime import timedelta
from itertools import groupby
+from markupsafe import Markup
from odoo import api, fields, models, _, Command
from odoo.exceptions import AccessError, UserError, ValidationError
@@ -1500,7 +1501,7 @@ class PosSession(models.Model):
f"{self.currency_id.round(difference)} " \
f"{self.currency_id.symbol if self.currency_id.position == 'after' else ''} "
if notes:
- message += notes.replace('\n', ' ')
+ message += notes.replace('\n', Markup(' '))
if message:
self.message_post(body=message)
@@ -1563,7 +1564,7 @@ class PosSession(models.Model):
message_content = [f"Cash {extras['translatedType']}", f'- Amount: {extras["formattedAmount"]}']
if reason:
message_content.append(f'- Reason: {reason}')
- self.message_post(body=' \n'.join(message_content))
+ self.message_post(body=Markup(' \n').join(message_content))
def get_onboarding_data(self):
return {
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index 1feb1badfd1..2f6bdd4f81e 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -3,6 +3,7 @@
import difflib
import logging
import time
+from markupsafe import escape
from odoo import api, fields, models, Command, _
@@ -130,7 +131,7 @@ class AccountMove(models.Model):
if not purchases:
continue
refs = [purchase._get_html_link() for purchase in purchases]
- message = _("This vendor bill has been created from: %s") % ','.join(refs)
+ message = escape(_("This vendor bill has been created from: %s")) % ','.join(refs)
move.message_post(body=message)
return moves
@@ -145,7 +146,7 @@ class AccountMove(models.Model):
diff_purchases = new_purchases - old_purchases[i]
if diff_purchases:
refs = [purchase._get_html_link() for purchase in diff_purchases]
- message = _("This vendor bill has been modified from: %s") % ','.join(refs)
+ message = escape(_("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 5281d3ec80b..7f1619c0f0b 100644
--- a/addons/sale/models/sale_order.py
+++ b/addons/sale/models/sale_order.py
@@ -3,7 +3,7 @@
from datetime import timedelta
from itertools import groupby
-from markupsafe import Markup
+from markupsafe import escape
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import AccessError, UserError, ValidationError
@@ -989,10 +989,9 @@ class SaleOrder(models.Model):
self.show_update_fpos = False
if self.partner_id:
- self.message_post(body=_(
- "Product taxes have been recomputed according to fiscal position %s.",
+ self.message_post(body=escape(_("Product taxes have been recomputed according to fiscal position %s.")) % \
self.fiscal_position_id._get_html_link() if self.fiscal_position_id else "",
- ))
+ )
def action_update_prices(self):
self.ensure_one()
@@ -1000,10 +999,8 @@ class SaleOrder(models.Model):
self._recompute_prices()
if self.pricelist_id:
- message = _(
- "Product prices have been recomputed according to pricelist %s.",
- self.pricelist_id._get_html_link(),
- )
+ message = escape(_("Product prices have been recomputed according to pricelist %s.")) % \
+ self.pricelist_id._get_html_link()
else:
message = _("Product prices have been recomputed.")
self.message_post(body=message)
@@ -1508,7 +1505,7 @@ class SaleOrder(models.Model):
order.activity_schedule(
'sale.mail_act_sale_upsell',
user_id=order.user_id.id or order.partner_id.user_id.id,
- note=_("Upsell %(order)s for customer %(customer)s", order=order_ref, customer=customer_ref))
+ note=escape(_("Upsell %(order)s for customer %(customer)s")) % {"order":order_ref, "customer":customer_ref})
def _prepare_analytic_account_data(self, prefix=None):
""" Prepare SO analytic account creation values.
diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py
index b5bb9c1c334..d8c3e0dacc8 100644
--- a/addons/sale/models/sale_order_line.py
+++ b/addons/sale/models/sale_order_line.py
@@ -3,6 +3,7 @@
from collections import defaultdict
from datetime import timedelta
+from markupsafe import Markup
from odoo import api, fields, models, _
from odoo.exceptions import UserError
@@ -1017,18 +1018,18 @@ class SaleOrderLine(models.Model):
orders = self.mapped('order_id')
for order in orders:
order_lines = self.filtered(lambda x: x.order_id == order)
- msg = "" + _("The ordered quantity has been updated.") + "
"
+ msg = Markup("%s
") % _("The ordered quantity has been updated.")
for line in order_lines:
- msg += "
'),
'email_from': False,
'email_to': 'test@example.com',
'is_notification': True,
diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py
index d04e3aec4f6..d1609aa18af 100644
--- a/addons/test_mail/tests/test_mail_message.py
+++ b/addons/test_mail/tests/test_mail_message.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
+from markupsafe import Markup
from unittest.mock import patch
from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon
@@ -483,7 +484,7 @@ class TestMessageAccess(MailCommon):
test_record.message_subscribe((partner_1 | self.user_admin.partner_id).ids)
message = test_record.message_post(
- body='
This is First Message
', subject='Subject',
+ body=Markup('
This is First Message
'), subject='Subject',
message_type='comment', subtype_xmlid='mail.mt_note')
# portal user have no rights to read the message
with self.assertRaises(AccessError):
diff --git a/addons/test_mail/tests/test_mail_thread_internals.py b/addons/test_mail/tests/test_mail_thread_internals.py
index 4bafa0458c5..bcee37e22ea 100644
--- a/addons/test_mail/tests/test_mail_thread_internals.py
+++ b/addons/test_mail/tests/test_mail_thread_internals.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
from unittest.mock import patch
from unittest.mock import DEFAULT
@@ -36,7 +37,7 @@ class TestAPI(MailCommon, TestRecipients):
# post a note
message = ticket_record.message_post(
attachment_ids=attachments.ids,
- body="
'),
partner_ids=self.partner_2.ids,
subject='Test Notify',
)
diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py
index e5471931dde..2d0ead3f7f3 100644
--- a/addons/test_mail/tests/test_message_post.py
+++ b/addons/test_mail/tests/test_message_post.py
@@ -6,7 +6,7 @@ import base64
from datetime import datetime, timedelta
from freezegun import freeze_time
from itertools import product
-from markupsafe import escape
+from markupsafe import escape, Markup
from unittest.mock import patch
from odoo import tools
@@ -295,7 +295,7 @@ class TestMessageNotify(TestMessagePostCommon):
with self.mock_mail_gateway():
new_notification = test_record.message_notify(
- body='
You have received a notification
',
+ body=Markup('
You have received a notification
'),
partner_ids=[self.partner_1.id, self.partner_admin.id, self.partner_employee_2.id],
subject='This should be a subject',
)
@@ -342,7 +342,7 @@ class TestMessageNotify(TestMessagePostCommon):
with self.mock_mail_gateway():
new_notification = test_record.message_notify(
- body='
You have received a notification
',
+ body=Markup('
You have received a notification
'),
partner_ids=(self.partner_1 + self.partner_employee).ids,
subject='This should be a subject',
)
@@ -351,7 +351,7 @@ class TestMessageNotify(TestMessagePostCommon):
with self.mock_mail_gateway():
new_notification = test_record.message_notify(
- body='
You have received a notification
',
+ body=Markup('
You have received a notification
'),
notify_author=True,
partner_ids=(self.partner_1 + self.partner_employee).ids,
subject='This should be a subject',
@@ -365,7 +365,7 @@ class TestMessageNotify(TestMessagePostCommon):
with self.mock_mail_gateway():
new_notification = test_record.with_context(mail_notify_author=True).message_notify(
- body='
You have received a notification
',
+ body=Markup('
You have received a notification
'),
partner_ids=(self.partner_1 + self.partner_employee).ids,
subject='This should be a subject',
)
@@ -383,7 +383,7 @@ class TestMessageNotify(TestMessagePostCommon):
with self.assertRaises(ValueError):
test_records.message_notify(
- body='
'),
partner_ids=self.partner_1.ids,
subject='This should not be accepted',
**parameters
@@ -481,7 +481,7 @@ class TestMessageNotify(TestMessagePostCommon):
# support of subtype xml id
new_message = test_record.message_notify(
- body='
You will not receive a notification
',
+ body=Markup('
You will not receive a notification
'),
partner_ids=self.partner_1.ids,
subtype_xmlid='mail.mt_note',
)
@@ -494,7 +494,7 @@ class TestMessageNotify(TestMessagePostCommon):
people without having a document. """
with self.mock_mail_gateway():
new_notification = self.env['mail.thread'].message_notify(
- body='
You have received a notification
',
+ body=Markup('
You have received a notification
'),
partner_ids=[self.partner_1.id, self.partner_admin.id, self.partner_employee_2.id],
subject='This should be a subject',
)
@@ -535,7 +535,7 @@ class TestMessageLog(TestMessagePostCommon):
test_record.message_subscribe(self.partner_employee_2.ids)
new_note = test_record._message_log(
- body='
'),
message_type='comment',
parent_id=parent_msg.id,
partner_ids=[self.partner_1.id],
@@ -1144,7 +1144,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
# post a reply to the reply: check parent is the first one
with self.mock_mail_gateway():
new_msg = test_record.message_post(
- body='
Test Answer Bis
',
+ body=Markup('
Test Answer Bis
'),
message_type='comment',
subtype_xmlid='mail.mt_comment',
parent_id=msg.id,
@@ -1697,7 +1697,7 @@ class TestMessagePostLang(MailCommon, TestRecipients):
with self.mock_mail_gateway():
test_records[1].message_post(
- body='
Hello
',
+ body=Markup('
Hello
'),
email_layout_xmlid='mail.test_layout',
message_type='comment',
subject='Subject',
@@ -1820,7 +1820,7 @@ class TestMessagePostLang(MailCommon, TestRecipients):
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app():
record.message_post(
- body='
Hi there
',
+ body=Markup('
Hi there
'),
email_layout_xmlid=email_layout_xmlid,
message_type='comment',
subject='TeDeum',
diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py
index ce57c95d01e..9f8508f6d57 100644
--- a/addons/test_mail/tests/test_performance.py
+++ b/addons/test_mail/tests/test_performance.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from contextlib import nullcontext
+from markupsafe import Markup
from unittest.mock import patch
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
@@ -589,7 +589,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(admin=1, employee=1):
record._message_log(
- body='
Test _message_log
',
+ body=Markup('
Test _message_log
'),
message_type='comment')
@users('admin', 'employee')
@@ -603,7 +603,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(admin=1, employee=1):
records._message_log_batch(
bodies=dict(
- (record.id, '
Test _message_log
')
+ (record.id, Markup('
Test _message_log
'))
for record in records
),
message_type='comment')
@@ -629,7 +629,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(admin=7, employee=7):
record.message_post(
- body='
Test message_post as log
',
+ body=Markup('
Test message_post as log
'),
subtype_xmlid='mail.mt_note',
message_type='comment')
@@ -640,7 +640,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(admin=7, employee=7):
record.message_post(
- body='
Test Post Performances basic
',
+ body=Markup('
Test Post Performances basic
'),
partner_ids=[],
message_type='comment',
subtype_xmlid='mail.mt_comment')
@@ -653,7 +653,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(admin=30, employee=30):
record.message_post(
- body='
Test Post Performances with an email ping
',
+ body=Markup('
Test Post Performances with an email ping
'),
partner_ids=self.customer.ids,
message_type='comment',
subtype_xmlid='mail.mt_comment')
@@ -665,7 +665,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(admin=17, employee=17):
record.message_post(
- body='
Test Post Performances with an inbox ping
',
+ body=Markup('
Test Post Performances with an inbox ping
'),
partner_ids=self.user_test.partner_id.ids,
message_type='comment',
subtype_xmlid='mail.mt_comment')
@@ -854,7 +854,7 @@ class TestMailComplexPerformance(BaseMailPerformance):
# about 20 (19?) queries per additional customer group
with self.assertQueryCount(admin=53, employee=52):
record.message_post(
- body='
Test Post Performances
',
+ body=Markup('
Test Post Performances
'),
message_type='comment',
subtype_xmlid='mail.mt_comment')
@@ -1311,7 +1311,7 @@ class TestMailHeavyPerformancePost(BaseMailPerformance):
# with self.assertQueryCount(employee=63), enable_logging:
with self.assertQueryCount(employee=60):
record_container.with_context({}).message_post(
- body='
Test body
',
+ body=Markup('
Test body
'),
subject='Test Subject',
message_type='notification',
subtype_xmlid=None,
diff --git a/addons/test_mail_full/tests/test_mail_performance.py b/addons/test_mail_full/tests/test_mail_performance.py
index 5cd06c4de67..6a7a049c689 100644
--- a/addons/test_mail_full/tests/test_mail_performance.py
+++ b/addons/test_mail_full/tests/test_mail_performance.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance
from odoo.tests.common import users, warmup
@@ -84,7 +85,7 @@ class TestMailPerformance(BaseMailPerformance):
with self.assertQueryCount(employee=85): # tmf: 85
new_message = record_ticket.message_post(
attachment_ids=attachments.ids,
- body='
Test Content
',
+ body=Markup('
Test Content
'),
message_type='comment',
subject='Test Subject',
subtype_xmlid='mail.mt_comment',
diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py
index 30f65fe277d..51200f659dc 100644
--- a/addons/website_crm_partner_assign/models/crm_lead.py
+++ b/addons/website_crm_partner_assign/models/crm_lead.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import random
+from markupsafe import Markup
from odoo import api, fields, models, _
from odoo.exceptions import AccessDenied, AccessError, UserError
@@ -189,23 +190,23 @@ class CrmLead(models.Model):
return res_partner_ids
def partner_interested(self, comment=False):
- message = _('
I am interested by this lead.
')
+ message = Markup('
%s
') % _('I am interested by this lead.')
if comment:
- message += '
%s
' % html_escape(comment)
+ message += Markup('
%s
') % comment
for lead in self:
lead.message_post(body=message)
lead.sudo().convert_opportunity(lead.partner_id) # sudo required to convert partner data
def partner_desinterested(self, comment=False, contacted=False, spam=False):
if contacted:
- message = '
%s
' % _('I am not interested by this lead. I contacted the lead.')
+ message = Markup('
%s
') % _('I am not interested by this lead. I contacted the lead.')
else:
- message = '
%s
' % _('I am not interested by this lead. I have not contacted the lead.')
+ message = Markup('
%s
') % _('I am not interested by this lead. I have not contacted the lead.')
partner_ids = self.env['res.partner'].search(
[('id', 'child_of', self.env.user.partner_id.commercial_partner_id.id)])
self.message_unsubscribe(partner_ids=partner_ids.ids)
if comment:
- message += '
%s
' % html_escape(comment)
+ message += Markup('
%s
') % comment
self.message_post(body=message)
values = {
'partner_assigned_id': False,
diff --git a/addons/website_payment/models/payment_transaction.py b/addons/website_payment/models/payment_transaction.py
index e416179de3e..d8d9aefb48a 100644
--- a/addons/website_payment/models/payment_transaction.py
+++ b/addons/website_payment/models/payment_transaction.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from markupsafe import Markup
from odoo import _, fields, models
@@ -20,8 +21,8 @@ class PaymentTransaction(models.Model):
if value:
if hasattr(value, 'name'):
value = value.name
- msg.append(' - %s: %s' % (field_name, value))
- tx.payment_id._message_log(body=''.join(msg))
+ msg.append(Markup(' - %s: %s') % (field_name, value))
+ tx.payment_id._message_log(body=Markup().join(msg))
def _send_donation_email(self, is_internal_notification=False, comment=None, recipient_email=None):
self.ensure_one()