summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibault Delavallée <tde@odoo.com>2019-08-09 09:50:20 +0000
committerThibault Delavallée <tde@odoo.com>2019-08-12 12:00:22 +0000
commit9bd9cbf5325eccde2e9fd2387c9d8167309bfb92 (patch)
tree3cda95b990f036405fbe2fcc67cd07b37208492a
parent5d1f08a01289375acbe44c2f33d95869477cdb1e (diff)
[IMP] mail: allow message creation in batch
PURPOSE Allow quick message creation by allowing create multi on mail.message. Also introduce a new helper tool method to log in batch on records. SPECIFICATIONS Purpose of this commit is to add a way to log notes in batch on a document. Those notes are logged without any notification mechanism, allowing to attach a note in as few queries as possible. This is implemented by * allowing create multi on mail.message. Nothing changes in its behavior except that it is not working on a list of dictionaries; * adding a ``_message_log_batch`` tool method allowing to post a batch of notes on records; * adding a tool method to correctly find author / email_from, to be used in various message_post / log / notify methods in order to have the same behavior in all those methods; Logging in batch will soon be used in SMS when attaching a note to sent SMS without having extra notifications. This will be used notably when performing mass SMS without using the standard notification mechanism. LINKS Task 1997464 PR #34424 Original SMS addition: Task 1922163 (4287481)
-rw-r--r--addons/mail/models/mail_message.py137
-rw-r--r--addons/mail/models/mail_thread.py120
2 files changed, 147 insertions, 110 deletions
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index c0ff09fec94..efcd8f8d2ae 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -985,78 +985,77 @@ class Message(models.Model):
'message_needaction_counter',
], ids=[res_id])
- @api.model
- def create(self, values):
- # coming from mail.js that does not have pid in its values
- if self.env.context.get('default_starred'):
- self = self.with_context({'default_starred_partner_ids': [(4, self.env.user.partner_id.id)]})
-
- if 'email_from' not in values: # needed to compute reply_to
- values['email_from'] = self._get_default_from()
- if not values.get('message_id'):
- values['message_id'] = self._get_message_id(values)
- if 'reply_to' not in values:
- values['reply_to'] = self._get_reply_to(values)
- if 'record_name' not in values and 'default_record_name' not in self.env.context:
- values['record_name'] = self._get_record_name(values)
-
- if 'attachment_ids' not in values:
- values['attachment_ids'] = []
- # extract base64 images
- if 'body' in values:
- Attachments = self.env['ir.attachment']
- data_to_url = {}
- def base64_to_boundary(match):
- key = match.group(2)
- if not data_to_url.get(key):
- name = match.group(4) if match.group(4) else 'image%s' % len(data_to_url)
- try:
- attachment = Attachments.create({
- 'name': name,
- 'datas': match.group(2),
- 'res_model': values.get('model'),
- 'res_id': values.get('res_id'),
- })
- except binascii_error:
- _logger.warning("Impossible to create an attachment out of badly formated base64 embedded image. Image has been removed.")
- return match.group(3) # group(3) is the url ending single/double quote matched by the regexp
- else:
- attachment.generate_access_token()
- values['attachment_ids'].append((4, attachment.id))
- data_to_url[key] = ['/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token), name]
- return '%s%s alt="%s"' % (data_to_url[key][0], match.group(3), data_to_url[key][1])
- values['body'] = _image_dataurl.sub(base64_to_boundary, tools.ustr(values['body']))
-
- # delegate creation of tracking after the create as sudo to avoid access rights issues
- tracking_values_cmd = values.pop('tracking_value_ids', False)
- message = super(Message, self).create(values)
-
- # check attachement access
- if values.get('attachment_ids'):
- attachment_ids = []
- if all(isinstance(command, int) or command[0] in (4, 6) for command in values.get('attachment_ids')):
- attachment_ids = []
+ @api.model_create_multi
+ def create(self, values_list):
+ tracking_values_list = []
+ for values in values_list:
+ if 'email_from' not in values: # needed to compute reply_to
+ values['email_from'] = self._get_default_from()
+ if not values.get('message_id'):
+ values['message_id'] = self._get_message_id(values)
+ if 'reply_to' not in values:
+ values['reply_to'] = self._get_reply_to(values)
+ if 'record_name' not in values and 'default_record_name' not in self.env.context:
+ values['record_name'] = self._get_record_name(values)
+
+ if 'attachment_ids' not in values:
+ values['attachment_ids'] = []
+ # extract base64 images
+ if 'body' in values:
+ Attachments = self.env['ir.attachment']
+ data_to_url = {}
+ def base64_to_boundary(match):
+ key = match.group(2)
+ if not data_to_url.get(key):
+ name = match.group(4) if match.group(4) else 'image%s' % len(data_to_url)
+ try:
+ attachment = Attachments.create({
+ 'name': name,
+ 'datas': match.group(2),
+ 'res_model': values.get('model'),
+ 'res_id': values.get('res_id'),
+ })
+ except binascii_error:
+ _logger.warning("Impossible to create an attachment out of badly formated base64 embedded image. Image has been removed.")
+ return match.group(3) # group(3) is the url ending single/double quote matched by the regexp
+ else:
+ attachment.generate_access_token()
+ values['attachment_ids'].append((4, attachment.id))
+ data_to_url[key] = ['/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token), name]
+ return '%s%s alt="%s"' % (data_to_url[key][0], match.group(3), data_to_url[key][1])
+ values['body'] = _image_dataurl.sub(base64_to_boundary, tools.ustr(values['body']))
+
+ # delegate creation of tracking after the create as sudo to avoid access rights issues
+ tracking_values_list.append(values.pop('tracking_value_ids', False))
+
+ messages = super(Message, self).create(values_list)
+
+ check_attachment_access = []
+ if all(isinstance(command, int) or command[0] in (4, 6) for values in values_list for command in values.get('attachment_ids')):
+ for values in values_list:
for command in values.get('attachment_ids'):
if isinstance(command, int):
- attachment_ids += [command]
+ check_attachment_access += [command]
elif command[0] == 6:
- attachment_ids += command[2]
- else: # command[0] == 4:
- attachment_ids += [command[1]]
- else:
- attachment_ids = message.mapped('attachment_ids').ids # fallback on read if any unknow command
- self.env['ir.attachment'].browse(attachment_ids).check(mode='read')
-
- if tracking_values_cmd:
- vals_lst = [dict(cmd[2], mail_message_id=message.id) for cmd in tracking_values_cmd if len(cmd) == 3 and cmd[0] == 0]
- other_cmd = [cmd for cmd in tracking_values_cmd if len(cmd) != 3 or cmd[0] != 0]
- if vals_lst:
- self.env['mail.tracking.value'].sudo().create(vals_lst)
- if other_cmd:
- message.sudo().write({'tracking_value_ids': tracking_values_cmd})
-
- if message.is_thread_message(values):
- message._invalidate_documents(values.get('model'), values.get('res_id'))
+ check_attachment_access += command[2]
+ else: # command[0] == 4:
+ check_attachment_access += [command[1]]
+ else:
+ check_attachment_access = messages.mapped('attachment_ids').ids # fallback on read if any unknow command
+ if check_attachment_access:
+ self.env['ir.attachment'].browse(check_attachment_access).check(mode='read')
+
+ for message, values, tracking_values_cmd in zip(messages, values_list, tracking_values_list):
+ if tracking_values_cmd:
+ vals_lst = [dict(cmd[2], mail_message_id=message.id) for cmd in tracking_values_cmd if len(cmd) == 3 and cmd[0] == 0]
+ other_cmd = [cmd for cmd in tracking_values_cmd if len(cmd) != 3 or cmd[0] != 0]
+ if vals_lst:
+ self.env['mail.tracking.value'].sudo().create(vals_lst)
+ if other_cmd:
+ message.sudo().write({'tracking_value_ids': tracking_values_cmd})
+
+ if message.is_thread_message(values):
+ message._invalidate_documents(values.get('model'), values.get('res_id'))
return message
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index bdfd821967c..701690039ca 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -1699,7 +1699,7 @@ class MailThread(models.AbstractModel):
@api.returns('mail.message', lambda value: value.id)
def message_post(self,
body='', subject=None, message_type='notification',
- email_from=False, author_id=None, parent_id=False,
+ email_from=None, author_id=None, parent_id=False,
subtype_id=False, subtype=None, partner_ids=None, channel_ids=None,
attachments=None, attachment_ids=None,
add_sign=True, record_name=False,
@@ -1750,10 +1750,8 @@ class MailThread(models.AbstractModel):
raise ValueError('message_post partner_ids and channel_ids must be integer list, not commands')
# Find the message's author
- if author_id is None: # keep False values
- author_id = self.env.user.partner_id.id
- if not email_from:
- email_from = self.env['res.partner'].browse(author_id).sudo().email_formatted if author_id else self.env['mail.message']._get_default_from()
+ author_info = self._message_compute_author(author_id, email_from, raise_exception=True)
+ author_id, email_from = author_info['author_id'], author_info['email_from']
if not subtype_id:
subtype = subtype or 'mt_note'
@@ -1786,6 +1784,7 @@ class MailThread(models.AbstractModel):
values = dict(msg_kwargs)
values.update({
'author_id': author_id,
+ 'email_from': email_from,
'model': self._name,
'res_id': self.id,
'body': body,
@@ -1797,7 +1796,6 @@ class MailThread(models.AbstractModel):
'channel_ids': channel_ids,
'add_sign': add_sign,
'record_name': record_name,
- 'email_from': email_from,
})
attachments = attachments or []
attachment_ids = attachment_ids or []
@@ -1887,7 +1885,7 @@ class MailThread(models.AbstractModel):
return composer.send_mail()
def message_notify(self, partner_ids=False, parent_id=False, model=False, res_id=False,
- author_id=False, body='', subject=False, **kwargs):
+ author_id=None, email_from=None, body='', subject=False, **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. """
@@ -1897,16 +1895,8 @@ class MailThread(models.AbstractModel):
msg_kwargs = dict((key, val) for key, val in kwargs.items() if key in self.env['mail.message']._fields)
notif_kwargs = dict((key, val) for key, val in kwargs.items() if key not in msg_kwargs)
- if author_id:
- author = self.env['res.partner'].sudo().browse(author_id)
- else:
- author = self.env.user.partner_id
-
- if not author.email:
- raise exceptions.UserError(_("Unable to notify message, please configure the sender's email address."))
- email_from = author.email_formatted
-
- partner_ids = partner_ids or set()
+ author_info = self._message_compute_author(author_id, email_from, raise_exception=True)
+ author_id, email_from = author_info['author_id'], author_info['email_from']
if not partner_ids:
_logger.warning('Message notify called without recipient_ids, skipping')
@@ -1924,7 +1914,7 @@ class MailThread(models.AbstractModel):
'message_type': 'user_notification',
'subject': subject,
'body': body,
- 'author_id': author.id,
+ 'author_id': author_id,
'email_from': email_from,
'partner_ids': partner_ids,
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
@@ -1937,7 +1927,7 @@ class MailThread(models.AbstractModel):
MailThread._notify_thread(new_message, values, **notif_kwargs)
return new_message
- def _message_log(self, body='', author_id=None, subject=False, message_type='notification', **kwargs):
+ def _message_log(self, body='', author_id=None, email_from=None, subject=False, message_type='notification', **kwargs):
""" 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
@@ -1945,24 +1935,13 @@ class MailThread(models.AbstractModel):
the log process. This method should be called within methods where
access rights are already granted to avoid privilege escalation. """
self.ensure_one()
- if author_id:
- author = self.env['res.partner'].sudo().browse(author_id)
- else:
- author = self.env.user.partner_id
- author_id = author.id
-
- if author.email:
- email_from = author.email_formatted
- elif self.env.su:
- # superuser mode without author email -> probably public user; anyway we don't want to crash
- email_from = False
- else:
- raise exceptions.UserError(_("Unable to log message, please configure the sender's email address."))
+ author_info = self._message_compute_author(author_id, email_from, raise_exception=False)
+ author_id, email_from = author_info['author_id'], author_info['email_from']
message_values = {
'subject': subject,
'body': body,
- 'author_id': author.id,
+ 'author_id': author_id,
'email_from': email_from,
'message_type': message_type,
'model': kwargs.get('model', self._name),
@@ -1973,14 +1952,73 @@ class MailThread(models.AbstractModel):
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
}
message_values.update(kwargs)
- message = self.sudo()._message_create(message_values)
- return message
-
- def _message_create(self, values):
- create_values = dict(values)
- create_values['partner_ids'] = [(4, pid) for pid in create_values.get('partner_ids', [])]
- create_values['channel_ids'] = [(4, cid) for cid in create_values.get('channel_ids', [])]
- return self.env['mail.message'].create(create_values)
+ return self.sudo()._message_create(message_values)
+
+ def _message_log_batch(self, bodies, author_id=None, email_from=None, subject=False, message_type='notification'):
+ """ Shortcut allowing to post notes on a batch of documents. It achieve the
+ same purpose as _message_log, done in batch to speedup quick note log.
+
+ :param bodies: dict {record_id: body}
+ """
+ author_info = self._message_compute_author(author_id, email_from, raise_exception=False)
+ author_id, email_from = author_info['author_id'], author_info['email_from']
+
+ base_message_values = {
+ 'subject': subject,
+ 'author_id': author_id,
+ 'email_from': email_from,
+ 'message_type': message_type,
+ 'model': self._name,
+ 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
+ 'record_name': False,
+ 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False],
+ 'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
+ }
+ values_list = [dict(base_message_values,
+ res_id=record.id,
+ body=bodies.get(record.id, ''))
+ for record in self]
+ return self.sudo()._message_create(values_list)
+
+ def _message_compute_author(self, author_id=None, email_from=None, raise_exception=True):
+ """ Tool method computing author information for messages. Purpose is
+ to ensure maximum coherence between author / current user / email_from
+ when sending emails. """
+ if author_id is None:
+ if email_from:
+ author = self._mail_find_partner_from_emails([email_from])[0]
+ else:
+ author = self.env.user.partner_id
+ email_from = author.email_formatted
+ author_id = author.id
+
+ if email_from is None:
+ if author_id:
+ author = self.env['res.partner'].browse(author_id)
+ email_from = author.email_formatted
+
+ # superuser mode without author email -> probably public user; anyway we don't want to crash
+ if not email_from and not self.env.su and raise_exception:
+ raise exceptions.UserError(_("Unable to log message, please configure the sender's email address."))
+
+ return {
+ 'author_id': author_id,
+ 'email_from': email_from,
+ }
+
+ def _message_create(self, values_list):
+ if not isinstance(values_list, (list)):
+ values_list = [values_list]
+ create_values_list = []
+ for values in values_list:
+ create_values = dict(values)
+ # Avoid warnings about non-existing fields
+ for x in ('from', 'to', 'cc', 'canned_response_ids'):
+ create_values.pop(x, None)
+ create_values['partner_ids'] = [(4, pid) for pid in create_values.get('partner_ids', [])]
+ create_values['channel_ids'] = [(4, cid) for cid in create_values.get('channel_ids', [])]
+ create_values_list.append(create_values)
+ return self.env['mail.message'].create(create_values_list)
# ------------------------------------------------------
# Notification API