[IMP] mail*: mail.message.original: binary field and optional

bzr revid: odo@openerp.com-20110907151348-jiqfqm5cagy8bzto
This commit is contained in:
Olivier Dony
2011-09-07 17:13:48 +02:00
parent 9e1acb0c83
commit 7c7c8a0348
4 changed files with 49 additions and 26 deletions
+12 -6
View File
@@ -26,6 +26,7 @@ import logging
import re
import time
from email.header import decode_header
from email.message import Message
import tools
from osv import osv
@@ -87,7 +88,6 @@ class mail_message_common(osv.osv_memory):
"select plaintext or rich text contents accordingly", readonly=1),
'body_text': fields.text('Text contents', help="Plain-text version of the message"),
'body_html': fields.text('Rich-text contents', help="Rich-text/HTML version of the message"),
'original': fields.text('Original', help="Original version of the message, as it was sent on the network", readonly=1),
}
_defaults = {
@@ -175,6 +175,7 @@ class mail_message(osv.osv):
('cancel', 'Cancelled'),
], 'State', readonly=True),
'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it"),
'original': fields.binary('Original', help="Original version of the message, as it was sent on the network", readonly=1),
}
_defaults = {
@@ -307,13 +308,16 @@ class mail_message(osv.osv):
_logger.exception("Failed processing mail queue")
return res
def parse_message(self, message):
def parse_message(self, message, save_original=False):
"""Parses a string or email.message.Message representing an
RFC-2822 email, and returns a generic dict holding the
message details.
: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.
:rtype: dict
:return: A dict with the following structure, where each
field may not be present if missing in original
@@ -349,8 +353,11 @@ class mail_message(osv.osv):
message_id = msg_txt.get('message-id', False)
msg = {}
# save original, we need to be able to read the original email sometimes
msg['original'] = message
if save_original:
# save original, we need to be able to read the original email sometimes
msg['original'] = message.as_string() if isinstance(message, Message) \
else message
msg['original'] = base64.b64encode(msg['original']) # binary fields are b64
if not message_id:
# Very unusual situation, be we should be fault-tolerant here
@@ -487,8 +494,7 @@ class mail_message(osv.osv):
mail_server_id=message.mail_server_id.id,
context=context)
if res:
message.write({'state':'sent', 'message_id': res,
'original': msg.as_string(message.email_from)})
message.write({'state':'sent', 'message_id': res})
else:
message.write({'state':'exception'})
+4 -4
View File
@@ -36,12 +36,12 @@
</group>
<notebook colspan="4">
<page string="Body (Plain)" attrs="{'invisible':[('subtype','=','html')]}">
<field name="body_text" colspan="4" widget="text" nolabel="1"/>
</page>
<page string="Body (Rich)" attrs="{'invisible':[('subtype','=','plain')]}">
<field name="body_html" widget="text_html" nolabel="1" colspan="4"/>
</page>
<page string="Body (Plain)">
<field name="body_text" colspan="4" widget="text" nolabel="1"/>
</page>
</notebook>
<separator string="" colspan="4"/>
@@ -66,7 +66,7 @@
<field name="subtype" attrs="{'invisible':[('subtype', '=', False)]}"/>
<field name="auto_delete"/>
<field name="headers" colspan="4" attrs="{'invisible':[('headers', '=', False)]}"/>
<field name="original" colspan="4" height="350" attrs="{'invisible':[('original', '=', False)]}"/>
<field name="original" colspan="4" attrs="{'invisible':[('original', '=', False)]}"/>
</group>
</page>
</notebook>
+16 -6
View File
@@ -153,10 +153,14 @@ class mail_thread(osv.osv):
containing all the details passed as parameters. All attachments
will be attached to the thread record as well as to the actual
message.
If only the ``threads`` and ``subject`` parameters are provided,
a *event log* message is created, without the usual envelope
attributes (sender, recipients, etc.).
:param threads: list of thread ids, or list of browse_records representing
threads to which a new message should be attached
:param subject: Thread action keyword e.g.: If thread is closed "Close" keyword is used
:param subject: subject of the message, or description of the event if this
is an *event log* entry.
:param email_to: Email-To / Recipient address
:param email_from: Email From / Sender address if any
:param email_cc: Comma-Separated list of Carbon Copy Emails To addresse if any
@@ -291,7 +295,8 @@ class mail_thread(osv.osv):
context = context)
def message_process(self, cr, uid, model, message, custom_values=None, context=None):
def message_process(self, cr, uid, model, message, custom_values=None,
save_original=False, context=None):
"""Process an incoming RFC2822 email message related to the
given thread model, relying on ``mail.message.parse()``
for the parsing operation, and then calling ``message_new``
@@ -307,6 +312,8 @@ class mail_thread(osv.osv):
to pass to ``message_new`` if a new
record needs to be created. Ignored
if the thread record already exists.
:param bool save_original: whether to keep a copy of the original
email source attached to the message after it is imported.
"""
# extract message bytes - we are forced to pass the message as binary because
# we don't know its encoding until we parse its headers and hence can't
@@ -328,7 +335,7 @@ class mail_thread(osv.osv):
if isinstance(message, unicode):
message = message.encode('utf-8')
msg_txt = email.message_from_string(message)
msg = mail_message.parse_message(msg_txt)
msg = mail_message.parse_message(msg_txt, save_original=save_original)
# Create New Record into particular model
def create_record(msg):
@@ -357,6 +364,9 @@ class mail_thread(osv.osv):
if model_pool.exists(cr, uid, res_id):
if hasattr(model_pool, 'message_update'):
model_pool.message_update(cr, uid, [res_id], msg, {}, context=context)
else:
# referenced thread was not found, we'll have to create a new one
res_id = False
if not res_id:
res_id = create_record(msg)
#To forward the email to other followers
@@ -386,8 +396,8 @@ class mail_thread(osv.osv):
followers = model_pool.message_thread_followers(cr, uid, [res.id])[res.id]
else:
followers = self.message_thread_followers(cr, uid, [res.id])[res.id]
message_followers_emails = mail_message.to_email(','.join(filter(None, followers)))
message_recipients = mail_message.to_email(','.join(filter(None,
message_followers_emails = to_email(','.join(filter(None, followers)))
message_recipients = to_email(','.join(filter(None,
[decode(msg['from']),
decode(msg['to']),
decode(msg['cc'])])))
@@ -398,7 +408,7 @@ class mail_thread(osv.osv):
del msg['reply-to']
msg['reply-to'] = res.section_id.reply_to
smtp_from = mail_message.to_email(msg['from'])
smtp_from = to_email(msg['from'])
msg['from'] = smtp_from
msg['to'] = forward_to
msg['message-id'] = tools.generate_tracking_message_id(res.id)
@@ -46,13 +46,14 @@ class DefaultConfig(object):
OPENERP_PORT = 8069
OPENERP_DEFAULT_DATABASE = 'openerp'
MAIL_ERROR = 'error@example.com'
MAIL_SERVER = 'localhost'
MAIL_ADMINS = ('admin@example.com',)
MAIL_SERVER = 'smtp.example.com'
MAIL_SERVER_PORT = 25
MAIL_ADMINS = ('info@example.com',)
config = DefaultConfig()
def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER):
def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER, port=config.MAIL_SERVER_PORT):
assert isinstance(to_, (list, tuple))
if files is None:
@@ -74,7 +75,7 @@ def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER)
% file_name)
msg.attach(part)
smtp = smtplib.SMTP(server)
smtp = smtplib.SMTP(server, port=port)
smtp.sendmail(_from_, to_, msg.as_string() )
smtp.close()
@@ -104,16 +105,15 @@ class EmailParser(object):
self.email_default = email_default
def parse(self, message, custom_values=None):
if custom_values is None:
custom_values = {}
def parse(self, message, custom_values=None, save_original=None):
# pass message as bytes because we don't know its encoding until we parse its headers
# and hence can't convert it to utf-8 for transport
res_id = self.rpc('mail.thread',
'message_process',
self.model,
xmlrpclib.Binary(message),
custom_values)
custom_values or {},
save_original or False)
def configure_parser():
parser = optparse.OptionParser(usage='usage: %prog [options]', version='%prog v1.1')
@@ -145,6 +145,10 @@ def configure_parser():
parser.add_option("--custom-values", dest="custom_values",
help="Add Custom Values to the object",
default=None)
parser.add_option("-s", dest="save_original",
action="store_true",
help="Attach a copy of original email to the message entry",
default=False)
return parser
@@ -169,12 +173,14 @@ def main():
custom_values = {}
try:
print "custom vals: %s" % options.custom_values
custom_values = dict(eval(options.custom_values or {} ))
except:
pass
import traceback
traceback.print_exc()
try:
email_parser.parse(msg_txt, custom_values)
email_parser.parse(msg_txt, custom_values, options.save_original or False)
except Exception:
msg = '\n'.join([
'parameters',
@@ -191,6 +197,7 @@ def main():
config.MAIL_ADMINS,
subject, msg, files=[('message.txt', msg_txt)]
)
sys.stderr.write("Failed to deliver email to OpenERP Server, sending error notification to %s\n" % config.MAIL_ADMINS)
if __name__ == '__main__':
main()