[MOV] mail: move method to process attachments

Purpose is to make a section that will cover pre- and post- process methods
for posting or mailing records.

Task-2710804 (Mail: Clean MailThread API)

Part-of: odoo/odoo#106658
This commit is contained in:
Thibault Delavallée
2022-11-28 15:52:58 +01:00
parent 49977c1116
commit f1bb452ce4
+115 -115
View File
@@ -1777,121 +1777,6 @@ class MailThread(models.AbstractModel):
# MESSAGE POST MAIN
# ------------------------------------------------------------
def _message_post_process_attachments(self, attachments, attachment_ids, message_values):
""" Preprocess attachments for mail_thread.message_post() or mail_mail.create().
Purpose is to
* transfer attachments given by ``attachment_ids`` from the composer to
the record (if any);
* limit attachments manipulation when being a shared user;
* create attachments from ``attachments``. If those are linked to the
content (body) through CIDs body is updated accordingly;
:param list(tuple(str,str), tuple(str,str, dict)) attachments : list of attachment
tuples in the form ``(name,content)`` or ``(name,content, info)`` where content
is NOT base64 encoded;
:param list attachment_ids: list of existing attachments to link to this message;
:param message_values: dictionary of values that will be used to create the
message. It is used to find back record- or content- context;
:return dict: new values for message: 'attachment_ids' and optionally 'body'
"""
return_values = {}
body = message_values.get('body')
model = message_values['model']
res_id = message_values['res_id']
m2m_attachment_ids = []
if attachment_ids:
# taking advantage of cache looks better in this case, to check
filtered_attachment_ids = self.env['ir.attachment'].sudo().browse(attachment_ids).filtered(
lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid)
# update filtered (pending) attachments to link them to the proper record
if filtered_attachment_ids:
filtered_attachment_ids.write({'res_model': model, 'res_id': res_id})
# prevent public and portal users from using attachments that are not theirs
if not self.env.user._is_internal():
attachment_ids = filtered_attachment_ids.ids
m2m_attachment_ids += [Command.link(id) for id in attachment_ids]
# Handle attachments parameter, that is a dictionary of attachments
if attachments: # generate
cids_in_body = set()
names_in_body = set()
cid_list = []
name_list = []
if body:
root = lxml.html.fromstring(tools.ustr(body))
# first list all attachments that will be needed in body
for node in root.iter('img'):
if node.get('src', '').startswith('cid:'):
cids_in_body.add(node.get('src').split('cid:')[1])
elif node.get('data-filename'):
names_in_body.add(node.get('data-filename'))
attachement_values_list = []
# generate values
for attachment in attachments:
cid = False
if len(attachment) == 2:
name, content = attachment
elif len(attachment) == 3:
name, content, info = attachment
cid = info and info.get('cid')
else:
continue
if isinstance(content, str):
content = content.encode('utf-8')
elif isinstance(content, EmailMessage):
content = content.as_bytes()
elif content is None:
continue
attachement_values = {
'name': name,
'datas': base64.b64encode(content),
'type': 'binary',
'description': name,
'res_model': model,
'res_id': res_id,
}
if body and (cid and cid in cids_in_body or name in names_in_body):
attachement_values['access_token'] = self.env['ir.attachment']._generate_access_token()
attachement_values_list.append(attachement_values)
# keep cid and name list synced with attachement_values_list length to match ids latter
cid_list.append(cid)
name_list.append(name)
new_attachments = self.env['ir.attachment'].create(attachement_values_list)
cid_mapping = {}
name_mapping = {}
for counter, new_attachment in enumerate(new_attachments):
cid = cid_list[counter]
if 'access_token' in attachement_values_list[counter]:
if cid:
cid_mapping[cid] = (new_attachment.id, attachement_values_list[counter]['access_token'])
name = name_list[counter]
name_mapping[name] = (new_attachment.id, attachement_values_list[counter]['access_token'])
m2m_attachment_ids.append((4, new_attachment.id))
# note: right know we are only taking attachments and ignoring attachment_ids.
if (cid_mapping or name_mapping) and body:
postprocessed = False
for node in root.iter('img'):
attachment_data = False
if node.get('src', '').startswith('cid:'):
cid = node.get('src').split('cid:')[1]
attachment_data = cid_mapping.get(cid)
if not attachment_data and node.get('data-filename'):
attachment_data = name_mapping.get(node.get('data-filename'), False)
if attachment_data:
node.set('src', '/web/image/%s?access_token=%s' % attachment_data)
postprocessed = True
if postprocessed:
return_values['body'] = lxml.html.tostring(root, pretty_print=False, encoding='UTF-8')
return_values['attachment_ids'] = m2m_attachment_ids
return return_values
@api.returns('mail.message', lambda value: value.id)
def message_post(self, *,
body='', subject=None, message_type='notification',
@@ -2021,6 +1906,121 @@ class MailThread(models.AbstractModel):
message and computed value are given, to try to lessen query count by
using already-computed values instead of having to rebrowse things. """
def _message_post_process_attachments(self, attachments, attachment_ids, message_values):
""" Preprocess attachments for mail_thread.message_post() or mail_mail.create().
Purpose is to
* transfer attachments given by ``attachment_ids`` from the composer to
the record (if any);
* limit attachments manipulation when being a shared user;
* create attachments from ``attachments``. If those are linked to the
content (body) through CIDs body is updated accordingly;
:param list(tuple(str,str), tuple(str,str, dict)) attachments : list of attachment
tuples in the form ``(name,content)`` or ``(name,content, info)`` where content
is NOT base64 encoded;
:param list attachment_ids: list of existing attachments to link to this message;
:param message_values: dictionary of values that will be used to create the
message. It is used to find back record- or content- context;
:return dict: new values for message: 'attachment_ids' and optionally 'body'
"""
return_values = {}
body = message_values.get('body')
model = message_values['model']
res_id = message_values['res_id']
m2m_attachment_ids = []
if attachment_ids:
# taking advantage of cache looks better in this case, to check
filtered_attachment_ids = self.env['ir.attachment'].sudo().browse(attachment_ids).filtered(
lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid)
# update filtered (pending) attachments to link them to the proper record
if filtered_attachment_ids:
filtered_attachment_ids.write({'res_model': model, 'res_id': res_id})
# prevent public and portal users from using attachments that are not theirs
if not self.env.user._is_internal():
attachment_ids = filtered_attachment_ids.ids
m2m_attachment_ids += [Command.link(id) for id in attachment_ids]
# Handle attachments parameter, that is a dictionary of attachments
if attachments: # generate
cids_in_body = set()
names_in_body = set()
cid_list = []
name_list = []
if body:
root = lxml.html.fromstring(tools.ustr(body))
# first list all attachments that will be needed in body
for node in root.iter('img'):
if node.get('src', '').startswith('cid:'):
cids_in_body.add(node.get('src').split('cid:')[1])
elif node.get('data-filename'):
names_in_body.add(node.get('data-filename'))
attachement_values_list = []
# generate values
for attachment in attachments:
cid = False
if len(attachment) == 2:
name, content = attachment
elif len(attachment) == 3:
name, content, info = attachment
cid = info and info.get('cid')
else:
continue
if isinstance(content, str):
content = content.encode('utf-8')
elif isinstance(content, EmailMessage):
content = content.as_bytes()
elif content is None:
continue
attachement_values = {
'name': name,
'datas': base64.b64encode(content),
'type': 'binary',
'description': name,
'res_model': model,
'res_id': res_id,
}
if body and (cid and cid in cids_in_body or name in names_in_body):
attachement_values['access_token'] = self.env['ir.attachment']._generate_access_token()
attachement_values_list.append(attachement_values)
# keep cid and name list synced with attachement_values_list length to match ids latter
cid_list.append(cid)
name_list.append(name)
new_attachments = self.env['ir.attachment'].create(attachement_values_list)
cid_mapping = {}
name_mapping = {}
for counter, new_attachment in enumerate(new_attachments):
cid = cid_list[counter]
if 'access_token' in attachement_values_list[counter]:
if cid:
cid_mapping[cid] = (new_attachment.id, attachement_values_list[counter]['access_token'])
name = name_list[counter]
name_mapping[name] = (new_attachment.id, attachement_values_list[counter]['access_token'])
m2m_attachment_ids.append((4, new_attachment.id))
# note: right know we are only taking attachments and ignoring attachment_ids.
if (cid_mapping or name_mapping) and body:
postprocessed = False
for node in root.iter('img'):
attachment_data = False
if node.get('src', '').startswith('cid:'):
cid = node.get('src').split('cid:')[1]
attachment_data = cid_mapping.get(cid)
if not attachment_data and node.get('data-filename'):
attachment_data = name_mapping.get(node.get('data-filename'), False)
if attachment_data:
node.set('src', '/web/image/%s?access_token=%s' % attachment_data)
postprocessed = True
if postprocessed:
return_values['body'] = lxml.html.tostring(root, pretty_print=False, encoding='UTF-8')
return_values['attachment_ids'] = m2m_attachment_ids
return return_values
# ------------------------------------------------------------
# MESSAGE POST API / WRAPPERS
# ------------------------------------------------------------