[FIX] mail: make message_process/parse work, restore attachment_ids m2m on mail.message, various lint cleanup

bzr revid: odo@openerp.com-20120823185443-zsnm6g14r6e42y3i
This commit is contained in:
Olivier Dony
2012-08-23 20:54:43 +02:00
parent 95a66586d9
commit 2c4ed841b9
3 changed files with 103 additions and 128 deletions
-2
View File
@@ -165,13 +165,11 @@ class hr_applicant(base_stage, osv.Model):
date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
ans = date_open - date_create
date_until = issue.date_open
elif field in ['day_close']:
if issue.date_closed:
date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
date_until = issue.date_closed
ans = date_close - date_create
if ans:
duration = float(ans.days)
+8 -25
View File
@@ -19,16 +19,10 @@
#
##############################################################################
import dateutil.parser
import email
import logging
import re
import time
from email.header import decode_header
from email.message import Message
from osv import osv
from osv import fields
from osv import osv, fields
import tools
_logger = logging.getLogger(__name__)
@@ -84,31 +78,22 @@ class mail_message(osv.Model):
('notification', 'System notification'),
], 'Type',
help="Message type: email for email message, notification for system "\
"message, comment for other messages such as user replies"),
"message, comment for other messages such as user replies"),
'author_id': fields.many2one('res.partner', 'Author', required=True),
'partner_ids': fields.many2many('res.partner',
'mail_notification', 'message_id', 'partner_id',
'Recipients'),
'attachment_ids': fields.one2many('ir.attachment', 'res_id',
'Attachments', domain=[('res_model','=','mail.message')]),
'parent_id': fields.many2one('mail.message', 'Parent Message',
select=True, ondelete='set null',
help="Initial thread message."),
'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id', 'Attachments'),
'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
'model': fields.char('Related Document Model', size=128, select=1),
'res_id': fields.integer('Related Document ID', select=1),
'record_name': fields.function(get_record_name, type='string',
string='Message Record Name',
help="Name get of the related document."),
'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
'subject': fields.char('Subject', size=128),
'subject': fields.char('Subject'),
'date': fields.datetime('Date'),
'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1),
'body': fields.html('Content'),
}
@@ -118,7 +103,6 @@ class mail_message(osv.Model):
return [('notification_ids.partner_id','=',partner_id),('notification_ids.read','=',False)]
return []
def _get_default_author(self, cr, uid, context={}):
return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
@@ -233,7 +217,6 @@ class mail_message(osv.Model):
('partner_id', '=', partner_id),
('message_id', 'in', ids),
], context=context)
notifications = {}
for notification in not_obj.browse(cr, uid, not_ids, context=context):
if notification.message_id.id in ids:
pass
+95 -101
View File
@@ -19,19 +19,23 @@
#
##############################################################################
import base64
import dateutil
import email
import logging
import pytz
import re
import time
import xmlrpclib
from email.utils import parsedate
from email.message import Message
from osv import osv, fields
from mail_message import decode
import time
import tools
from mail_message import decode
from osv import osv, fields
from tools.translate import _
from tools.safe_eval import safe_eval as eval
import pytz
import xmlrpclib
_logger = logging.getLogger(__name__)
@@ -372,9 +376,8 @@ class mail_thread(osv.Model):
routes = self.message_route(cr, uid, msg_txt, model,
thread_id, custom_values,
context=context)
msg = self.parse_message(cr, uid, msg_txt, save_original=save_original, context=context)
if strip_attachments and 'attachments' in msg:
del msg['attachments']
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
if strip_attachments: msg.pop('attachments', None)
for model, thread_id, custom_values, user_id in routes:
if self._name != model:
context.update({'thread_model': model})
@@ -445,7 +448,50 @@ class mail_thread(osv.Model):
self.write(cr, uid, ids, update_vals, context=context)
return True
def parse_message(self, cr, uid, message, save_original=False, context=None):
def _message_extract_payload(self, message, save_original=False):
"""Extract body as HTML and attachments from the mail message"""
attachments = []
body = u''
if save_original:
attachments.append(('original_email.eml', message.as_string()))
if not message.is_multipart() or 'text/' in message.get('content-type', ''):
encoding = message.get_content_charset()
body = message.get_payload(decode=True)
body = tools.ustr(body, encoding, errors='replace')
else:
alternative = (message.get_content_type() == 'multipart/alternative')
for part in message.walk():
if part.get_content_maintype() == 'multipart':
continue # skip container
filename = part.get_filename() # None if normal part
encoding = part.get_content_charset() # None if attachment
# 1) Explicit Attachments -> attachments
if filename or part.get('content-disposition','').strip().startswith('attachment'):
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
continue
# 2) text/plain -> <pre/>
if part.get_content_type() == 'text/plain' and (not alternative or not body):
insertion_point = body.find('</html>')
# plain text is wrapped in <pre/> to preserve formatting
text = u'<pre>%s</pre>' % tools.ustr(part.get_payload(decode=True),
encoding, errors='replace')
body = body[:insertion_point] + text + body[:insertion_point]
# 3) text/html -> raw
elif part.get_content_type() == 'text/html':
html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace')
if alternative:
body = html
else:
html = re.sub('(?i)</?html>', '', html)
insertion_point = body.find('</html>')
body = body[:insertion_point] + text + body[:insertion_point]
# 4) Anything else -> attachment
else:
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
return body, attachments
def message_parse(self, cr, uid, message, save_original=False, context=None):
"""Parses a string or email.message.Message representing an
RFC-2822 email, and returns a generic dict holding the
message details.
@@ -453,8 +499,8 @@ class mail_thread(osv.Model):
:param message: the message to parse
:type message: email.message.Message | string | unicode
:param bool save_original: whether the returned dict
should include an ``original`` entry with the base64
encoded source of the message.
should include an ``original`` attachment containing
the source of the message
:rtype: dict
:return: A dict with the following structure, where each
field may not be present if missing in original
@@ -462,113 +508,58 @@ class mail_thread(osv.Model):
{ 'message-id': msg_id,
'subject': subject,
'from': from, --> author_id
'to': to, --> partner_ids
'cc': cc, --> partner_ids
'headers' : { 'X-Mailer': mailer, --> to remove
#.. all X- headers...
},
'content_subtype': msg_mime_subtype, --> to remove
'body': plaintext_body --> keep body
'from': from,
'to': to,
'cc': cc,
'body': unified_body,
'body_html': html_body, --> to remove
'attachments': [('file1', 'bytes'),
('file2', 'bytes') }
# ...
'original': source_of_email, --> attachment document
('file2', 'bytes')}
}
"""
msg_txt = message
attachments = []
if isinstance(message, str):
msg_txt = email.message_from_string(message)
# Warning: message_from_string doesn't always work correctly on unicode,
# we must use utf-8 strings here :-(
if isinstance(message, unicode):
message = message.encode('utf-8')
msg_txt = email.message_from_string(message)
message_id = msg_txt.get('message-id', False)
msg = {}
if save_original:
msg_original = message.as_string() if isinstance(message, Message) \
else message
attachments.append(('original_email.eml', msg_original))
msg_dict = {}
if not isinstance(message, Message):
if isinstance(message, unicode):
# Warning: message_from_string doesn't always work correctly on unicode,
# we must use utf-8 strings here :-(
message = message.encode('utf-8')
message = email.message_from_string(message)
message_id = message['message-id']
if not message_id:
# Very unusual situation, be we should be fault-tolerant here
message_id = time.time()
msg_txt['message-id'] = message_id
_logger.info('Parsing Message without message-id, generating a random one: %s', message_id)
message_id = "<%s@localhost>" % time.time()
_logger.debug('Parsing Message without message-id, generating a random one: %s', message_id)
msg_dict['message_id'] = message_id
msg_fields = msg_txt.keys()
if 'Subject' in message:
msg_dict['subject'] = decode(message.get('Subject'))
msg['message_id'] = message_id
# Envelope fields not stored in mail.message but made available for message_new()
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
if 'Subject' in msg_fields:
msg['subject'] = decode(msg_txt.get('Subject'))
#if 'Content-Type' in msg_fields:
# msg['content-type'] = msg_txt.get('Content-Type')
# find author_id
if 'From' in msg_fields:
author_ids = self._message_find_partners(cr, uid, msg_txt, ['From'], context=context)
#decode(msg_txt.get('From') or msg_txt.get_unixfrom()) )
if 'From' in message:
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg['author_id'] = author_ids[0]
msg_dict['author_id'] = author_ids[0]
partner_ids = self._message_find_partners(cr, uid, message, ['From','To','Cc'], context=context)
msg_dict['partner_ids'] = partner_ids
partner_ids = self._message_find_partners(cr, uid, msg_txt, ['From','To','Delivered-To','CC','Cc'], context=context)
msg['partner_ids'] = partner_ids
if 'Date' in msg_fields:
date_hdr = decode(msg_txt.get('Date'))
if 'Date' in message:
date_hdr = decode(message.get('Date'))
# convert from email timezone to server timezone
date_server_datetime = dateutil.parser.parse(date_hdr).astimezone(pytz.timezone(tools.get_server_timezone()))
date_server_datetime_str = date_server_datetime.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
msg['date'] = date_server_datetime_str
#if 'Content-Transfer-Encoding' in msg_fields:
# msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
#if 'References' in msg_fields:
# msg['references'] = msg_txt.get('References')
msg_dict['date'] = date_server_datetime_str
# FP Note: todo - find parent_id
if 'In-Reply-To' in msg_fields:
if 'In-Reply-To' in message:
pass
if not msg_txt.is_multipart() or 'text/plain' in msg.get('content-type', ''):
encoding = msg_txt.get_content_charset()
body = msg_txt.get_payload(decode=True)
if 'text/html' in msg.get('content-type', ''):
msg['body'] = body
if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
body = ""
for part in msg_txt.walk():
if part.get_content_maintype() == 'multipart':
continue
encoding = part.get_content_charset()
filename = part.get_filename()
if part.get_content_maintype()=='text':
content = part.get_payload(decode=True)
if filename:
attachments.append(( filename, msg_original))
content = tools.ustr(content, encoding)
msg['body'] = content
elif part.get_content_maintype() in ('application', 'image'):
if filename:
attachments.append(( filename, part.get_payload(decode=True)))
else:
res = part.get_payload(decode=True)
msg['body'] += tools.ustr(res, encoding)
msg['attachments'] = attachments
return msg
msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message)
return msg_dict
#------------------------------------------------------
# Note specific
@@ -610,9 +601,11 @@ class mail_thread(osv.Model):
content = content.encode('utf-8')
data_attach = {
'name': name,
'datas': content,
'datas': base64.b64encode(str(content)),
'datas_fname': name,
'description': name,
'res_model': context.get('thread_model') or self._name,
'res_id': thread_id,
}
to_attach.append((0,0, data_attach))
@@ -626,6 +619,7 @@ class mail_thread(osv.Model):
'parent_id': parent_id,
'attachment_ids': to_attach
})
for x in ('from','to','cc'): values.pop(x, None) # Avoid warnings
return self.pool.get('mail.message').create(cr, uid, values, context=context)