- Create a PO for a French customer
- Send by email
The body of the email is in French, but some parts (such as the View
button or the footer) are not translated.
The body is translated following the field `lang` of the email template,
in this case `${object.partner_id.lang}`. This is done when opening the
mail composer (so the message appears in French). However, other parts
are rendered only when sending: at this point, the language to be used
in the template is lost forever in the darkness.
To avoid this, we keep a specific context key `custom_layout_lang` that
we retrieve when rendering the custom layout.
opw-1908420
253 lines
12 KiB
Python
253 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
import threading
|
|
|
|
from odoo.tools.misc import split_every
|
|
|
|
from odoo import _, api, fields, models, registry, SUPERUSER_ID
|
|
from odoo.osv import expression
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Partner(models.Model):
|
|
""" Update partner to add a field about notification preferences. Add a generic opt-out field that can be used
|
|
to restrict usage of automatic email templates. """
|
|
_name = "res.partner"
|
|
_inherit = ['res.partner', 'mail.thread', 'mail.activity.mixin', 'mail.blacklist.mixin']
|
|
_mail_flat_thread = False
|
|
|
|
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
|
|
channel_ids = fields.Many2many('mail.channel', 'mail_channel_partner', 'partner_id', 'channel_id', string='Channels', copy=False)
|
|
|
|
@api.multi
|
|
def message_get_suggested_recipients(self):
|
|
recipients = super(Partner, self).message_get_suggested_recipients()
|
|
for partner in self:
|
|
partner._message_add_suggested_recipient(recipients, partner=partner, reason=_('Partner Profile'))
|
|
return recipients
|
|
|
|
@api.multi
|
|
def message_get_default_recipients(self):
|
|
return dict((res_id, {'partner_ids': [res_id], 'email_to': False, 'email_cc': False}) for res_id in self.ids)
|
|
|
|
@api.model
|
|
def _notify_prepare_template_context(self, message, record, model_description=False, mail_auto_delete=True):
|
|
# compute send user and its related signature
|
|
signature = ''
|
|
if message.author_id and message.author_id.user_ids:
|
|
user = message.author_id.user_ids[0]
|
|
if message.add_sign:
|
|
signature = user.signature
|
|
else:
|
|
user = self.env.user
|
|
if message.add_sign:
|
|
signature = "<p>-- <br/>%s</p>" % message.author_id.name
|
|
|
|
company = record.company_id.sudo() if record and 'company_id' in record else user.company_id
|
|
if company.website:
|
|
website_url = 'http://%s' % company.website if not company.website.lower().startswith(('http:', 'https:')) else company.website
|
|
else:
|
|
website_url = False
|
|
|
|
# Retrieve the language in which the template was rendered, in order to render the custom
|
|
# layout in the same language.
|
|
lang = self.env.context.get('lang')
|
|
if {'default_template_id', 'default_model', 'default_res_id'} <= self.env.context.keys():
|
|
template = self.env['mail.template'].browse(self.env.context['default_template_id'])
|
|
if template and template.lang:
|
|
lang = template._render_template(template.lang, self.env.context['default_model'], self.env.context['default_res_id'])
|
|
|
|
if not model_description and message.model:
|
|
model_description = self.env['ir.model'].with_context(lang=lang)._get(message.model).display_name
|
|
|
|
tracking = []
|
|
for tracking_value in self.env['mail.tracking.value'].sudo().search([('mail_message_id', '=', message.id)]):
|
|
tracking.append((tracking_value.field_desc,
|
|
tracking_value.get_old_display_value()[0],
|
|
tracking_value.get_new_display_value()[0]))
|
|
|
|
is_discussion = message.subtype_id.id == self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment')
|
|
|
|
return {
|
|
'message': message,
|
|
'signature': signature,
|
|
'website_url': website_url,
|
|
'company': company,
|
|
'model_description': model_description,
|
|
'record': record,
|
|
'record_name': message.record_name,
|
|
'tracking_values': tracking,
|
|
'is_discussion': is_discussion,
|
|
'subtype': message.subtype_id,
|
|
'lang': lang,
|
|
}
|
|
|
|
@api.model
|
|
def _notify(self, message, rdata, record, force_send=False, send_after_commit=True, model_description=False, mail_auto_delete=True):
|
|
""" Method to send email linked to notified messages. The recipients are
|
|
the recordset on which this method is called.
|
|
|
|
:param message: mail.message record to notify;
|
|
:param rdata: recipient data (see mail.message _notify);
|
|
:param record: optional record on which the message was posted;
|
|
:param force_send: tells whether to send notification emails within the
|
|
current transaction or to use the email queue;
|
|
:param send_after_commit: if force_send, tells whether to send emails after
|
|
the transaction has been committed using a post-commit hook;
|
|
:param model_description: optional data used in notification process (see
|
|
notification templates);
|
|
:param mail_auto_delete: delete notification emails once sent;
|
|
"""
|
|
if not rdata:
|
|
return True
|
|
|
|
base_template_ctx = self._notify_prepare_template_context(message, record, model_description=model_description)
|
|
template_xmlid = message.layout if message.layout else 'mail.message_notification_email'
|
|
try:
|
|
base_template = self.env.ref(template_xmlid, raise_if_not_found=True).with_context(lang=base_template_ctx['lang'])
|
|
except ValueError:
|
|
_logger.warning('QWeb template %s not found when sending notification emails. Sending without layouting.' % (template_xmlid))
|
|
base_template = False
|
|
|
|
# prepare notification mail values
|
|
base_mail_values = {
|
|
'mail_message_id': message.id,
|
|
'mail_server_id': message.mail_server_id.id,
|
|
'auto_delete': mail_auto_delete,
|
|
'references': message.parent_id.message_id if message.parent_id else False
|
|
}
|
|
if record:
|
|
base_mail_values.update(self.env['mail.thread']._notify_specific_email_values_on_records(message, records=record))
|
|
|
|
# classify recipients: actions / no action
|
|
recipients = self.env['mail.thread']._notify_classify_recipients_on_records(message, rdata, records=record)
|
|
|
|
Mail = self.env['mail.mail'].sudo()
|
|
emails = self.env['mail.mail'].sudo()
|
|
email_pids = set()
|
|
recipients_nbr, recipients_max = 0, 50
|
|
for group_tpl_values in [group for group in recipients.values() if group['recipients']]:
|
|
# generate notification email content
|
|
template_ctx = {**base_template_ctx, **group_tpl_values}
|
|
mail_body = base_template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
|
|
mail_body = self.env['mail.thread']._replace_local_links(mail_body)
|
|
mail_subject = message.subject or (message.record_name and 'Re: %s' % message.record_name)
|
|
|
|
# send email
|
|
for email_chunk in split_every(50, group_tpl_values['recipients']):
|
|
recipient_values = self.env['mail.thread']._notify_email_recipients_on_records(message, email_chunk, records=record)
|
|
create_values = {
|
|
'body_html': mail_body,
|
|
'subject': mail_subject,
|
|
}
|
|
create_values.update(base_mail_values)
|
|
create_values.update(recipient_values)
|
|
recipient_ids = [r[1] for r in create_values.get('recipient_ids', [])]
|
|
email = Mail.create(create_values)
|
|
|
|
if email and recipient_ids:
|
|
notifications = self.env['mail.notification'].sudo().search([
|
|
('mail_message_id', '=', email.mail_message_id.id),
|
|
('res_partner_id', 'in', list(recipient_ids))
|
|
])
|
|
notifications.write({
|
|
'is_email': True,
|
|
'mail_id': email.id,
|
|
'is_read': True, # handle by email discards Inbox notification
|
|
'email_status': 'ready',
|
|
})
|
|
|
|
emails |= email
|
|
email_pids.update(recipient_ids)
|
|
|
|
# NOTE:
|
|
# 1. for more than 50 followers, use the queue system
|
|
# 2. do not send emails immediately if the registry is not loaded,
|
|
# to prevent sending email during a simple update of the database
|
|
# using the command-line.
|
|
test_mode = getattr(threading.currentThread(), 'testing', False)
|
|
if force_send and len(emails) < recipients_max and \
|
|
(not self.pool._init or test_mode):
|
|
email_ids = emails.ids
|
|
dbname = self.env.cr.dbname
|
|
_context = self._context
|
|
|
|
def send_notifications():
|
|
db_registry = registry(dbname)
|
|
with api.Environment.manage(), db_registry.cursor() as cr:
|
|
env = api.Environment(cr, SUPERUSER_ID, _context)
|
|
env['mail.mail'].browse(email_ids).send()
|
|
|
|
# unless asked specifically, send emails after the transaction to
|
|
# avoid side effects due to emails being sent while the transaction fails
|
|
if not test_mode and send_after_commit:
|
|
self._cr.after('commit', send_notifications)
|
|
else:
|
|
emails.send()
|
|
|
|
return True
|
|
|
|
@api.multi
|
|
def _notify_by_chat(self, message):
|
|
""" Broadcast the message to all the partner since """
|
|
if not self:
|
|
return
|
|
message_values = message.message_format()[0]
|
|
notifications = []
|
|
for partner in self:
|
|
notifications.append([(self._cr.dbname, 'ir.needaction', partner.id), dict(message_values)])
|
|
self.env['bus.bus'].sendmany(notifications)
|
|
|
|
@api.model
|
|
def get_needaction_count(self):
|
|
""" compute the number of needaction of the current user """
|
|
if self.env.user.partner_id:
|
|
self.env.cr.execute("""
|
|
SELECT count(*) as needaction_count
|
|
FROM mail_message_res_partner_needaction_rel R
|
|
WHERE R.res_partner_id = %s AND (R.is_read = false OR R.is_read IS NULL)""", (self.env.user.partner_id.id,))
|
|
return self.env.cr.dictfetchall()[0].get('needaction_count')
|
|
_logger.error('Call to needaction_count without partner_id')
|
|
return 0
|
|
|
|
@api.model
|
|
def get_starred_count(self):
|
|
""" compute the number of starred of the current user """
|
|
if self.env.user.partner_id:
|
|
self.env.cr.execute("""
|
|
SELECT count(*) as starred_count
|
|
FROM mail_message_res_partner_starred_rel R
|
|
WHERE R.res_partner_id = %s """, (self.env.user.partner_id.id,))
|
|
return self.env.cr.dictfetchall()[0].get('starred_count')
|
|
_logger.error('Call to starred_count without partner_id')
|
|
return 0
|
|
|
|
@api.model
|
|
def get_static_mention_suggestions(self):
|
|
""" To be overwritten to return the id, name and email of partners used as static mention
|
|
suggestions loaded once at webclient initialization and stored client side. """
|
|
return []
|
|
|
|
@api.model
|
|
def get_mention_suggestions(self, search, limit=8):
|
|
""" Return 'limit'-first partners' id, name and email such that the name or email matches a
|
|
'search' string. Prioritize users, and then extend the research to all partners. """
|
|
search_dom = expression.OR([[('name', 'ilike', search)], [('email', 'ilike', search)]])
|
|
fields = ['id', 'name', 'email']
|
|
|
|
# Search users
|
|
domain = expression.AND([[('user_ids.id', '!=', False)], search_dom])
|
|
users = self.search_read(domain, fields, limit=limit)
|
|
|
|
# Search partners if less than 'limit' users found
|
|
partners = []
|
|
if len(users) < limit:
|
|
partners = self.search_read(search_dom, fields, limit=limit)
|
|
# Remove duplicates
|
|
partners = [p for p in partners if not len([u for u in users if u['id'] == p['id']])]
|
|
|
|
return [users, partners]
|