[REF] mail: cleanup post with {view, template} wrappers

RATIONALE

Purpose of this commit is to cleanup main post helpers and have a more easy
and understandable way of calling them.

SUMMARY

We now have two main API methods, based on business flow: either posting
on documents, either sending a mass mailing. Indeed those two flows are
different

  * post: create message, then launch notification process by taking into
    account subtype, followers, ...
  * mail: create mails in batch with recipients being based on template or
    given partners. No notifications is involved, only maybe traces if a
    mass mailing is linked

Delegate QWeb rendering to the render mixin (i.e. _render_template_qweb_view)
in order to have a single point to forge evaluation context and re-use
existing rendering code.

SPECIFICATIONS

Main API helpers are now

  * ``message_post_with_source``: (batch) post on records, using an ir.ui.view
    (given a record or its xml id) or a mail.template record (given a record or
    its xml id). When using a template, a composer is called to post on each
    record (as batch post is not yet supported). When using a view, a direct
    call to message_post using the rendered bodies is done, one record at a
    time.
  * ``message_mail_with_source``: send a mass mailing on records, acting like
    invoking the mail composer in mass mode. Same arguments are valid, either
    a reference to a view, either a reference to a mail template.

Other helpers are

  * ``_message_log_with_view``: (batch) log on records, using an ir.ui.view
    to render the body using QWeb (no notification process);
  * ``_message_log(_batch)``: (batch) log on records (no notification process);
  * ``message_notify``: notify partners on records (creating notifications
    specifically for some people while message itself is not displayed in
    chatter);

Code migration

  * ``message_post_with_template`` in "mass mode": use ``message_mail_with_source``
    and set the template record as source;
  * ``message_post_with_template`` in "comment" mode: use ``message_post_with_source``
    and set the template record as source;
  * ``message_post_with_view``: its main usage was to post on a document, in which
    case it generally can be replaced by ``message_mail_with_source`` using
    the view reference as source;

Task-2710804 (Mail: Clean MailThread Posting API)

