[IMP] *: enforce usage of Markup in mail

When using message_post, the body format must be explicitly specified.
If html is expected, a Markup object should be used.
If text is given, the content will be escaped.

Before this PR:
message_post was unaware if the content of a message was HTML or
text. This lead to multiple situation where the content was
incorrectly considered as HTML and led to display errors.
In
  self.message_post(body="Hello %s!" % self.name)
if the name contained HTML, it would be evaluated.

In
  self.message_post(body="Contact Raoul <raoul@caramail.be>")
the email would not be displayed as considered as unknown HTML and
discarded by the sanitizer

Now each call must explict the type of content.
Use the escape() helper to properly combine Markup and translations.
It would also be acceptable to use Markup() to wrap a static
translation but escape is better as one can not guarantee the content
of a translation.

closes odoo/odoo#111850

Related: odoo/documentation#3612
Related: odoo/enterprise#36728
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Martin Trigaux
2023-04-13 16:39:48 +02:00
parent e1865b3d38
commit 69f911d994
61 changed files with 344 additions and 268 deletions
+5 -6
View File
@@ -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 \
'<br/>' + _('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('<br/>') + _('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
+2 -4
View File
@@ -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
+4 -9
View File
@@ -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(
+2 -1
View File
@@ -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
+4 -3
View File
@@ -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()
@@ -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:<ul><li>%(link1)s cancelling '
'{percent:.2f}%% of {amount}</li><li>%(link0)s postponing it to {new_date}</li></ul>',
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<ul><li>%(link1)s %(second)s</li><li>%(link2)s %(third)s</li></ul>") % {
'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='<br/>\n'.join(destination_messages))
destination_move.message_post(body=Markup('<br/>\n').join(destination_messages))
for accrual_move, messages in accrual_move_messages.items():
accrual_move.message_post(body='<br/>\n'.join(messages))
accrual_move.message_post(body=Markup('<br/>\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 = _("<li>{amount} ({debit_credit}) from {link}, <strong>%(account_source_name)s</strong></li>")
rslt = _("This entry transfers the following amounts to <strong>%(destination)s</strong> <ul>", 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 += '</ul>'
transfer_format = Markup("<li>%s, <strong>%%(account_source_name)s</strong></li>") % \
_("{amount} ({debit_credit}) from {link}")
rslt = Markup(_("This entry transfers the following amounts to %(destination)s") + "<ul>%(transfer_logs)s</ul>") % {
'destination': Markup("<strong>%s</strong>") % 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 = _("<li>{amount} ({debit_credit}) from <strong>%s</strong> were transferred to <strong>{account_target_name}</strong> by {link}</li>")
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 '<ul>' + content + '</ul>' or None
if not balances_per_account:
return None
transfer_format = Markup(
_("{amount} ({debit_credit}) from %s were transferred to <strong>{account_target_name}</strong> by {link}")
)
return Markup("<ul>%s</ul>") % Markup().join([
Markup("<li>%s</li>") % \
self._format_strings(transfer_format, transfer_move, balance) % (Markup("<strong>%s</strong>") % 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 '',
+7 -6
View File
@@ -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'),
@@ -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
@@ -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("<strong>%s</strong>") % \
_("Format used to import the invoice: %s",
self.env['ir.model']._get(self._name).name)
if logs:
body = _(
"<strong>Format used to import the invoice: %s</strong> <p><li> %s </li></p>",
str(self._description), "</li><li>".join(logs)
)
else:
body = _("<strong>Format used to import the invoice: %s</strong>", str(self._description))
body += Markup("<ul>%s</ul>") % \
Markup().join(Markup("<li>%s</li>") % 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(
"<li><font style='color:Blue;'><strong>" + item['artifactPath'] + "</strong></font></li>")
Markup("<li><font style='color:Blue;'><strong>%s</strong></font></li>") % item['artifactPath'])
for detail in item['Item']:
if detail['errorLevel'] == 'WARN':
errors_cnt += 1
report.append(
"<li><font style='color:Orange;'><strong>" + detail['errorText'] + "</strong></font></li>")
Markup("<li><font style='color:Orange;'><strong>%s</strong></font></li>") % detail['errorText'])
elif detail['errorLevel'] == 'ERROR':
errors_cnt += 1
report.append(
"<li><font style='color:Tomato;'><strong>" + detail['errorText'] + "</strong></font></li>")
Markup("<li><font style='color:Tomato;'><strong>%s</strong></font></li>") % detail['errorText'])
if errors_cnt == 0:
invoice.message_post(body=f"<font style='color:Green;'><strong>ECOSIO: All clear for format {ecosio_format}!</strong></font>")
invoice.message_post(body=Markup("<font style='color:Green;'><strong>ECOSIO: All clear for format %s!</strong></font>") % ecosio_format)
else:
invoice.message_post(
body=f"<font style='color:Tomato;'><strong>ECOSIO ERRORS/WARNINGS for format {ecosio_format}</strong></font>: <ul> "
+ "\n".join(report) + " </ul>"
body=Markup("<font style='color:Tomato;'><strong>ECOSIO ERRORS/WARNINGS for format %s</strong></font>: <ul>%s</<ul>") % (
ecosio_format,
Markup().join(report)
)
)
return response
+2 -4
View File
@@ -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)
@@ -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)
+11 -10
View File
@@ -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 = "<p>%s<p>" % Markup(_("Meeting scheduled at %(html_time)s<br/>Subject: %(subject)s<br/>Duration: %(duration)s")) % {
'html_time': Markup("<time datetime='%(meeting_start)s+00:00'>%(meeting_user_time)s</time>") % {
'meeting_start': meeting.start,
'meeting_user_time': meeting_usertime,
},
'subject': meeting._get_html_link(), # Already Markup valid
'duration': duration,
meeting_usertime = fields.Datetime.to_string(fields.Datetime.context_timestamp(self, meeting.start))
meeting_time = Markup("<time datetime='%(meeting_start)s+00:00'>%(meeting_user_time)s</time>") % {
'meeting_start': meeting.start,
'meeting_user_time': meeting_usertime,
}
message = Markup("<p>%(meeting)s<br/>%(subject_string)s %(subject_link)s<br/>%(duration)s<p>") % {
'meeting': escape(_("Meeting scheduled at %s")) % meeting_time,
'subject_string': _("Subject: "),
'subject_link': meeting._get_html_link(),
'duration': _("Duration: %s", duration),
}
return self.message_post(body=message)
+3 -2
View File
@@ -7,6 +7,7 @@ import random
import threading
from ast import literal_eval
from markupsafe import Markup
from odoo import api, exceptions, fields, models, _
from odoo.osv import expression
@@ -263,13 +264,13 @@ class Team(models.Model):
# format result messages
logs = self._action_assign_leads_logs(teams_data, members_data)
html_message = '<br />'.join(logs)
html_message = Markup('<br />').join(logs)
notif_message = ' '.join(logs)
# log a note in case of manual assign (as this method will mainly be called
# on singleton record set, do not bother doing a specific message per team)
log_action = _("Lead Assignment requested by %(user_name)s", user_name=self.env.user.name)
log_message = "<p>%s<br /><br />%s</p>" % (log_action, html_message)
log_message = Markup("<p>%s<br /><br />%s</p>") % (log_action, html_message)
self._message_log_batch(bodies=dict((team.id, log_message) for team in self))
return {
+2 -1
View File
@@ -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(
'<div style="margin-bottom: 4px;"><p>%s:</p>%s<br /></div>' % (
Markup('<div style="margin-bottom: 4px;"><p>%s:</p>%s<br /></div>') % (
_('Lost Comment'),
self.lost_feedback
)
+2 -4
View File
@@ -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):
@@ -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 = '<p>%(message)s: <ul>%(booth_names)s</ul></p>' % {
body = Markup('<p>%(message)s: <ul>%(booth_names)s</ul></p>') % {
'message': _('Your order has been cancelled because the following booths have been reserved'),
'booth_names': ''.join('<li>%s</li>' % booth.display_name for booth in self.event_booth_id)
'booth_names': Markup().join(Markup('<li>%s</li>') % booth.display_name for booth in self.event_booth_id)
}
other_registrations = self.search([
('event_booth_id', 'in', self.event_booth_id.ids),
@@ -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 += _(
"<br/>Reward (badge %(badge_name)s) for every succeeding user was sent to %(users)s.",
message_body += Markup("<br/>") + _(
"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 += _("<br/>Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")
message_body += Markup("<br/>") + _("Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")
# reward bests
reward_message = _("<br/> %(rank)d. %(user_name)s - %(reward_name)s")
reward_message = Markup("<br/> %(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 += _("<br/>Special rewards were sent to the top competing users. The ranking for this challenge is:")
message_body += Markup("<br/>") + _("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,
+4 -4
View File
@@ -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. </br>"
"It will not be synced as long at it is not updated.</br>"
"%(reason)s", reason=reason)
body = _("The following event could not be synced with Google Calendar.") + Markup("<br/>") + \
_("It will not be synced as long at it is not updated.") + Markup("<br/>") + \
reason
if event:
event.message_post(
+3 -3
View File
@@ -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(_(
'<b>Congratulations!</b> May I recommend you to setup an <a href="%s">onboarding plan?</a>',
url,
)
)) % url
employees._message_log_batch(onboarding_notes_bodies)
return employees
+5 -3
View File
@@ -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 += '<ul>'
body += Markup('<ul>')
for activity in activities:
body += '<li>%s</li>' % activity
body += '</ul>'
body += Markup('<li>%s</li>') % activity
body += Markup('</ul>')
employee.message_post(body=body)
if len(self.employee_ids) == 1:
+2 -1
View File
@@ -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')
+2 -1
View File
@@ -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()
@@ -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 = '<p>%s</p>' % 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('<p>%s</p>') % content
self.applicant_id.message_post(body=body)
return super().action_invite()
@@ -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 <a href="%(link)s">%(course)s</a>',
link=scp.channel_id.website_url,
course=scp.channel_id.name)
msg = escape(_('The employee has completed the course %s')) % \
Markup('<a href="%(link)s">%(course)s</a>') % {
'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 <a href="%(link)s">%(course)s</a>', link=channel.website_url, course=channel.name),
Markup(_('The employee subscribed to the course <a href="%(link)s">%(course)s</a>')) % {
'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 <a href="%(link)s">%(course)s</a>', link=channel.website_url, course=channel.name),
Markup(_('The employee left the course <a href="%(link)s">%(course)s</a>')) % {
'link': channel.website_url,
'course': channel.name,
},
partners)
return res
+3 -1
View File
@@ -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="<img src='%s' alt=':%s/5' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
body=Markup("<img src='%s' alt=':%s/5' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s")
% (record_rating.rating_image_url, record_rating.rating, record_rating.feedback),
rating_id=record_rating.id,
)
@@ -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! 🎉<br/>Try typing <span class=\"o_odoobot_command\">:</span> to use canned responses.")
return Markup(_("That's me! 🎉<br/>Try typing %s to use canned responses.", "<span class=\"o_odoobot_command\">:</span>"))
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.<br/><br/><b>It's the end of this overview</b>, enjoy discovering Odoo!")
return Markup(_("Good, you can customize canned responses in the live chat application.<br/><br/><b>It's the end of this overview</b>, enjoy discovering Odoo!"))
# repeat question if needed
elif odoobot_state == 'onboarding_canned' and not self._is_help_requested(body):
self.env.user.odoobot_failed = True
return _("Not sure what you are doing. Please, type <span class=\"o_odoobot_command\">:</span> and wait for the propositions. Select one of them and press enter.")
return escape(_("Not sure what you are doing. Please, type %s and wait for the propositions. Select one of them and press enter.")) % \
Markup("<span class=\"o_odoobot_command\">:</span>")
return super(MailBot, self)._get_answer(record, body, values, command)
@@ -7,6 +7,7 @@ import pytz
import markupsafe
from collections import defaultdict
from markupsafe import Markup
from odoo import models, fields, api, _
from odoo.tools import html_escape, float_is_zero, float_compare
@@ -125,12 +126,12 @@ class AccountEdiFormat(models.Model):
if not response.get("error"):
error = []
odoobot = self.env.ref("base.partner_root")
invoice.message_post(author_id=odoobot.id, body=_(
invoice.message_post(author_id=odoobot.id, body=Markup(_(
"Somehow this invoice had been submited to government before." \
"<br/>Normally, this should not happen too often" \
"<br/>Just verify value of invoice by uploade json to government website " \
"<a href='https://einvoice1.gst.gov.in/Others/VSignedInvoice'>here<a>."
))
)))
if "no-credit" in error_codes:
return {invoice: {
"success": False,
@@ -180,12 +181,12 @@ class AccountEdiFormat(models.Model):
if "9999" in error_codes:
response = {}
odoobot = self.env.ref("base.partner_root")
invoice.message_post(author_id=odoobot.id, body=_(
invoice.message_post(author_id=odoobot.id, body=Markup(_(
"Somehow this invoice had been cancelled to government before." \
"<br/>Normally, this should not happen too often" \
"<br/>Just verify by logging into government website " \
"<a href='https://einvoice1.gst.gov.in'>here<a>."
))
)))
if "no-credit" in error_codes:
return {invoice: {
"success": False,
@@ -4,6 +4,7 @@
import re
import json
from datetime import timedelta
from markupsafe import Markup
from odoo import models, fields, api, _
from odoo.tools import html_escape
@@ -150,12 +151,12 @@ class AccountEdiFormat(models.Model):
if "312" in error_codes:
# E-waybill is already canceled
# this happens when timeout from the Government portal but IRN is generated
error_message = "<br/>".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message") or self._l10n_in_edi_ewaybill_get_error_message(e.get('code')))) for e in error])
error_message = Markup("<br/>").join([Markup("[%s] %s") % (e.get("code"), e.get("message") or self._l10n_in_edi_ewaybill_get_error_message(e.get('code'))) for e in error])
error = []
response = {"data": ""}
odoobot = self.env.ref("base.partner_root")
invoices.message_post(author_id=odoobot.id, body=
"%s<br/>%s:<br/>%s" %(
Markup("%s<br/>%s:<br/>%s") %(
_("Somehow this E-waybill has been canceled in the government portal before. You can verify by checking the details into the government (https://ewaybillgst.gov.in/Others/EBPrintnew.asp)"),
_("Error"),
error_message
@@ -168,7 +169,7 @@ class AccountEdiFormat(models.Model):
"blocking_level": "error",
}
elif error:
error_message = "<br/>".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message") or self._l10n_in_edi_ewaybill_get_error_message(e.get('code')))) for e in error])
error_message = Markup("<br/>").join([Markup("[%s] %s") % (e.get("code"), e.get("message") or self._l10n_in_edi_ewaybill_get_error_message(e.get('code'))) for e in error])
blocking_level = "error"
if "404" in error_codes:
blocking_level = "warning"
+2 -1
View File
@@ -7,6 +7,7 @@ import logging
import re
from datetime import datetime
from markupsafe import escape
from odoo import api, fields, models, _
from odoo.tools import float_repr, float_compare
@@ -352,7 +353,7 @@ class AccountMove(models.Model):
})
with other_invoice._get_edi_creation() as other_invoice:
self.env['account.edi.format']._import_fattura_pa(tree, other_invoice)
other_invoice.message_post(body=_("Created from attachment in %s", invoice._get_html_link()))
other_invoice.message_post(body=escape(_("Created from attachment in %s")) % invoice._get_html_link())
return True
+3
View File
@@ -3,6 +3,7 @@
from collections import defaultdict
from datetime import datetime, timedelta
from markupsafe import Markup
from psycopg2 import IntegrityError
from psycopg2.errorcodes import UNIQUE_VIOLATION
@@ -238,6 +239,8 @@ class DiscussController(http.Controller):
thread = channel_member_sudo.channel_id
else:
thread = request.env[thread_model].browse(int(thread_id)).exists()
if 'body' in post_data:
post_data['body'] = Markup(post_data['body']) # contains HTML such as @mentions
message_data = thread.message_post(**{key: value for key, value in post_data.items() if key in self._get_allowed_message_post_params()}).message_format()[0]
if 'temporary_id' in request.context:
message_data['temporary_id'] = request.context['temporary_id']
+9 -8
View File
@@ -5,6 +5,7 @@ import base64
import logging
from collections import defaultdict
from hashlib import sha512
from markupsafe import Markup
from secrets import choice
from markupsafe import Markup
@@ -315,7 +316,7 @@ class Channel(models.Model):
# channel_info is called before actually unpinning the channel
channel_info['is_pinned'] = False
self.env['bus.bus']._sendone(partner, 'mail.channel/leave', channel_info)
notification = _('<div class="o_mail_notification">left the channel</div>')
notification = Markup('<div class="o_mail_notification">%s</div>') % _('left the channel')
# post 'channel left' message as root since the partner just unsubscribed from the channel
self.sudo().message_post(body=notification, subtype_xmlid="mail.mt_comment", author_id=partner.id)
self.env['bus.bus']._sendone(self, 'mail.record/insert', {
@@ -387,15 +388,13 @@ class Channel(models.Model):
if post_joined_message:
# notify existing members with a new message in the channel
if member.partner_id == self.env.user.partner_id:
notification = _('<div class="o_mail_notification">joined the channel</div>')
notification = Markup('<div class="o_mail_notification">%s</div>') % _('joined the channel')
else:
notification = _(
'<div class="o_mail_notification">invited %s to the channel</div>',
member.partner_id._get_html_link(),
)
notification = (Markup('<div class="o_mail_notification">%s</div>') % _("invited %s to the channel")) % member.partner_id._get_html_link()
member.channel_id.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment")
for member in new_members.filtered(lambda member: member.guest_id):
member.channel_id.message_post(body=_('<div class="o_mail_notification">joined the channel</div>'), message_type="notification", subtype_xmlid="mail.mt_comment")
member.channel_id.message_post(body=Markup('<div class="o_mail_notification">%s</div>') % _('joined the channel'),
message_type="notification", subtype_xmlid="mail.mt_comment")
guest = member.guest_id
if guest:
notifications.append((guest, 'mail.channel/joined', {
@@ -1135,7 +1134,9 @@ class Channel(models.Model):
new_channel = self.create(vals)
group = self.env['res.groups'].search([('id', '=', group_id)]) if group_id else None
new_channel.group_public_id = group.id if group else None
notification = _('<div class="o_mail_notification">created <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>', new_channel.id, new_channel.name)
notification = (Markup('<div class="o_mail_notification">%s</div>') % _("created %(link)s")) % {
'link': Markup('<a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a>') % (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)
+5 -5
View File
@@ -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)
+2 -1
View File
@@ -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='<p>Log on P1</p>',
body=Markup('<p>Log on P1</p>'),
subtype_id=self.env.ref('mail.mt_comment').id
)
self.assertEqual(p1.activity_ids, p1_act1)
+13 -13
View File
@@ -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! 👍<br/>To access special commands, <b>start your sentence with</b> <span class=\"o_odoobot_command\">/</span>. Try getting help.")
return Markup(_("Great! 👍<br/>To access special commands, <b>start your sentence with</b> <span class=\"o_odoobot_command\">/</span>. 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!<br/>Ping someone with @username to grab their attention. <b>Try to ping me using</b> <span class=\"o_odoobot_command\">@OdooBot</span> in a sentence.")
return Markup(_("Wow you are a natural!<br/>Ping someone with @username to grab their attention. <b>Try to ping me using</b> <span class=\"o_odoobot_command\">@OdooBot</span> 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! 🎉 <br/>Now, try <b>sending an attachment</b>, like a picture of your cute dog...")
return Markup(_("Yep, I am here! 🎉 <br/>Now, try <b>sending an attachment</b>, 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 😊 <br/>Congratulations, you finished this tour. You can now <b>close this chat window</b>. Enjoy discovering Odoo.")
return Markup(_("I am a simple bot, but if that's a dog, he is the cutest 😊 <br/>Congratulations, you finished this tour. You can now <b>close this chat window</b>. 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 "
"<a href=\"https://www.odoo.com/documentation\" target=\"_blank\">our documentation</a> or "
"<a href=\"https://www.odoo.com/slides\" target=\"_blank\">our videos</a>.")
"<a href=\"https://www.odoo.com/slides\" target=\"_blank\">our videos</a>."))
else:
# repeat question
if odoobot_state == 'onboarding_emoji':
self.env.user.odoobot_failed = True
return _("Not exactly. To continue the tour, send an emoji: <b>type</b> <span class=\"o_odoobot_command\">:)</span> and press enter.")
return Markup(_("Not exactly. To continue the tour, send an emoji: <b>type</b> <span class=\"o_odoobot_command\">:)</span> and press enter."))
elif odoobot_state == 'onboarding_attachement':
self.env.user.odoobot_failed = True
return _("To <b>send an attachment</b>, click on the <i class=\"fa fa-paperclip\" aria-hidden=\"true\"></i> icon and select a file.")
return Markup(_("To <b>send an attachment</b>, click on the <i class=\"fa fa-paperclip\" aria-hidden=\"true\"></i> 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 <span class=\"o_odoobot_command\">/</span> and wait for the propositions. Select <span class=\"o_odoobot_command\">help</span> and press enter")
return Markup(_("Not sure what you are doing. Please, type <span class=\"o_odoobot_command\">/</span> and wait for the propositions. Select <span class=\"o_odoobot_command\">help</span> 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, <b>ping him</b>. Write <span class=\"o_odoobot_command\">@OdooBot</span> and select me.")
return Markup(_("Sorry, I am not listening. To get someone's attention, <b>ping him</b>. Write <span class=\"o_odoobot_command\">@OdooBot</span> and select me."))
return random.choice([
_("I'm not smart enough to answer your question.<br/>To follow my guide, ask: <span class=\"o_odoobot_command\">start the tour</span>."),
Markup(_("I'm not smart enough to answer your question.<br/>To follow my guide, ask: <span class=\"o_odoobot_command\">start the tour</span>.")),
_("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...<br/>I can show you features if you write: <span class=\"o_odoobot_command\">start the tour</span>.")
Markup(_("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language...<br/>I can show you features if you write: <span class=\"o_odoobot_command\">start the tour</span>."))
])
return False
+7 -1
View File
@@ -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,<br/>Odoo's chat helps employees collaborate efficiently. I'm here to help you discover its features.<br/><b>Try to send me an emoji</b> <span class=\"o_odoobot_command\">:)</span>")
message = Markup("%s<br/>%s<br/><b>%s</b> <span class=\"o_odoobot_command\">:)</span>") % (
_("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
+2 -4
View File
@@ -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(
+2 -2
View File
@@ -780,8 +780,8 @@ class MassMailing(models.Model):
])
opt_out_records.write({'opt_out': value})
message = _('The recipient <strong>unsubscribed from %s</strong> mailing list(s)') \
if value else _('The recipient <strong>subscribed to %s</strong> mailing list(s)')
message = Markup(_('The recipient <strong>unsubscribed from %s</strong> mailing list(s)')) \
if value else Markup(_('The recipient <strong>subscribed to %s</strong> 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)
@@ -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:<br>%s',
mail_sudo.email_to,
mail_sudo.failure_reason)
_('Test mailing could not be sent to %s:', mail_sudo.email_to) +
(Markup("<br/>") + 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='<ul>%s</ul>' % ''.join(
['<li>%s</li>' % notification_message for notification_message in notification_messages]
self.mass_mailing_id._message_log(body=Markup('<ul>%s</ul>') % ''.join(
[Markup('<li>%s</li>') % notification_message for notification_message in notification_messages]
))
return True
@@ -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='<ul>%s</ul>' % ''.join(
['<li>%s</li>' % notification_message for notification_message in notification_messages]
self.mailing_id._message_log(body=Markup('<ul>%s</ul>') % ''.join(
[Markup('<li>%s</li>') % notification_message for notification_message in notification_messages]
))
return True
+6 -7
View File
@@ -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',
+3 -2
View File
@@ -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 += "<br />" + _("Error: %s", self.state_message)
message += Markup("<br/>") + _("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 += "<br />" + _("Reason: %s", self.state_message)
message += Markup("<br/>") + _("Reason: %s", self.state_message)
return message
def _get_last(self):
+4 -4
View File
@@ -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,
+3 -2
View File
@@ -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 ''}<br/>"
if notes:
message += notes.replace('\n', '<br/>')
message += notes.replace('\n', Markup('<br/>'))
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='<br/>\n'.join(message_content))
self.message_post(body=Markup('<br/>\n').join(message_content))
def get_onboarding_data(self):
return {
+3 -2
View File
@@ -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
+6 -9
View File
@@ -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.
+7 -6
View File
@@ -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 = "<b>" + _("The ordered quantity has been updated.") + "</b><ul>"
msg = Markup("<b>%s</b><ul>") % _("The ordered quantity has been updated.")
for line in order_lines:
msg += "<li> %s: <br/>" % line.product_id.display_name
msg += Markup("<li> %s: <br/>") % line.product_id.display_name
msg += _(
"Ordered Quantity: %(old_qty)s -> %(new_qty)s",
old_qty=line.product_uom_qty,
new_qty=values["product_uom_qty"]
) + "<br/>"
) + Markup("<br/>")
if line.product_id.type in ('consu', 'product'):
msg += _("Delivered Quantity: %s", line.qty_delivered) + "<br/>"
msg += _("Invoiced Quantity: %s", line.qty_invoiced) + "<br/>"
msg += "</ul>"
msg += _("Delivered Quantity: %s", line.qty_delivered) + Markup("<br/>")
msg += _("Invoiced Quantity: %s", line.qty_invoiced) + Markup("<br/>")
msg += Markup("</ul>")
order.message_post(body=msg)
def _check_line_unlink(self):
@@ -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 api, Command, fields, models, _
from odoo.tools import format_amount
@@ -67,7 +68,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 and not has_task:
msg_body = _("Task Created (%s): %s", line.product_id.name, line.task_id._get_html_link())
msg_body = escape(_("Task Created (%s): %s")) % (line.product_id.name, line.task_id._get_html_link())
line.order_id.message_post(body=msg_body)
return lines
@@ -179,7 +180,10 @@ 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._get_html_link(), self.product_id.name)
task_msg = escape(_("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
+2 -1
View File
@@ -4,6 +4,7 @@
from collections import defaultdict
from datetime import timedelta
from markupsafe import escape
from operator import itemgetter
from odoo import _, api, Command, fields, models
@@ -740,7 +741,7 @@ Please change the quantity done or the rounding precision of your unit of measur
if not documents or not doc_orig:
return
msg = _("The deadline has been automatically updated due to a delay on %s.", doc_orig[0]._get_html_link())
msg = escape(_("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:
+2 -1
View File
@@ -6,6 +6,7 @@ import time
from ast import literal_eval
from datetime import date, timedelta
from collections import defaultdict
from markupsafe import escape
from odoo import SUPERUSER_ID, _, api, fields, models
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
@@ -1247,7 +1248,7 @@ class Picking(models.Model):
'backorder_id': picking.id
})
picking.message_post(
body=_('The backorder %s has been created.', backorder_picking._get_html_link())
body=escape(_('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})
+10 -9
View File
@@ -1,6 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from markupsafe import Markup
import json
from odoo import _, api, fields, models
@@ -189,13 +190,13 @@ class StockPicking(models.Model):
for p in previous_pickings - without_tracking:
p.carrier_tracking_ref += "," + res['tracking_number']
order_currency = self.sale_id.currency_id or self.company_id.currency_id
msg = _(
"Shipment sent to carrier %(carrier_name)s for shipping with tracking number %(ref)s<br/>Cost: %(price).2f %(currency)s",
carrier_name=self.carrier_id.name,
ref=self.carrier_tracking_ref,
price=self.carrier_price,
currency=order_currency.name
)
msg = _("Shipment sent to carrier %(carrier_name)s for shipping with tracking number %(ref)s",
carrier_name=self.carrier_id.name,
ref=self.carrier_tracking_ref) + \
Markup("<br/>") + \
_("Cost: %(price).2f %(currency)s",
price=self.carrier_price,
currency=order_currency.name)
self.message_post(body=msg)
self._add_delivery_cost_to_so()
@@ -234,9 +235,9 @@ class StockPicking(models.Model):
except ValueError:
carrier_trackers = self.carrier_tracking_url
else:
msg = "Tracking links for shipment: <br/>"
msg = _("Tracking links for shipment:") + Markup("<br/>")
for tracker in carrier_trackers:
msg += '<a href=' + tracker[1] + '>' + tracker[0] + '</a><br/>'
msg += Markup('<a href="%s">%s</a><br/>') % (tracker[1], tracker[0])
self.message_post(body=msg)
return self.env["ir.actions.actions"]._for_xml_id("delivery.act_delivery_trackers_url")
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import escape
from odoo import _, api, Command, fields, models
from odoo.osv import expression
from odoo.exceptions import ValidationError
@@ -245,7 +247,7 @@ class StockPicking(models.Model):
pickings = self.filtered(lambda p: p.user_id.id != user_id)
pickings.write({'user_id': user_id})
for pick in pickings:
log_message = _('Assigned to %s Responsible', (pick.batch_id._get_html_link()))
log_message = escape(_('Assigned to %s Responsible')) % pick.batch_id._get_html_link()
pick.message_post(body=log_message)
def action_view_batch(self):
@@ -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 UserError
from odoo.osv.expression import AND
@@ -239,7 +241,7 @@ class StockPickingBatch(models.Model):
if has_no_qty_done(picking):
empty_pickings.add(picking.id)
picking.message_post(
body="<b>%s:</b> %s <a href=#id=%s&view_type=form&model=stock.picking.batch>%s</a>" % (
body=Markup("<b>%s:</b> %s <a href=#id=%s&view_type=form&model=stock.picking.batch>%s</a>") % (
_("Transferred by"),
_("Batch Transfer"),
picking.batch_id.id,
@@ -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.addons.mail.tests.common import MailCommon
from odoo.exceptions import AccessError
from odoo.tests import tagged, users
@@ -660,7 +662,7 @@ class RecipientsNotificationTest(MailCommon):
'status': 'sent', 'type': 'inbox'}],
message_info={'content': 'User Choice Notification'}):
test.message_post(
body='<p>User Choice Notification</p>',
body=Markup('<p>User Choice Notification</p>'),
message_type='comment',
partner_ids=shared_partner.ids,
subtype_xmlid='mail.mt_comment',
+3 -2
View File
@@ -7,6 +7,7 @@ import smtplib
from datetime import datetime, timedelta
from freezegun import freeze_time
from markupsafe import Markup
from OpenSSL.SSL import Error as SSLError
from socket import gaierror, timeout
from unittest.mock import call, patch
@@ -38,9 +39,9 @@ class TestMailMail(MailCommon):
'email_from': 'ignasse@example.com',
}).with_context({})
cls.test_message = cls.test_record.message_post(body='<p>Message</p>', subject='Subject')
cls.test_message = cls.test_record.message_post(body=Markup('<p>Message</p>'), subject='Subject')
cls.test_mail = cls.env['mail.mail'].create([{
'body': '<p>Body</p>',
'body': Markup('<p>Body</p>'),
'email_from': False,
'email_to': 'test@example.com',
'is_notification': True,
+2 -1
View File
@@ -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='<p>This is First Message</p>', subject='Subject',
body=Markup('<p>This is First Message</p>'), subject='Subject',
message_type='comment', subtype_xmlid='mail.mt_note')
# portal user have no rights to read the message
with self.assertRaises(AccessError):
@@ -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="<p>Initial Body</p>",
body=Markup("<p>Initial Body</p>"),
message_type="comment",
partner_ids=self.partner_1.ids,
)
@@ -543,7 +544,7 @@ class TestNoThread(MailCommon, TestRecipients):
'subtype': 'mail.mt_note',
}]):
_message = self.env['mail.thread'].message_notify(
body='<p>Hello Paulo</p>',
body=Markup('<p>Hello Paulo</p>'),
partner_ids=self.partner_2.ids,
subject='Test Notify',
)
+21 -21
View File
@@ -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='<p>You have received a notification</p>',
body=Markup('<p>You have received a notification</p>'),
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='<p>You have received a notification</p>',
body=Markup('<p>You have received a notification</p>'),
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='<p>You have received a notification</p>',
body=Markup('<p>You have received a notification</p>'),
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='<p>You have received a notification</p>',
body=Markup('<p>You have received a notification</p>'),
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='<p>Nice notification content</p>',
body=Markup('<p>Nice notification content</p>'),
partner_ids=self.partner_employee_2.ids,
subject='Notify Subject',
)
@@ -473,7 +473,7 @@ class TestMessageNotify(TestMessagePostCommon):
self.mock_mail_gateway(), \
self.assertRaises(ValueError):
_new_message = test_record.message_notify(
body='<p>You will not receive a notification</p>',
body=Markup('<p>You will not receive a notification</p>'),
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='<p>You will not receive a notification</p>',
body=Markup('<p>You will not receive a notification</p>'),
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='<p>You have received a notification</p>',
body=Markup('<p>You have received a notification</p>'),
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='<p>Labrador</p>',
body=Markup('<p>Labrador</p>'),
)
self.assertMessageFields(
new_note,
@@ -559,7 +559,7 @@ class TestMessageLog(TestMessagePostCommon):
new_notes = test_records._message_log_batch(
bodies=dict(
(test_record.id, '<p>Test _message_log_batch</p>')
(test_record.id, Markup('<p>Test _message_log_batch</p>'))
for test_record in test_records
),
)
@@ -833,7 +833,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
self.assertMsgWithoutNotifications(), \
self.capture_triggers(cron_id) as capt:
msg = test_record.message_post(
body='<p>Test</p>',
body=Markup('<p>Test</p>'),
message_type='comment',
subject='Subject',
subtype_xmlid='mail.mt_comment',
@@ -880,7 +880,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
self.mock_mail_gateway(mail_unlink_sent=False), \
self.capture_triggers(cron_id) as capt:
msg = test_record.message_post(
body='<p>Test</p>',
body=Markup('<p>Test</p>'),
message_type='comment',
subject='Subject',
subtype_xmlid='mail.mt_comment',
@@ -908,7 +908,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
with freeze_time(now), \
self.assertMsgWithoutNotifications():
msg = test_record.message_post(
body='<p>Test</p>',
body=Markup('<p>Test</p>'),
message_type='comment',
subject='Subject',
subtype_xmlid='mail.mt_comment',
@@ -1086,7 +1086,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
]
), patch.object(MailTestSimple, 'check_access_rights', return_value=True):
new_msg = self.test_record.with_user(self.user_portal).message_post(
body='<p>Test</p>',
body=Markup('<p>Test</p>'),
message_type='comment',
subject='Subject',
subtype_xmlid='mail.mt_comment',
@@ -1095,7 +1095,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
with self.assertRaises(AccessError):
self.test_record.with_user(self.user_portal).message_post(
body='<p>Test</p>',
body=Markup('<p>Test</p>'),
message_type='comment',
subject='Subject',
subtype_xmlid='mail.mt_comment',
@@ -1108,7 +1108,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
with self.mock_mail_gateway():
parent_msg = test_record.message_post(
body='<p>Test</p>',
body=Markup('<p>Test</p>'),
message_type='comment',
subject='Test Subject',
subtype_xmlid='mail.mt_comment',
@@ -1121,7 +1121,7 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
[{'content': '<p>Test Answer</p>', 'notif': [{'partner': self.partner_1, 'type': 'email'}]}]
):
msg = test_record.message_post(
body='<p>Test Answer</p>',
body=Markup('<p>Test Answer</p>'),
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='<p>Test Answer Bis</p>',
body=Markup('<p>Test Answer Bis</p>'),
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='<p>Hello</p>',
body=Markup('<p>Hello</p>'),
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='<p>Hi there</p>',
body=Markup('<p>Hi there</p>'),
email_layout_xmlid=email_layout_xmlid,
message_type='comment',
subject='TeDeum',
+9 -9
View File
@@ -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='<p>Test _message_log</p>',
body=Markup('<p>Test _message_log</p>'),
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, '<p>Test _message_log</p>')
(record.id, Markup('<p>Test _message_log</p>'))
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='<p>Test message_post as log</p>',
body=Markup('<p>Test message_post as log</p>'),
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='<p>Test Post Performances basic</p>',
body=Markup('<p>Test Post Performances basic</p>'),
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='<p>Test Post Performances with an email ping</p>',
body=Markup('<p>Test Post Performances with an email ping</p>'),
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='<p>Test Post Performances with an inbox ping</p>',
body=Markup('<p>Test Post Performances with an inbox ping</p>'),
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='<p>Test Post Performances</p>',
body=Markup('<p>Test Post Performances</p>'),
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='<p>Test body <img src="cid:cid1"> <img src="cid:cid2"></p>',
body=Markup('<p>Test body <img src="cid:cid1"> <img src="cid:cid2"></p>'),
subject='Test Subject',
message_type='notification',
subtype_xmlid=None,
@@ -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='<p>Test Content</p>',
body=Markup('<p>Test Content</p>'),
message_type='comment',
subject='Test Subject',
subtype_xmlid='mail.mt_comment',
@@ -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 = _('<p>I am interested by this lead.</p>')
message = Markup('<p>%s</p>') % _('I am interested by this lead.')
if comment:
message += '<p>%s</p>' % html_escape(comment)
message += Markup('<p>%s</p>') % 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 = '<p>%s</p>' % _('I am not interested by this lead. I contacted the lead.')
message = Markup('<p>%s</p>') % _('I am not interested by this lead. I contacted the lead.')
else:
message = '<p>%s</p>' % _('I am not interested by this lead. I have not contacted the lead.')
message = Markup('<p>%s</p>') % _('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 += '<p>%s</p>' % html_escape(comment)
message += Markup('<p>%s</p>') % comment
self.message_post(body=message)
values = {
'partner_assigned_id': False,
@@ -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('<br/>- %s: %s' % (field_name, value))
tx.payment_id._message_log(body=''.join(msg))
msg.append(Markup('<br/>- %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()