Files
odoo_source/addons/mail/models/res_partner.py
T
Pratima Gupta 1184f7872d [IMP] mail: ease the use and specification of notification emails layout
This commit proposes to ease the use and specification of layout used to
encapsulate email body when sending notification emails. This is done
through two means

 * message_post_with_template now supports the custom layout parameter
   that allows to set the layout to use when sending notification emails
   in the message_post process. We have to rely on the context as it is
   the current way of using it in the composer. It is likely to change in
   a near future;
 * composer now supports through context (as we have no other choice)
   specific notification values to give to the notification layout rendering;

This commit is related to task 51122 (and PR #24052).
2018-06-08 15:20:04 +02:00

288 lines
13 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_flat_thread = False
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
opt_out = fields.Boolean(
'Opt-Out', help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
"Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing.")
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, notif_values):
# compute signature
if not notif_values.pop('add_sign', True):
signature = False
elif message.author_id and message.author_id.user_ids and message.author_id.user_ids[0].signature:
signature = message.author_id.user_ids[0].signature
elif message.author_id:
signature = "<p>-- <br/>%s</p>" % message.author_id.name
else:
signature = ""
# compute Sent by
if message.author_id and message.author_id.user_ids:
user = message.author_id.user_ids[0]
else:
user = self.env.user
if user.company_id.website:
website_url = 'http://%s' % user.company_id.website if not user.company_id.website.lower().startswith(('http:', 'https:')) else user.company_id.website
else:
website_url = False
model_name = False
if message.model:
model_name = self.env['ir.model']._get(message.model).display_name
record_name = message.record_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')
record = False
if message.res_id and message.model in self.env:
record = self.env[message.model].browse(message.res_id)
company = user.company_id
if record and hasattr(record, 'company_id'):
company = record.company_id
return {
'message': message,
'signature': signature,
'website_url': website_url,
'company': company,
'model_description': model_name,
'record': record,
'record_name': record_name,
'tracking_values': tracking,
'is_discussion': is_discussion,
'subtype': message.subtype_id,
}
@api.model
def _notify_prepare_email_values(self, message, notif_values):
# compute email references
references = message.parent_id.message_id if message.parent_id else False
# custom values
custom_values = dict()
if message.res_id and message.model:
custom_values = self.env['mail.thread']._notify_specific_email_values_on_records(message, records=self.env[message.model].browse(message.res_id))
mail_values = {
'mail_message_id': message.id,
'mail_server_id': message.mail_server_id.id,
'auto_delete': notif_values.pop('mail_auto_delete', True),
'references': references,
}
mail_values.update(custom_values)
return mail_values
@api.model
def _notify_send(self, body, subject, recipients, **mail_values):
emails = self.env['mail.mail']
recipients_nbr = len(recipients)
for email_chunk in split_every(50, recipients.ids):
# TDE FIXME: missing message parameter. So we will find mail_message_id
# in the mail_values and browse it. It should already be in the
# cache so should not impact performances.
mail_message_id = mail_values.get('mail_message_id')
message = self.env['mail.message'].browse(mail_message_id) if mail_message_id else None
tig = self.env[message.model].browse(message.res_id) if message and message.model and message.res_id else False
recipient_values = self.env['mail.thread']._notify_email_recipients_on_records(message, email_chunk, records=tig)
create_values = {
'body_html': body,
'subject': subject,
}
create_values.update(mail_values)
create_values.update(recipient_values)
emails |= self.env['mail.mail'].create(create_values)
return emails, recipients_nbr
@api.model
def _notify_udpate_notifications(self, emails):
for email in emails:
notifications = self.env['mail.notification'].sudo().search([
('mail_message_id', '=', email.mail_message_id.id),
('res_partner_id', 'in', email.recipient_ids.ids)])
notifications.write({
'mail_id': email.id,
'is_email': True,
'is_read': True, # handle by email discards Inbox notification
'email_status': 'ready',
})
@api.multi
def _notify(self, message, layout=False, force_send=False, send_after_commit=True, values=None):
""" Method to send email linked to notified messages. The recipients are
the recordset on which this method is called.
:param boolean force_send: send notification emails now instead of letting the scheduler handle the email queue
:param boolean send_after_commit: send notification emails after the transaction end instead of durign the
transaction; this option is used only if force_send is True
:param dict values: values used to compute the notification process, containing
* add_sign: add user signature to notification email, default is True
* mail_auto_delete: auto delete send emails, default is True
* other values are given to the context used to render the notification template, allowing customization
"""
if not self.ids:
return True
values = values if values is not None else {}
template_xmlid = layout if layout else 'mail.message_notification_email'
try:
base_template = self.env.ref(template_xmlid, raise_if_not_found=True)
except ValueError:
_logger.warning('QWeb template %s not found when sending notification emails. Skipping.' % (template_xmlid))
return False
base_template_ctx = self._notify_prepare_template_context(message, values)
base_mail_values = self._notify_prepare_email_values(message, values)
# classify recipients: actions / no action
tig = self.env[message.model].browse(message.res_id) if message.model and message.res_id else False
recipients = self.env['mail.thread']._notify_classify_recipients_on_records(message, self, records=tig)
emails = self.env['mail.mail']
recipients_nbr, recipients_max = 0, 50
for email_type, recipient_template_values in recipients.items():
if recipient_template_values['recipients']:
# generate notification email content
template_ctx = {**base_template_ctx, **recipient_template_values, **values} # fixme: set button_unfollow to none
fol_values = {
'subject': message.subject or (message.record_name and 'Re: %s' % message.record_name),
'body': base_template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True),
}
fol_values['body'] = self.env['mail.thread']._replace_local_links(fol_values['body'])
# send email
new_emails, new_recipients_nbr = self._notify_send(fol_values['body'], fol_values['subject'], recipient_template_values['recipients'], **base_mail_values)
# update notifications
self._notify_udpate_notifications(new_emails)
emails |= new_emails
recipients_nbr += new_recipients_nbr
# 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 recipients_nbr < 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]