Part-of: odoo/odoo#99482
This commit is contained in:
Thibault Delavallée
2023-01-17 20:58:34 +01:00
parent 3a9a791be8
commit 4775bd93a2
45 changed files with 509 additions and 287 deletions
+2 -2
View File
@@ -750,9 +750,9 @@ class Meeting(models.Model):
raise UserError(_("There are no attendees on these events"))
template_id = self.env['ir.model.data']._xmlid_to_res_id('calendar.calendar_template_meeting_update', raise_if_not_found=False)
# The mail is sent with datetime corresponding to the sending user TZ
composition_mode = self.env.context.get('composition_mode', 'comment')
default_composition_mode = self.env.context.get('default_composition_mode', self.env.context.get('composition_mode', 'comment'))
compose_ctx = dict(
default_composition_mode=composition_mode,
default_composition_mode=default_composition_mode,
default_model='calendar.event',
default_res_ids=self.ids,
default_use_template=bool(template_id),
+1 -1
View File
@@ -73,7 +73,7 @@
<field name="arch" type="xml">
<tree string="Meetings" sample="1" multi_edit="1">
<header>
<button name="action_open_composer" type="object" context="{'composition_mode':'mass_mail'}"
<button name="action_open_composer" type="object" context="{'default_composition_mode':'mass_mail'}"
string="Send Mail"/>
</header>
<field name="name" string="Subject" decoration-bf="1" attrs="{'readonly':[('recurrency','=',True)]}"/>
+2 -2
View File
@@ -1561,9 +1561,9 @@ class Lead(models.Model):
def _merge_log_summary(self, merged_followers, opportunities_tail):
"""Log the merge message on the lead."""
self.ensure_one()
self.message_post_with_view(
self.message_post_with_source(
"crm.crm_lead_merge_summary",
values={
render_values={
"merged_followers": merged_followers,
"opportunities": opportunities_tail,
"is_html_empty": is_html_empty,
+5 -5
View File
@@ -68,7 +68,7 @@ class Lead(models.Model):
normalized_email = tools.email_normalize(lead.email_from)
if not normalized_email:
lead.message_post_with_view(
lead.message_post_with_source(
'crm_iap_enrich.mail_message_lead_enrich_no_email',
subtype_xmlid='mail.mt_note',
)
@@ -78,7 +78,7 @@ class Lead(models.Model):
# Discard domains of generic email providers as it won't return relevant information
if email_domain in iap_tools._MAIL_DOMAIN_BLACKLIST:
lead.write({'iap_enrich_done': True})
lead.message_post_with_view(
lead.message_post_with_source(
'crm_iap_enrich.mail_message_lead_enrich_notfound',
subtype_xmlid='mail.mt_note',
)
@@ -128,7 +128,7 @@ class Lead(models.Model):
iap_data = iap_response.get(str(lead.id))
if not iap_data:
lead.write({'iap_enrich_done': True})
lead.message_post_with_view(
lead.message_post_with_source(
'crm_iap_enrich.mail_message_lead_enrich_notfound',
subtype_xmlid='mail.mt_note',
)
@@ -161,9 +161,9 @@ class Lead(models.Model):
template_values = iap_data
template_values['flavor_text'] = _("Lead enriched based on email address")
lead.message_post_with_view(
lead.message_post_with_source(
'iap_mail.enrich_company',
values=template_values,
render_values=template_values,
subtype_xmlid='mail.mt_note',
)
@@ -278,9 +278,9 @@ class CRMLeadMiningRequest(models.Model):
leads = self.env['crm.lead'].create(lead_vals_list)
for lead in leads:
if messages_to_post.get(lead.reveal_id):
lead.message_post_with_view(
lead.message_post_with_source(
'iap_mail.enrich_company',
values=messages_to_post[lead.reveal_id],
render_values=messages_to_post[lead.reveal_id],
subtype_xmlid='mail.mt_note',
)
+2 -2
View File
@@ -100,9 +100,9 @@ class EventBooth(models.Model):
def _post_confirmation_message(self):
for booth in self:
booth.event_id.message_post_with_view(
booth.event_id.message_post_with_source(
'event_booth.event_booth_booked_template',
values={
render_values={
'booth': booth,
},
subtype_xmlid='event_booth.mt_event_booth_booked',
@@ -77,9 +77,9 @@ class EventRegistration(models.Model):
registrations = super(EventRegistration, self).create(vals_list)
for registration in registrations:
if registration.sale_order_id:
registration.message_post_with_view(
registration.message_post_with_source(
'mail.message_origin_link',
values={'self': registration, 'origin': registration.sale_order_id},
render_values={'self': registration, 'origin': registration.sale_order_id},
subtype_xmlid='mail.mt_note',
)
return registrations
+8 -7
View File
@@ -636,11 +636,12 @@ class HrExpense(models.Model):
def refuse_expense(self, reason):
self.write({'is_refused': True})
self.sheet_id.write({'state': 'cancel'})
self.sheet_id.message_post_with_view(
'hr_expense.hr_expense_template_refuse_reason',
values={'reason': reason, 'is_sheet': False, 'name': self.name},
subtype_xmlid='mail.mt_comment',
)
for expense in self:
expense.sheet_id.message_post_with_source(
'hr_expense.hr_expense_template_refuse_reason',
render_values={'reason': reason, 'is_sheet': False, 'name': expense.name},
subtype_xmlid='mail.mt_comment',
)
@api.model
def get_expense_dashboard(self):
@@ -1218,10 +1219,10 @@ class HrExpenseSheet(models.Model):
self.write({'state': 'cancel'})
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
for sheet in self:
sheet.message_post_with_view(
sheet.message_post_with_source(
'hr_expense.hr_expense_template_refuse_reason',
subtype_id=subtype_id,
values={'reason': reason, 'is_sheet': True, 'name': sheet.name},
render_values={'reason': reason, 'is_sheet': True, 'name': sheet.name},
)
self.activity_update()
+2 -2
View File
@@ -27,9 +27,9 @@ class HrEmployee(models.Model):
employees = super().create(vals_list)
for employee in employees:
if employee.applicant_id:
employee.applicant_id.message_post_with_view(
employee.applicant_id.message_post_with_source(
'hr_recruitment.applicant_hired_template',
values={'applicant': employee.applicant_id},
render_values={'applicant': employee.applicant_id},
subtype_xmlid='hr_recruitment.mt_applicant_hired',
)
return employees
@@ -45,9 +45,16 @@ class ApplicantGetRefuseReason(models.TransientModel):
self.applicant_ids.write({'refuse_reason_id': self.refuse_reason_id.id, 'active': False})
if self.send_mail:
applicants = self.applicant_ids.filtered(lambda x: x.email_from or x.partner_id.email)
applicants.with_context(active_test=True).message_post_with_template(
self.template_id.id,
auto_delete_message=True,
email_layout_xmlid='mail.mail_notification_light',
subtype_xmlid='mail.mt_note',
)
# TDE note: keeping 16.0 behavior, clean me please
message_values = {
'auto_delete_message' : True,
'email_layout_xmlid' : 'mail.mail_notification_light',
}
if len(applicants) > 1:
applicants.with_context(active_test=True).message_mail_with_source(self.template_id, **message_values)
else:
applicants.with_context(active_test=True).message_post_with_source(
self.template_id,
subtype_xmlid='mail.mt_note',
**message_values
)
@@ -134,9 +134,9 @@ class HrContract(models.Model):
).create(all_new_leave_vals)
new_leaves.filtered(lambda l: l.state in 'validate')._validate_leave_request()
for index, new_leave in enumerate(new_leaves):
new_leave.message_post_with_view(
new_leave.message_post_with_source(
'mail.message_origin_link',
values={'self': new_leave, 'origin': all_new_leave_origin[index]},
render_values={'self': new_leave, 'origin': all_new_leave_origin[index]},
subtype_xmlid='mail.mt_note',
)
return super(HrContract, self - specific_contracts).write(vals)
+4 -6
View File
@@ -207,12 +207,10 @@ class ServerActions(models.Model):
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
else:
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note')
for record in records:
record.message_post_with_template(
self.template_id.id,
composition_mode='comment',
subtype_id=subtype_id,
)
records.message_post_with_source(
self.template_id,
subtype_id=subtype_id,
)
else:
template = self.template_id.with_context(cleaned_ctx)
for res_id in res_ids:
+4 -4
View File
@@ -574,16 +574,16 @@ class MailActivity(models.Model):
next_activities_values.append(vals)
# post message on activity, before deleting it
activity_message = record.message_post_with_view(
activity_message = record.message_post_with_source(
'mail.message_activity_done',
values={
attachment_ids=attachment_ids,
render_values={
'activity': activity,
'feedback': feedback,
'display_assignee': activity.user_id != self.env.user
},
subtype_xmlid='mail.mt_activities',
mail_activity_type_id=activity.activity_type_id.id,
attachment_ids=[Command.link(attachment_id) for attachment_id in attachment_ids] if attachment_ids else [],
subtype_xmlid='mail.mt_activities',
)
# Moving the attachments in the message
+4 -6
View File
@@ -342,12 +342,10 @@ class MailActivityMixin(models.AbstractModel):
template = self.env['mail.template'].browse(template_id).exists()
if not template:
return False
for record in self:
record.message_post_with_template(
template_id,
composition_mode='comment',
subtype_xmlid='mail.mt_comment',
)
self.message_post_with_source(
template,
subtype_xmlid='mail.mt_comment',
)
return True
def activity_search(self, act_type_xmlids='', user_id=None, additional_domain=None):
+287 -112
View File
@@ -635,7 +635,7 @@ class MailThread(models.AbstractModel):
:param dict changes: mapping {record_id: (changed_field_names, tracking_value_ids)}
containing existing records only
"""
if not changes:
if not self or not changes:
return True
# Clean the context to get rid of residual default_* keys
# that could cause issues afterward during the mail.message
@@ -644,15 +644,18 @@ class MailThread(models.AbstractModel):
# its creation, but could refer to wrong parent message id,
# leading to a traceback in case the related message_id
# doesn't exist
self = self.with_context(clean_context(self._context))
cleaned_self = self.with_context(clean_context(self._context))._fallback_lang()
templates = self._track_template(changes)
default_composition_mode = 'mass_mail' if len(self) != 1 else 'comment'
for _field_name, (template, post_kwargs) in templates.items():
if not template:
continue
if isinstance(template, str):
self._fallback_lang().message_post_with_view(template, **post_kwargs)
composition_mode = post_kwargs.pop('composition_mode', default_composition_mode)
if composition_mode == 'mass_mail':
cleaned_self.message_mail_with_source(template, **post_kwargs)
else:
self._fallback_lang().message_post_with_template(template.id, **post_kwargs)
cleaned_self.message_post_with_source(template, **post_kwargs)
return True
def _track_template(self, changes):
@@ -1813,6 +1816,8 @@ class MailThread(models.AbstractModel):
:param int author_id: optional ID of partner record being the author. See
``_message_compute_author`` that uses it to make email_from / author_id coherent;
:param int parent_id: handle thread formation
:param str subtype_xmlid: optional xml id of a mail.message.subtype to
fetch, will force value of subtype_id;
:param int subtype_id: subtype_id of the message, used mainly for followers
notification mechanism;
:param list(int) partner_ids: partner_ids to notify in addition to partners
@@ -1909,9 +1914,9 @@ class MailThread(models.AbstractModel):
'res_id': self.id,
# content
'body': body,
'subject': subject or False,
'message_type': message_type,
'parent_id': self._message_compute_parent_id(parent_id),
'subject': subject or False,
'subtype_id': subtype_id,
# recipients
'partner_ids': partner_ids,
@@ -2094,95 +2099,203 @@ class MailThread(models.AbstractModel):
# MESSAGE POST API / WRAPPERS
# ------------------------------------------------------------
def _message_compose_with_view(self, views_or_xmlid, message_log=False, **kwargs):
""" Helper method to send a mail / post a message / log a note using
a view_id to render using the ir.qweb engine. This method is stand
alone, because there is nothing in template and composer that allows
to handle views in batch. This method should probably disappear when
templates handle ir ui views. """
values = kwargs.pop('values', None) or dict()
try:
from odoo.addons.http_routing.models.ir_http import slug
values['slug'] = slug
except ImportError:
values['slug'] = lambda self: self.id
view_ref = views_or_xmlid.id if isinstance(views_or_xmlid, models.BaseModel) else views_or_xmlid
def message_mail_with_source(self, source_ref, render_values=None,
message_type='notification',
auto_commit=False,
**kwargs):
""" Send a mass mail on self, using an external source to render part
of the content. It can be either a 'mail.template', either a view used
to render the body using QWeb.
messages_as_sudo = self.env['mail.message']
for record in self:
values['object'] = record
rendered_template = self.env['ir.qweb']._render(view_ref, values, minimal_qcontext=True, raise_if_not_found=False)
if not rendered_template:
continue
if message_log:
messages_as_sudo += record._message_log(body=rendered_template, **kwargs)
else:
kwargs['body'] = rendered_template
# a bit complicated to handle, to be improved soon in master
_mails_as_sudo, _messages_as_sudo = record.message_post_with_template(False, **kwargs)
if _messages_as_sudo:
messages_as_sudo += _messages_as_sudo
SPOILER: this method currently calls a composer in a loop when using
a view even if it is suboptimal. This is due to current composer
implementation.. This will be cleaned soon to optimize mass mailing
through mail.thread and lessen usage of composer itself.
return messages_as_sudo
Default values
* subtype_id: will be False, forced by composer in mass mode;
def message_post_with_view(self, views_or_xmlid, **kwargs):
""" Helper method to send a mail / post a message using a view_id """
return self._message_compose_with_view(views_or_xmlid, **kwargs)
:param record/str source_ref: reference to a source for rendering.
It can be one of
* a MailTemplate record. It will be used to render the various
message values (body, subject, recipients, ...). It should behave
like using the mail composer with a template;
* an IrUIView record. It will be used to render the content
(body). Other fields are left to the caller and/or default values
computation;
* an XmlID of a MailTemplate or of an IrUiView: see above;
:param dict render_values: additional rendering values for qweb context;
def message_post_with_template(self, template_id, email_layout_xmlid=None, auto_commit=False, **kwargs):
""" Helper method to send a mail with a template
:param template_id : the id of the template to render to create the body of the message
:param **kwargs : parameter to create a mail.compose.message woaerd (which inherit from mail.message)
:param str message_type: one of 'notification' or 'comment';
:param bool auto_commit: auto commit after each batch of emails sent
(see ``MailComposer._action_send_mail()``);
:param dict kwargs: additional values given to the 'mail.compose.message'
creation;
:return: created mail.mail records, as sudo
"""
template, view = self._get_source_from_ref(source_ref)
# preliminary value safety check
self._raise_for_invalid_parameters(
set(kwargs.keys()),
forbidden_names={'model', 'res_id'}
forbidden_names={'body', 'composition_mode', 'model', 'res_id', 'values'}
)
# Get composition mode, or force it according to the number of record in self
if not kwargs.get('composition_mode'):
kwargs['composition_mode'] = 'comment' if len(self.ids) == 1 else 'mass_mail'
if not kwargs.get('message_type'):
kwargs['message_type'] = 'notification'
res_id = self.ids[0] if self.ids else 0
res_ids = self.ids
# with a view, render bodies in batch (template is managed by composer)
bodies = self.env['mail.render.mixin']._render_template_qweb_view(
view,
self._name,
self.ids,
add_context=render_values,
) if view else {}
# support xml based subtype id
if kwargs.get('subtype_xmlid') and not kwargs.get('subtype_id'):
kwargs['subtype_id'] = self.env['ir.model.data']._xmlid_to_res_id(kwargs.pop('subtype_xmlid'))
# Prepare composer values for creation
composer_values = {
'composition_mode': 'mass_mail',
'message_type': message_type,
# subtype is not really used in mass mail mode as it is used mainly
# when posting, but keep it in case it is used in post send
'subtype_id': kwargs.pop('subtype_id', False) or self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
**kwargs,
}
composer_ctx = {
'default_composition_mode': 'mass_mail',
'default_model': self._name,
'default_template_id': template.id if template else False,
}
# Create the composer
composer = self.env['mail.compose.message'].with_context(
active_id=res_id,
active_ids=res_ids,
active_model=self._name,
default_composition_mode=kwargs['composition_mode'],
default_email_layout_xmlid=email_layout_xmlid,
default_model=self._name,
default_res_id=res_id,
default_template_id=template_id,
).create(kwargs)
# Simulate the onchange (like trigger in form the view) only
# when having a template in single-email mode
if template_id:
update_values = composer._onchange_template_id(
template_id,
kwargs['composition_mode'],
self._name,
res_id
)['value']
composer.write(update_values)
return composer._action_send_mail(auto_commit=auto_commit)
mails_su = self.env['mail.mail'].sudo()
for subset in [self] if template else self:
composer_ctx['active_ids'] = subset.ids # still required for mass mail mode
if template:
composer_ctx['default_res_id'] = subset.ids[0] if subset.ids else 0
else:
composer_values['body'] = bodies[subset.id]
composer_ctx['default_res_id'] = subset.id
composer = self.env['mail.compose.message'].with_context(
**composer_ctx
).create(composer_values)
# Simulate the onchange (like trigger in form the view) only
# when having a template in single-email mode
if template:
update_values = composer._onchange_template_id(
template.id,
composer_values['composition_mode'],
self._name,
subset.ids[0] if subset.ids else 0
)['value']
composer.write(update_values)
mails_as_sudo, _messages_as_sudo = composer._action_send_mail(auto_commit=auto_commit)
mails_su += mails_as_sudo
return mails_su
def message_post_with_source(self, source_ref, render_values=None,
message_type='notification',
subtype_xmlid=False, subtype_id=False,
**kwargs):
""" Post a message on each record of self, using a view to render the
body using QWeb.
Default values
* subtype_id: if not given, fallback on ``note`` to be consistent
with what message_post does;
:param record/str source_ref: reference to a source for rendering.
It can be one of
* a MailTemplate record. It will be used to render the various
message values (body, subject, recipients, ...). It should behave
like using the mail composer with a template;
* an IrUIView record. It will be used to render the content
(body). Other fields are left to the caller and/or default values
computation;
* an XmlID of a MailTemplate or of an IrUiView: see above
:param dict render_values: additional rendering values for qweb context;
:param str message_type: one of 'notification' or 'comment';
:param str subtype_xmlid: optional xml id of a mail.message.subtype to
fetch, will force value of subtype_id;
:param int subtype_id: subtype_id of the message, used mainly for followers
notification mechanism;
:param dict kwargs: additional values given to the 'mail.compose.message'
creation;
:return: posted mail.message records
"""
template, view = self._get_source_from_ref(source_ref)
# preliminary value safety check
self._raise_for_invalid_parameters(
set(kwargs.keys()),
forbidden_names={'body', 'composition_mode', 'model', 'res_id', 'values'}
)
# with a view, render bodies in batch (template is managed by composer)
bodies = self.env['mail.render.mixin']._render_template_qweb_view(
view,
self._name,
self.ids,
add_context=render_values,
) if view else {}
# Prepare composer values for creation
if subtype_xmlid:
subtype_id = self.env['ir.model.data']._xmlid_to_res_id(subtype_xmlid)
if not subtype_id:
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note')
messages = self.env['mail.message']
for record in self:
if template:
composer = self.env['mail.compose.message'].with_context(
active_ids=record.ids, # still required for mass mail mode
default_composition_mode='comment',
default_model=self._name,
default_res_id=record.id,
default_template_id=template.id,
).create({
'message_type': message_type,
'subtype_id': subtype_id,
**kwargs,
})
# Simulate the onchange (like trigger in form the view) to do the
# rendering in mono-record mode
update_values = composer._onchange_template_id(
template.id,
'comment',
self._name,
record.id,
)['value']
composer.write(update_values)
_mails_as_sudo, messages_as_sudo = composer._action_send_mail()
messages += messages_as_sudo
else:
messages += record.message_post(
body=bodies[record.id],
message_type=message_type,
subtype_id=subtype_id,
**kwargs
)
return messages
def message_notify(self, *,
partner_ids=False, parent_id=False, model=False, res_id=False,
author_id=None, email_from=None, body='', subject=False,
subtype_xmlid=None,
**kwargs):
""" Shortcut allowing to notify partners of messages that shouldn't be
displayed on a document. It pushes notifications on inbox or by email depending
on the user configuration, like other notifications. """
""" Shortcut allowing to notify partners of messages that should not be
displayed on a document. It pushes notifications on inbox or by email
depending on the user configuration, like other notifications.
Default values
* subtype_id: if not given, fallback on ``note`` to be consistent
with what message_post does;
:return: posted mail.message records
"""
if self:
self.ensure_one()
if not partner_ids:
@@ -2245,47 +2358,51 @@ class MailThread(models.AbstractModel):
self._notify_thread(new_message, msg_values, **notif_kwargs)
return new_message
def _message_log_with_view(self, views_or_xmlid, **kwargs):
""" Helper method to log a note using a view_id without notifying followers. """
return self._message_compose_with_view(views_or_xmlid, message_log=True, **kwargs)
def _message_log_with_view(self, view_ref, render_values=None,
message_type='notification', **kwargs):
""" Log a message on each record of self, using a view to render the
body using QWeb.
:param str/int/record view_ref: source QWeb template. It should be an
XmlID allowing to fetch an ``ir.ui.view``, or an ID of a view or
an ``ir.ui.view`` record;
:param dict render_values: additional rendering values for qweb context;
:param str message_type: one of 'notification' or 'comment';
:param kwargs: additional values propagated to ``_message_log``;
:return: posted mail.message records (as sudo)
"""
self._raise_for_invalid_parameters(
set(kwargs.keys()),
forbidden_names={'body', 'bodies'}
)
# with a view, render bodies in batch (template is managed by composer)
bodies = self.env['mail.render.mixin']._render_template_qweb_view(
view_ref,
self._name,
self.ids,
add_context=render_values,
)
return self._message_log_batch(
bodies=bodies,
message_type=message_type,
**kwargs
)
def _message_log(self, *, body='', author_id=None, email_from=None, subject=False,
message_type='notification',
attachment_ids=False, tracking_value_ids=False):
""" Shortcut allowing to post note on a document. It does not perform
any notification and pre-computes some values to have a short code
as optimized as possible. This method is private as it does not check
access rights and perform the message creation as sudo to speedup
the log process. This method should be called within methods where
access rights are already granted to avoid privilege escalation.
:return: created message (as sudo)"""
""" Shortcut allowing to post note on a document. See ``_message_log_batch``
for more details. """
self.ensure_one()
author_id, email_from = self._message_compute_author(author_id, email_from, raise_on_email=False)
msg_values = {
# author
'author_id': author_id,
'email_from': email_from,
# document
'model': self._name,
'record_name': False,
'res_id': self.id,
# content
'attachment_ids': attachment_ids,
'body': body,
'is_internal': True,
'message_type': message_type,
'subject': subject,
'subtype_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
'tracking_value_ids': tracking_value_ids,
# recipients
'email_add_signature': False, # False as no notification -> no need to compute signature
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from)[False],
}
return self.sudo()._message_create([msg_values])
return self._message_log_batch(
{self.id: body}, author_id=author_id, email_from=email_from,
subject=subject, message_type=message_type,
attachment_ids=attachment_ids, tracking_value_ids=tracking_value_ids
)
def _message_log_batch(self, bodies, author_id=None, email_from=None, subject=False,
message_type='notification',
@@ -2326,6 +2443,7 @@ class MailThread(models.AbstractModel):
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from)[False],
}
values_list = [dict(base_message_values,
res_id=record.id,
body=bodies.get(record.id, ''))
@@ -2401,6 +2519,8 @@ class MailThread(models.AbstractModel):
return self.name_get()[0][1]
def _message_create(self, values_list):
""" Low-level helper to create mail.message records. It is mainly used
to hide the cleanup of given values, for mail gateway or helpers."""
create_values_list = []
# preliminary value safety check
@@ -2455,6 +2575,61 @@ class MailThread(models.AbstractModel):
'tracking_value_ids',
}
def _get_source_from_ref(self, source_ref):
""" From a source_reference, return either a mail template, either
an ir ui view.
:return tuple(template, view): one is a recordset (may be void if
source_ref is a void recordset, or a singleton), the other one is
False. Always only one is set, as source is either a template,
either a view.
"""
template, view = False, False
if isinstance(source_ref, models.BaseModel):
if source_ref._name == 'mail.template':
template = source_ref
elif source_ref._name == 'ir.ui.view':
view = source_ref
else:
raise ValueError(
_('Invalid template or view source record %(svalue)s, is %(model)s instead',
svalue=source_ref,
model=source_ref._name,
))
if not template and not view:
raise ValueError(
_('Mailing or posting with a source should not be called with an empty %(source_type)s',
source_type=_('template') if template is not False else _('view'))
)
elif isinstance(source_ref, str):
try:
res_model, res_id = self.env['ir.model.data']._xmlid_to_res_model_res_id(
source_ref,
raise_if_not_found=True
)
except ValueError as e:
raise ValueError(
_('Invalid template or view source Xml ID %(source_ref)s does not exist anymore',
source_ref=source_ref)
) from e
if res_model == 'mail.template':
template = self.env['mail.template'].browse(res_id)
elif res_model == 'ir.ui.view':
view = self.env['ir.ui.view'].browse(res_id)
else:
raise ValueError(
_('Invalid template or view source reference %(svalue)s, is %(model)s instead',
svalue=source_ref,
model=res_model,
))
else:
raise ValueError(
_('Invalid template or view source %(svalue)s (type %(stype)s), should be a record or an XMLID',
svalue=source_ref,
stype=type(source_ref),
))
return template, view
def _get_notify_valid_parameters(self):
""" Several parameters exist for notification methods as business
flows often want to customize the standard notification experience.
+1 -1
View File
@@ -314,7 +314,7 @@ class MailGroup(models.Model):
values
))
mail_message = Mailthread._message_create(values)
mail_message = Mailthread._message_create([values])
# Find the <mail.group.message> parent
group_message_parent_id = False
@@ -117,9 +117,9 @@ class MailPluginController(http.Controller):
partner.write(partner_values)
partner.message_post_with_view(
partner.message_post_with_source(
'iap_mail.enrich_company',
values=iap_data,
render_values=iap_data,
subtype_xmlid='mail.mt_note',
)
@@ -357,9 +357,9 @@ class MailPluginController(http.Controller):
new_company = request.env['res.partner'].create(new_company_info)
new_company.message_post_with_view(
new_company.message_post_with_source(
'iap_mail.enrich_company',
values=iap_data,
render_values=iap_data,
subtype_xmlid='mail.mt_note',
)
+10 -2
View File
@@ -28,13 +28,21 @@ class MailThread(models.AbstractModel):
self.env['mailing.trace'].set_replied(domain=[('message_id', 'in', msg_references)])
return super(MailThread, self)._message_route_process(message, message_dict, routes)
def message_post_with_template(self, template_id, **kwargs):
def message_mail_with_source(self, source_ref, **kwargs):
# avoid having message send through `message_post*` methods being implicitly considered as
# mass-mailing
return super(MailThread, self.with_context(
default_mass_mailing_name=False,
default_mass_mailing_id=False,
)).message_post_with_template(template_id, **kwargs)
)).message_mail_with_source(source_ref, **kwargs)
def message_post_with_source(self, source_ref, **kwargs):
# avoid having message send through `message_post*` methods being implicitly considered as
# mass-mailing
return super(MailThread, self.with_context(
default_mass_mailing_name=False,
default_mass_mailing_id=False,
)).message_post_with_source(source_ref, **kwargs)
@api.model
def _routing_handle_bounce(self, email_message, message_dict):
+4 -4
View File
@@ -65,15 +65,15 @@ class StockRule(models.Model):
subtype_id=note_subtype_id
)
elif orderpoint:
production.message_post_with_view(
production.message_post_with_source(
'mail.message_origin_link',
values={'self': production, 'origin': orderpoint},
render_values={'self': production, 'origin': orderpoint},
subtype_id=note_subtype_id,
)
elif origin_production:
production.message_post_with_view(
production.message_post_with_source(
'mail.message_origin_link',
values={'self': production, 'origin': origin_production},
render_values={'self': production, 'origin': origin_production},
subtype_id=note_subtype_id,
)
return True
@@ -81,9 +81,9 @@ class ResCompany(models.Model):
if additional_data:
template_values = json.loads(additional_data)
template_values['flavor_text'] = _("Company auto-completed by Odoo Partner Autocomplete Service")
self.partner_id.message_post_with_view(
self.partner_id.message_post_with_source(
'iap_mail.enrich_company',
values=template_values,
render_values=template_values,
subtype_xmlid='mail.mt_note',
)
return True
@@ -166,9 +166,9 @@ class ResPartner(models.Model):
if partners.additional_info:
template_values = json.loads(partners.additional_info)
template_values['flavor_text'] = _("Partner created by Odoo Partner Autocomplete Service")
partners.message_post_with_view(
partners.message_post_with_source(
'iap_mail.enrich_company',
values=template_values,
render_values=template_values,
subtype_xmlid='mail.mt_note',
)
partners.write({'additional_info': False})
+8 -8
View File
@@ -64,14 +64,14 @@ class PortalShare(models.TransientModel):
share_link = self.resource_ref.get_base_url() + self.resource_ref._get_share_url(redirect=True, pid=partner.id)
saved_lang = self.env.lang
self = self.with_context(lang=partner.lang)
template = self.env.ref('portal.portal_share_template', False)
self.resource_ref.message_post_with_view(template,
values={'partner': partner, 'note': self.note, 'record': self.resource_ref,
self.resource_ref.message_post_with_source(
'portal.portal_share_template',
render_values={'partner': partner, 'note': self.note, 'record': self.resource_ref,
'share_link': share_link},
subject=_("You are invited to access %s", self.resource_ref.display_name),
subtype_xmlid='mail.mt_note',
email_layout_xmlid='mail.mail_notification_light',
partner_ids=[(6, 0, partner.ids)])
partner_ids=partner.ids)
self = self.with_context(lang=saved_lang)
def _send_signup_link(self, partners=None):
@@ -83,14 +83,14 @@ class PortalShare(models.TransientModel):
share_link = partner._get_signup_url_for_action(action='/mail/view', res_id=self.res_id, model=self.res_model)[partner.id]
saved_lang = self.env.lang
self = self.with_context(lang=partner.lang)
template = self.env.ref('portal.portal_share_template', False)
self.resource_ref.message_post_with_view(template,
values={'partner': partner, 'note': self.note, 'record': self.resource_ref,
self.resource_ref.message_post_with_source(
'portal.portal_share_template',
render_values={'partner': partner, 'note': self.note, 'record': self.resource_ref,
'share_link': share_link},
subject=_("You are invited to access %s", self.resource_ref.display_name),
subtype_xmlid='mail.mt_note',
email_layout_xmlid='mail.mail_notification_light',
partner_ids=[(6, 0, partner.ids)])
partner_ids=partner.ids)
self = self.with_context(lang=saved_lang)
def action_send_mail(self):
@@ -18,9 +18,8 @@ class AccountMove(models.Model):
comment_subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
for line in invoice.invoice_line_ids:
if line.product_id.email_template_id:
invoice.message_post_with_template(
line.product_id.email_template_id.id,
composition_mode="comment",
invoice.message_post_with_source(
line.product_id.email_template_id,
email_layout_xmlid="mail.mail_notification_light",
subtype_id=comment_subtype_id,
)
+6 -6
View File
@@ -750,8 +750,8 @@ class PurchaseOrder(models.Model):
if send_single:
return order._send_reminder_open_composer(template.id)
else:
order.with_context(is_reminder=True).message_post_with_template(
template.id,
order.with_context(is_reminder=True).message_post_with_source(
template,
email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature",
subtype_xmlid='mail.mt_comment',
)
@@ -1104,9 +1104,9 @@ class PurchaseOrderLine(models.Model):
if 'product_qty' in values:
for line in self:
if line.order_id.state == 'purchase':
line.order_id.message_post_with_view(
line.order_id.message_post_with_source(
'purchase.track_po_line_template',
values={'line': line, 'product_qty': values['product_qty']},
render_values={'line': line, 'product_qty': values['product_qty']},
subtype_xmlid='mail.mt_note',
)
if 'qty_received' in values:
@@ -1425,9 +1425,9 @@ class PurchaseOrderLine(models.Model):
def _track_qty_received(self, new_qty):
self.ensure_one()
if new_qty != self.qty_received and self.order_id.state == 'purchase':
self.order_id.message_post_with_view(
self.order_id.message_post_with_source(
'purchase.track_po_line_qty_received_template',
values={'line': self, 'qty_received': new_qty},
render_values={'line': self, 'qty_received': new_qty},
subtype_xmlid='mail.mt_note',
)
@@ -144,9 +144,9 @@ class PurchaseOrder(models.Model):
self.env['purchase.order.group'].create({'order_ids': [Command.set(origin_po_id.ids + orders.ids)]})
for order in orders:
if order.requisition_id:
order.message_post_with_view(
order.message_post_with_source(
'mail.message_origin_link',
values={'self': order, 'origin': order.requisition_id},
render_values={'self': order, 'origin': order.requisition_id},
subtype_xmlid='mail.mt_note',
)
return orders
@@ -158,9 +158,9 @@ class PurchaseOrder(models.Model):
result = super(PurchaseOrder, self).write(vals)
if vals.get('requisition_id'):
for order in self:
order.message_post_with_view(
order.message_post_with_source(
'mail.message_origin_link',
values={'self': order, 'origin': order.requisition_id, 'edit': True},
render_values={'self': order, 'origin': order.requisition_id, 'edit': True},
subtype_xmlid='mail.mt_note',
)
if vals.get('alternative_po_ids', False):
+3 -2
View File
@@ -251,8 +251,9 @@ class PurchaseOrder(models.Model):
# Get following pickings (created by push rules) to confirm them as well.
forward_pickings = self.env['stock.picking']._get_impacted_pickings(moves)
(pickings | forward_pickings).action_confirm()
picking.message_post_with_view('mail.message_origin_link',
values={'self': picking, 'origin': order},
picking.message_post_with_source(
'mail.message_origin_link',
render_values={'self': picking, 'origin': order},
subtype_xmlid='mail.mt_note',
)
return True
+5 -6
View File
@@ -181,12 +181,11 @@ class RatingMixin(models.AbstractModel):
"""
if lang:
template = template.with_context(lang=lang)
for record in self.with_context(mail_notify_force_send=force_send):
record.message_post_with_template(
template.id,
email_layout_xmlid='mail.mail_notification_light',
subtype_xmlid='mail.mt_note',
)
self.with_context(mail_notify_force_send=force_send).message_post_with_source(
template,
email_layout_xmlid='mail.mail_notification_light',
subtype_xmlid='mail.mt_note',
)
def rating_apply(self, rate, token=None, rating=None, feedback=None,
subtype_xmlid=None, notify_delay_send=False):
+8 -6
View File
@@ -131,6 +131,9 @@ class PaymentTransaction(models.Model):
))
if not template_id:
return
template = self.env['mail.template'].browse(template_id).exists()
if not template:
return
for tx in self:
tx = tx.with_company(tx.company_id).with_context(
@@ -140,12 +143,11 @@ class PaymentTransaction(models.Model):
lambda i: not i.is_move_sent and i.state == 'posted' and i._is_ready_to_be_sent()
)
invoice_to_send.is_move_sent = True # Mark invoice as sent
for invoice in invoice_to_send.with_user(SUPERUSER_ID):
invoice.message_post_with_template(
template_id,
email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
subtype_xmlid='mail.mt_comment',
)
invoice_to_send.with_user(SUPERUSER_ID).message_post_with_source(
template,
email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
subtype_xmlid='mail.mt_comment',
)
def _cron_send_invoice(self):
"""
+4 -4
View File
@@ -842,8 +842,8 @@ class SaleOrder(models.Model):
mail_template = sale_order._get_confirmation_template()
if not mail_template:
continue
sale_order.with_context(force_send=True).message_post_with_template(
mail_template.id,
sale_order.with_context(force_send=True).message_post_with_source(
mail_template,
email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
subtype_xmlid='mail.mt_comment',
)
@@ -1181,9 +1181,9 @@ class SaleOrder(models.Model):
if final:
moves.sudo().filtered(lambda m: m.amount_total < 0).action_switch_move_type()
for move in moves:
move.message_post_with_view(
move.message_post_with_source(
'mail.message_origin_link',
values={'self': move, 'origin': move.line_ids.sale_line_ids.order_id},
render_values={'self': move, 'origin': move.line_ids.sale_line_ids.order_id},
subtype_xmlid='mail.mt_note',
)
return moves
+2 -2
View File
@@ -87,8 +87,8 @@ class TestSaleSignature(HttpCaseWithUserPortal):
# must be sent to the user so he can see it
email_act = sales_order.action_quotation_send()
email_ctx = email_act.get('context', {})
sales_order.with_context(**email_ctx).message_post_with_template(
email_ctx.get('default_template_id'),
sales_order.with_context(**email_ctx).message_post_with_source(
self.env['mail.template'].browse(email_ctx.get('default_template_id')),
subtype_xmlid='mail.mt_comment',
)
+4 -4
View File
@@ -79,8 +79,8 @@ class TestSaleOrder(SaleCommon):
# send quotation
email_act = self.sale_order.action_quotation_send()
email_ctx = email_act.get('context', {})
self.sale_order.with_context(**email_ctx).message_post_with_template(
email_ctx.get('default_template_id'),
self.sale_order.with_context(**email_ctx).message_post_with_source(
self.env['mail.template'].browse(email_ctx.get('default_template_id')),
subtype_xmlid='mail.mt_comment',
)
self.assertTrue(self.sale_order.state == 'sent', 'Sale: state after sending is wrong')
@@ -104,8 +104,8 @@ class TestSaleOrder(SaleCommon):
# sent to to author or not (in case author is present in 'Recipients' of composer).
mail_template = self.env['mail.template'].browse(email_ctx.get('default_template_id')).copy({'auto_delete': False})
# send the mail with same user as customer
sale_order.with_context(**email_ctx).with_user(self.sale_user).message_post_with_template(
mail_template.id,
sale_order.with_context(**email_ctx).with_user(self.sale_user).message_post_with_source(
mail_template,
subtype_xmlid='mail.mt_comment',
)
self.assertTrue(sale_order.state == 'sent', 'Sale : state should be changed to sent')
+2 -2
View File
@@ -632,8 +632,8 @@ class TestSaleToInvoice(TestSaleCommon):
# send quotation
email_act = self.sale_order.action_quotation_send()
email_ctx = email_act.get('context', {})
self.sale_order.with_context(**email_ctx).message_post_with_template(
email_ctx.get('default_template_id'),
self.sale_order.with_context(**email_ctx).message_post_with_source(
self.env['mail.template'].browse(email_ctx.get('default_template_id')),
subtype_xmlid='mail.mt_comment',
)
self.assertTrue(self.sale_order.state == 'sent', 'Sale: state after sending is wrong')
@@ -186,9 +186,9 @@ class SaleAdvancePaymentInv(models.TransientModel):
self._prepare_invoice_values(order, down_payment_so_line)
).with_user(self.env.uid) # Unsudo the invoice after creation
invoice.message_post_with_view(
invoice.message_post_with_source(
'mail.message_origin_link',
values={'self': invoice, 'origin': order},
render_values={'self': invoice, 'origin': order},
subtype_xmlid='mail.mt_note',
)
+2 -2
View File
@@ -42,9 +42,9 @@ class StockMove(models.Model):
picking_id = self.mapped('picking_id')
sale_order_ids = self.mapped('sale_line_id.order_id')
for sale_order_id in sale_order_ids:
picking_id.message_post_with_view(
picking_id.message_post_with_source(
'mail.message_origin_link',
values={'self': picking_id, 'origin': sale_order_id},
render_values={'self': picking_id, 'origin': sale_order_id},
subtype_xmlid='mail.mt_note',
)
@@ -310,7 +310,10 @@ class TestSaleStockMargin(TestStockValuationCommon):
so = so_form.save()
email_act = so.action_quotation_send()
email_ctx = email_act.get('context', {})
so.with_context(**email_ctx).message_post_with_template(email_ctx.get('default_template_id'))
so.with_context(**email_ctx).message_post_with_source(
self.env['mail.template'].browse(email_ctx.get('default_template_id')),
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
)
self.assertEqual(so.state, 'sent')
self.assertEqual(so.order_line[0].purchase_price, 15)
+2 -2
View File
@@ -667,9 +667,9 @@ class StockMoveLine(models.Model):
data['result_package_name'] = self.env['stock.quant.package'].browse(vals.get('result_package_id')).name
if 'owner_id' in vals and vals['owner_id'] != move.owner_id.id:
data['owner_name'] = self.env['res.partner'].browse(vals.get('owner_id')).name
record.message_post_with_view(
record.message_post_with_source(
template,
values={'move': move, 'vals': dict(vals, **data)},
render_values={'move': move, 'vals': dict(vals, **data)},
subtype_xmlid='mail.mt_note',
)
+3 -3
View File
@@ -886,9 +886,9 @@ class Picking(models.Model):
def _send_confirmation_email(self):
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
for stock_pick in self.filtered(lambda p: p.company_id.stock_move_email_validation and p.picking_type_id.code == 'outgoing'):
delivery_template_id = stock_pick.company_id.stock_mail_confirmation_template_id.id
stock_pick.with_context(force_send=True).message_post_with_template(
delivery_template_id,
delivery_template = stock_pick.company_id.stock_mail_confirmation_template_id
stock_pick.with_context(force_send=True).message_post_with_source(
delivery_template,
email_layout_xmlid='mail.mail_notification_light',
subtype_id=subtype_id,
)
+2 -2
View File
@@ -133,9 +133,9 @@ class ReturnPicking(models.TransientModel):
# create new picking for returned products
new_picking = self.picking_id.copy(self._prepare_picking_default_values())
picking_type_id = new_picking.picking_type_id.id
new_picking.message_post_with_view(
new_picking.message_post_with_source(
'mail.message_origin_link',
values={'self': new_picking, 'origin': self.picking_id},
render_values={'self': new_picking, 'origin': self.picking_id},
subtype_xmlid='mail.mt_note',
)
returned_lines = 0
+65 -33
View File
@@ -404,6 +404,7 @@ class TestMessageNotify(TestMessagePostCommon):
)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.tests')
def test_notify_parameters(self):
""" Test usage of parameters in notify, both for unwanted side effects
and magic parameters. """
@@ -536,7 +537,7 @@ class TestMessageLog(TestMessagePostCommon):
new_notes = test_records._message_log_with_view(
'test_mail.mail_template_simple_test',
values={'partner': self.user_employee.partner_id}
render_values={'partner': self.user_employee.partner_id}
)
for test_record, new_note in zip(test_records, new_notes):
self.assertMessageFields(
@@ -1031,6 +1032,30 @@ class TestMessagePostHelpers(TestMessagePostCommon):
# Force the attachments of the template to be in the natural order.
cls.test_template.invalidate_recordset(['attachment_ids'])
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_helpers_source_ref(self):
""" Test various sources (record or xml id) to ensure source_ref right
computation. """
test_records = self.test_records.with_env(self.env)
template = self.test_template.with_env(self.env)
view = self.env.ref('test_mail.mail_template_simple_test')
for source_ref in ('test_mail.mail_test_ticket_tracking_tpl', template,
'test_mail.mail_template_simple_test', view):
with self.subTest(source_ref=source_ref), self.mock_mail_gateway():
_new_mails = test_records.with_user(self.user_employee).message_mail_with_source(
source_ref,
render_values={'partner': self.user_employee.partner_id},
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
)
_new_messages = test_records.with_user(self.user_employee).message_post_with_source(
source_ref,
render_values={'partner': self.user_employee.partner_id},
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_mail_with_template(self):
@@ -1038,9 +1063,8 @@ class TestMessagePostHelpers(TestMessagePostCommon):
test_records = self.test_records.with_env(self.env)
template = self.test_template.with_env(self.env)
with self.mock_mail_gateway():
_new_mails, _new_messages = test_records.with_user(self.user_employee).message_post_with_template(
template.id,
composition_mode='mass_mail',
_new_mails = test_records.with_user(self.user_employee).message_mail_with_source(
template,
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
)
@@ -1086,14 +1110,13 @@ class TestMessagePostHelpers(TestMessagePostCommon):
test_record.message_subscribe(test_record.customer_id.ids)
with self.mock_mail_gateway():
new_messages = test_records.message_post_with_view(
new_mails = test_records.message_mail_with_source(
'test_mail.mail_template_simple_test',
values={'partner': self.user_employee.partner_id},
composition_mode='mass_mail',
render_values={'partner': self.user_employee.partner_id},
subject='About mass mailing',
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
)
self.assertEqual(len(new_messages), 0)
self.assertEqual(len(new_mails), 10)
self.assertEqual(len(self._new_mails), 10)
# sent emails (mass mail mode)
@@ -1120,6 +1143,19 @@ class TestMessagePostHelpers(TestMessagePostCommon):
}
)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_post_with_source_subtype(self):
""" Test subtype tweaks when posting with a source """
test_record = self.test_records.with_env(self.env)[0]
test_template = self.test_template.with_env(self.env)
with self.mock_mail_gateway():
new_message = test_record.with_user(self.user_employee).message_post_with_source(
test_template,
subtype_xmlid='mail.mt_activities',
)
self.assertEqual(new_message.subtype_id, self.env.ref("mail.mt_activities"))
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_post_with_template(self):
@@ -1128,9 +1164,8 @@ class TestMessagePostHelpers(TestMessagePostCommon):
test_record.message_subscribe(test_record.customer_id.ids)
test_template = self.test_template.with_env(self.env)
with self.mock_mail_gateway():
_new_mail, new_message = test_record.with_user(self.user_employee).message_post_with_template(
test_template.id,
composition_mode='comment',
new_message = test_record.with_user(self.user_employee).message_post_with_source(
test_template,
message_type='comment',
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
)
@@ -1165,15 +1200,15 @@ class TestMessagePostHelpers(TestMessagePostCommon):
)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_post_with_template_defaults(self):
""" Test default values, notably subtype being a comment """
test_record = self.test_records.with_env(self.env)[0]
test_record.message_subscribe(test_record.customer_id.ids)
test_template = self.test_template.with_env(self.env)
with self.mock_mail_gateway():
_new_mail, new_message = test_record.with_user(self.user_employee).message_post_with_template(
test_template.id,
new_message = test_record.with_user(self.user_employee).message_post_with_source(
test_template,
)
# created partners from inline email addresses
@@ -1192,7 +1227,7 @@ class TestMessagePostHelpers(TestMessagePostCommon):
{'partner': new_partners[1], 'type': 'email'},
{'partner': test_record.customer_id, 'type': 'email'},
],
'subtype': 'mail.mt_comment',
'subtype': 'mail.mt_note',
}])
self.assertMessageFields(
new_message,
@@ -1213,10 +1248,10 @@ class TestMessagePostHelpers(TestMessagePostCommon):
test_record.message_subscribe(test_record.customer_id.ids)
with self.mock_mail_gateway():
new_message = test_record.message_post_with_view(
new_message = test_record.message_post_with_source(
'test_mail.mail_template_simple_test',
message_type='comment',
values={'partner': self.user_employee.partner_id},
render_values={'partner': self.user_employee.partner_id},
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
)
@@ -1250,9 +1285,9 @@ class TestMessagePostHelpers(TestMessagePostCommon):
# defaults is a note, take into account specified recipients
with self.mock_mail_gateway():
new_message = test_record.message_post_with_view(
new_message = test_record.message_post_with_source(
'test_mail.mail_template_simple_test',
values={'partner': self.user_employee.partner_id},
render_values={'partner': self.user_employee.partner_id},
partner_ids=test_record.customer_id.ids,
)
@@ -1263,7 +1298,7 @@ class TestMessagePostHelpers(TestMessagePostCommon):
'notif': [
{'partner': test_record.customer_id, 'type': 'email'},
],
'subtype': 'mail.mt_comment',
'subtype': 'mail.mt_note',
}])
self.assertMessageFields(
new_message,
@@ -1340,9 +1375,8 @@ class TestMessagePostLang(TestMailCommon, TestRecipients):
test_template = self.test_template.with_user(self.env.user)
with self.mock_mail_gateway():
test_record.message_post_with_template(
test_template.id,
composition_mode='comment',
test_record.message_post_with_source(
test_template,
email_layout_xmlid='mail.test_layout',
message_type='comment',
subtype_id=self.env.ref('mail.mt_comment').id,
@@ -1382,9 +1416,8 @@ class TestMessagePostLang(TestMailCommon, TestRecipients):
test_template = self.test_template.with_user(self.env.user)
with self.mock_mail_gateway():
test_records.message_post_with_template(
test_template.id,
composition_mode='mass_mail',
test_records.message_mail_with_source(
test_template,
email_layout_xmlid='mail.test_layout',
message_type='comment',
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
@@ -1448,13 +1481,12 @@ class TestMessagePostLang(TestMailCommon, TestRecipients):
test_template = self.test_template.with_user(self.env.user)
with self.mock_mail_gateway():
for test_record in test_records:
test_record.message_post_with_template(
test_template.id,
email_layout_xmlid='mail.test_layout',
message_type='comment',
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
)
test_records.message_post_with_source(
test_template,
email_layout_xmlid='mail.test_layout',
message_type='comment',
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
)
record0_customer = self.env['res.partner'].search([('email_normalized', '=', 'test.record.1@test.customer.com')], limit=1)
self.assertTrue(record0_customer, 'Template usage should have created a contact based on record email')
+1 -1
View File
@@ -150,7 +150,7 @@ class TestTracking(TestMailCommon):
def patched_message_track_post_template(*args, **kwargs):
if args[0]._name == "mail.test.track":
args[0].message_post_with_template(template.id)
args[0].message_post_with_source(template)
return True
with patch('odoo.addons.mail.models.mail_thread.MailThread._message_track_post_template', patched_message_track_post_template):
+6 -7
View File
@@ -621,7 +621,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
with self.assertQueryCount(__system__=11, employee=11):
records._message_log_with_view(
'test_mail.mail_template_simple_test',
values={'partner': self.customer.with_env(self.env)}
render_values={'partner': self.customer.with_env(self.env)}
)
@users('__system__', 'employee')
@@ -862,14 +862,13 @@ class TestMailComplexPerformance(BaseMailPerformance):
def test_complex_message_post_template(self):
self.container.message_subscribe(self.user_portal.partner_id.ids)
record = self.container.with_user(self.env.user)
template_id = self.env.ref('test_mail.mail_test_container_tpl').id
template = self.env.ref('test_mail.mail_test_container_tpl')
# about 20 (19 ?) queries per additional customer group
with self.assertQueryCount(__system__=42, employee=43):
record.message_post_with_template(
template_id,
record.message_post_with_source(
template,
message_type='comment',
composition_mode='comment',
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
)
@@ -892,9 +891,9 @@ class TestMailComplexPerformance(BaseMailPerformance):
composer._onchange_template_id_wrapper()
with self.assertQueryCount(__system__=121, employee=141):
messages_as_sudo = test_records.message_post_with_view(
messages_as_sudo = test_records.message_post_with_source(
'test_mail.mail_template_simple_test',
values={'partner': self.user_test.partner_id},
render_values={'partner': self.user_test.partner_id},
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
)
+2 -2
View File
@@ -221,10 +221,10 @@ class BlogPost(models.Model):
def _check_for_publication(self, vals):
if vals.get('is_published'):
for post in self.filtered(lambda p: p.active):
post.blog_id.message_post_with_view(
post.blog_id.message_post_with_source(
'website_blog.blog_post_template_new_post',
subject=post.name,
values={'post': post},
render_values={'post': post},
subtype_xmlid='website_blog.mt_blog_blog_published',
)
return True
@@ -376,9 +376,9 @@ class CRMRevealRule(models.Model):
'flavor_text': _("Opportunity created by Odoo Lead Generation"),
'people_data': result.get('people_data'),
})
lead.message_post_with_view(
lead.message_post_with_source(
'iap_mail.enrich_company',
values=template_values,
render_values=template_values,
subtype_xmlid='mail.mt_note',
)
@@ -406,9 +406,9 @@ class Track(models.Model):
post_values = {} if self.env.user.email else {'email_from': self.env.company.catchall_formatted}
for track in tracks:
track.event_id.message_post_with_view(
track.event_id.message_post_with_source(
'website_event_track.event_track_template_new',
values={
render_values={
'track': track,
'is_html_empty': is_html_empty,
},
+5 -5
View File
@@ -623,17 +623,17 @@ class Post(models.Model):
tag_partners = post.tag_ids.sudo().mapped('message_partner_ids')
if post.state == 'active' and post.parent_id:
post.parent_id.message_post_with_view(
post.parent_id.message_post_with_source(
'website_forum.forum_post_template_new_answer',
subject=_('Re: %s', post.parent_id.name),
partner_ids=[(4, p.id) for p in tag_partners],
partner_ids=tag_partners.ids,
subtype_xmlid='website_forum.mt_answer_new',
)
elif post.state == 'active' and not post.parent_id:
post.message_post_with_view(
post.message_post_with_source(
'website_forum.forum_post_template_new_question',
subject=post.name,
partner_ids=[(4, p.id) for p in tag_partners],
partner_ids=tag_partners.ids,
subtype_xmlid='website_forum.mt_question_new',
)
elif post.state == 'pending' and not post.parent_id:
@@ -642,7 +642,7 @@ class Post(models.Model):
partners = post.sudo().message_partner_ids | tag_partners
partners = partners.filtered(lambda partner: partner.user_ids and any(user.karma >= post.forum_id.karma_moderate for user in partner.user_ids))
post.message_post_with_view(
post.message_post_with_source(
'website_forum.forum_post_template_validation',
subject=post.name,
partner_ids=partners.ids,