[FIX] mail: message_process now handles replies to messages without model, thread_id. Based on in-reply-to, it finds the parent message. mail_thread.message_post_user_api is called to create a new mail.message, bypassing message_new / message_update in this case. Some improvements added to partner_ids when using the chatter or message processing.

bzr revid: tde@openerp.com-20121108152502-aow7vhu4erx7fb9l
This commit is contained in:
Thibault Delavallée 2012-11-08 16:25:02 +01:00
parent 72f218bd24
commit d5d7dced4d
2 changed files with 74 additions and 38 deletions

View File

@ -319,10 +319,12 @@ class mail_thread(osv.AbstractModel):
""" """
assert isinstance(message, Message), 'message must be an email.message.Message at this point' assert isinstance(message, Message), 'message must be an email.message.Message at this point'
message_id = message.get('Message-Id') message_id = message.get('Message-Id')
references = decode_header(message, 'References')
in_reply_to = decode_header(message, 'In-Reply-To')
# 1. Verify if this is a reply to an existing thread # 1. Verify if this is a reply to an existing thread
references = decode_header(message, 'References') or decode_header(message, 'In-Reply-To') thread_references = references or in_reply_to
ref_match = references and tools.reference_re.search(references) ref_match = thread_references and tools.reference_re.search(thread_references)
if ref_match: if ref_match:
thread_id = int(ref_match.group(1)) thread_id = int(ref_match.group(1))
model = ref_match.group(2) or model model = ref_match.group(2) or model
@ -333,6 +335,15 @@ class mail_thread(osv.AbstractModel):
message_id, model, thread_id, custom_values, uid) message_id, model, thread_id, custom_values, uid)
return [(model, thread_id, custom_values, uid)] return [(model, thread_id, custom_values, uid)]
# Verify this is a reply to a private message
message_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'ilike', in_reply_to)], limit=1, context=context)
if message_ids:
message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
_logger.debug('Routing mail with Message-Id %s: reply to a private message: %s, custom_values: %s, uid: %s',
message_id, message.id, custom_values, uid)
return [(False, 0, custom_values, uid)]
# 2. Look for a matching mail.alias entry # 2. Look for a matching mail.alias entry
# Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
# for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value. # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
@ -376,14 +387,19 @@ class mail_thread(osv.AbstractModel):
def message_process(self, cr, uid, model, message, custom_values=None, def message_process(self, cr, uid, model, message, custom_values=None,
save_original=False, strip_attachments=False, save_original=False, strip_attachments=False,
thread_id=None, context=None): thread_id=None, context=None):
"""Process an incoming RFC2822 email message, relying on """ Process an incoming RFC2822 email message, relying on
``mail.message.parse()`` for the parsing operation, ``mail.message.parse()`` for the parsing operation,
and ``message_route()`` to figure out the target model. and ``message_route()`` to figure out the target model.
Once the target model is known, its ``message_new`` method Once the target model is known, its ``message_new`` method
is called with the new message (if the thread record did not exist) is called with the new message (if the thread record did not exist)
or its ``message_update`` method (if it did). or its ``message_update`` method (if it did).
There is a special case where the target model is False: a reply
to a private message. In this case, we skip the message_new /
message_update step, to just post a new message using mail_thread
message_post.
:param string model: the fallback model to use if the message :param string model: the fallback model to use if the message
does not match any of the currently configured mail aliases does not match any of the currently configured mail aliases
(may be None if a matching alias is supposed to be present) (may be None if a matching alias is supposed to be present)
@ -425,15 +441,18 @@ class mail_thread(osv.AbstractModel):
for model, thread_id, custom_values, user_id in routes: for model, thread_id, custom_values, user_id in routes:
if self._name != model: if self._name != model:
context.update({'thread_model': model}) context.update({'thread_model': model})
model_pool = self.pool.get(model) if model:
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \ model_pool = self.pool.get(model)
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \ assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
(msg['message_id'], model) "Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
if thread_id and hasattr(model_pool, 'message_update'): (msg['message_id'], model)
model_pool.message_update(cr, user_id, [thread_id], msg, context=context) if thread_id and hasattr(model_pool, 'message_update'):
model_pool.message_update(cr, user_id, [thread_id], msg, context=context)
else:
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context)
else: else:
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context) model_pool = self.pool.get('mail.thread')
model_pool.message_post(cr, uid, [thread_id], context=context, **msg) model_pool.message_post_user_api(cr, uid, [thread_id], context=context, **msg)
return thread_id return thread_id
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None): def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
@ -556,7 +575,6 @@ class mail_thread(osv.AbstractModel):
""" """
msg_dict = { msg_dict = {
'type': 'email', 'type': 'email',
'subtype': 'mail.mt_comment',
'author_id': False, 'author_id': False,
} }
if not isinstance(message, Message): if not isinstance(message, Message):
@ -640,10 +658,13 @@ class mail_thread(osv.AbstractModel):
``(name,content)``, where content is NOT base64 encoded ``(name,content)``, where content is NOT base64 encoded
:return: ID of newly created mail.message :return: ID of newly created mail.message
""" """
context = context or {} if context is None:
attachments = attachments or [] context = {}
if attachments is None:
attachments = {}
assert (not thread_id) or isinstance(thread_id, (int, long)) or \ assert (not thread_id) or isinstance(thread_id, (int, long)) or \
(isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id" (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id; should be 0, False, an ID or a list with one ID"
if isinstance(thread_id, (list, tuple)): if isinstance(thread_id, (list, tuple)):
thread_id = thread_id and thread_id[0] thread_id = thread_id and thread_id[0]
mail_message = self.pool.get('mail.message') mail_message = self.pool.get('mail.message')
@ -707,30 +728,45 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context) return mail_message.create(cr, uid, values, context=context)
def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None): def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
""" Wrapper on message_post, used only in Chatter (JS). The purpose is attachment_ids=None, context=None, content_subtype='plaintext', **kwargs):
to handle attachments. """ Wrapper on message_post, used for user input :
- body is plaintext: convert it into html - mail gateway
- handle reply to a previous message - quick reply in Chatter (refer to mail.js), not
the mail.compose.message wizard
The purpose is to perform some pre- and post-processing:
- if body is plaintext: convert it into html
- if parent_id: handle reply to a previous message by adding the
parent partners to the message
- type and subtype: comment and mail.mt_comment by default
- attachment_ids: supposed not attached to any document; attach them
to the related document. Should only be set by Chatter.
""" """
# 1. handle body ir_attachment = self.pool.get('ir.attachment')
body = tools.text2html(body) mail_message = self.pool.get('mail.message')
# 2. handle message partner_ids # 1. Pre-processing: body, partner_ids, type and subtype
if content_subtype == 'plaintext':
body = tools.text2html(body)
partner_ids = kwargs.pop('partner_ids', [])
if parent_id: if parent_id:
parent_data = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context) parent_message = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context)
partner_ids = [(4, partner.id) for partner in parent_data.partner_ids] partner_ids += [(4, partner.id) for partner in parent_message.partner_ids]
else: # TDE FIXME HACK: mail.thread -> private message
partner_ids = [] if self._name == 'mail.thread' and parent_message.author_id.id:
partner_ids.append((4, parent_message.author_id.id))
# 3. post message message_type = kwargs.pop('type', 'comment')
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment', message_subtype = kwargs.pop('type', 'mail.mt_comment')
subtype='mail.mt_comment', parent_id=parent_id, context=context, partner_ids=partner_ids)
# 4. HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message # 2. Post message
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type,
subtype=message_subtype, parent_id=parent_id, context=context, partner_ids=partner_ids, **kwargs)
# 3. Post-processing
# HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
if attachment_ids: if attachment_ids:
ir_attachment = self.pool.get('ir.attachment')
mail_message = self.pool.get('mail.message')
filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [ filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
('res_model', '=', 'mail.compose.message'), ('res_model', '=', 'mail.compose.message'),
('res_id', '=', 0), ('res_id', '=', 0),

View File

@ -350,7 +350,7 @@ openerp.mail = function (session) {
if (body.match(/\S+/)) { if (body.match(/\S+/)) {
//session.web.blockUI(); //session.web.blockUI();
this.parent_thread.ds_thread.call('message_post_api', [ this.parent_thread.ds_thread.call('message_post_user_api', [
this.context.default_res_id, this.context.default_res_id,
mail.ChatterUtils.get_text2html(body), mail.ChatterUtils.get_text2html(body),
false, false,