[RENAME] python file
[odoo/odoo.git] / addons / mail / mail_message.py
index f4874f8..621eef5 100644 (file)
 ##############################################################################
 
 import base64
+import dateutil.parser
 import email
 import logging
 import re
 import time
 from email.header import decode_header
+from email.message import Message
 
 import tools
 from osv import osv
@@ -72,19 +74,20 @@ class mail_message_common(osv.osv_memory):
         'model': fields.char('Related Document model', size=128, select=1, readonly=1),
         'res_id': fields.integer('Related Document ID', select=1, readonly=1),
         'date': fields.datetime('Date'),
-        'email_from': fields.char('From', size=128, help='Message sender'),
+        'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences'),
         'email_to': fields.char('To', size=256, help='Message recipients'),
         'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
         'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),
-        'reply_to':fields.char('Reply-To', size=256, help='Response address for the message'),
-        'headers': fields.text('Message headers', help="Full message headers, e.g. SMTP session headers", readonly=1),
+        'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
+        'headers': fields.text('Message headers', readonly=1,
+                               help="Full message headers, e.g. SMTP session headers "
+                                    "(usually available on inbound messages only)"),
         'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
         'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
         'subtype': fields.char('Message type', size=32, help="Type of message, usually 'html' or 'plain', used to "
                                                              "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, before being imported by the system", readonly=1),
     }
 
     _defaults = {
@@ -150,8 +153,8 @@ class mail_message(osv.osv):
             msg_txt = ''
             if message.email_from:
                 msg_txt += _('%s wrote on %s: \n Subject: %s \n\t') % (message.email_from or '/', format_date_tz(message.date, tz), message.subject)
-                if message.body:
-                    msg_txt += truncate_text(message.body)
+                if message.body_text:
+                    msg_txt += truncate_text(message.body_text)
             else:
                 msg_txt = (message.user_id.name or '/') + _(' on ') + format_date_tz(message.date, tz) + ':\n\t'
                 msg_txt += message.subject
@@ -168,10 +171,15 @@ class mail_message(osv.osv):
                         ('outgoing', 'Outgoing'),
                         ('sent', 'Sent'),
                         ('received', 'Received'),
-                        ('exception', 'Exception'),
+                        ('exception', 'Delivery Failed'),
                         ('cancel', 'Cancelled'),
                         ], 'State', readonly=True),
-        'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it"),
+        'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
+        'original': fields.binary('Original', help="Original version of the message, as it was sent on the network", readonly=1),
+    }
+
+    _defaults = {
+        'state': 'received',
     }
 
     def init(self, cr):
@@ -179,6 +187,13 @@ class mail_message(osv.osv):
         if not cr.fetchone():
             cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
 
+    def copy(self, cr, uid, id, default=None, context=None):
+        """Overridden to avoid duplicating fields that are unique to each email"""
+        if default is None:
+            default = {}
+        default.update(message_id=False,original=False,headers=False)
+        return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
+
     def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, email_cc=None,
                              email_bcc=None, reply_to=False, attachments=None, message_id=False, references=False,
                              res_id=False, subtype='plain', headers=None, mail_server_id=False, auto_delete=False,
@@ -204,7 +219,7 @@ class mail_message(osv.osv):
            :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
                                   must match the format of the ``body`` parameter. Default is 'plain',
                                   making the content part of the mail "text/plain".
-           :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
+           :param dict attachments: map of filename to filecontents, where filecontents is a string
                                     containing the bytes of the attachment
            :param dict headers: optional map of headers to set on the outgoing mail (may override the
                                 other headers, including Subject, Reply-To, Message-Id, etc.)
@@ -227,7 +242,7 @@ class mail_message(osv.osv):
                 'user_id': uid,
                 'model': model,
                 'res_id': res_id,
-                'body_text': body if subtype == 'plain' else False,
+                'body_text': body if subtype != 'html' else False,
                 'body_html': body if subtype == 'html' else False,
                 'email_from': email_from,
                 'email_to': email_to and ','.join(email_to) or '',
@@ -244,7 +259,7 @@ class mail_message(osv.osv):
             }
         email_msg_id = self.create(cr, uid, msg_vals, context)
         attachment_ids = []
-        for fname, fcontent in attachments.items():
+        for fname, fcontent in attachments.iteritems():
             attachment_data = {
                     'name': fname,
                     'datas_fname': fname,
@@ -293,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
@@ -314,7 +332,7 @@ class mail_message(osv.osv):
                                     #.. all X- headers...
                                   },
                       'subtype': msg_mime_subtype,
-                      'body': plaintext_body
+                      'body_text': plaintext_body
                       'body_html': html_body,
                       'attachments': { 'file1': 'bytes',
                                        'file2': 'bytes' }
@@ -335,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
@@ -357,6 +378,8 @@ class mail_message(osv.osv):
         if 'From' in fields:
             msg['from'] = decode(msg_txt.get('From') or msg_txt.get_unixfrom())
 
+        if 'To' in fields:
+            msg['to'] = decode(msg_txt.get('To'))
         if 'Delivered-To' in fields:
             msg['to'] = decode(msg_txt.get('Delivered-To'))
 
@@ -367,7 +390,8 @@ class mail_message(osv.osv):
             msg['reply'] = decode(msg_txt.get('Reply-To'))
 
         if 'Date' in fields:
-            msg['date'] = decode(msg_txt.get('Date'))
+            date_hdr = decode(msg_txt.get('Date'))
+            msg['date'] = dateutil.parser.parse(date_hdr).strftime("%Y-%m-%d %H:%M:%S")
 
         if 'Content-Transfer-Encoding' in fields:
             msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
@@ -379,6 +403,7 @@ class mail_message(osv.osv):
             msg['in-reply-to'] = msg_txt.get('In-Reply-To')
 
         msg['headers'] = {}
+        msg['subtype'] = 'plain'
         for item in msg_txt.items():
             if item[0].startswith('X-'):
                 msg['headers'].update({item[0]: item[1]})
@@ -389,9 +414,7 @@ class mail_message(osv.osv):
                 msg['body_html'] =  body
                 msg['subtype'] = 'html'
                 body = tools.html2plaintext(body)
-            else:
-                msg['subtype'] = 'plain'
-            msg['body'] = tools.ustr(body, encoding)
+            msg['body_text'] = tools.ustr(body, encoding)
 
         attachments = {}
         if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
@@ -413,6 +436,7 @@ class mail_message(osv.osv):
                     content = tools.ustr(content, encoding)
                     if part.get_content_subtype() == 'html':
                         msg['body_html'] = content
+                        msg['subtype'] = 'html' # html version prevails
                         body = tools.ustr(tools.html2plaintext(content))
                     elif part.get_content_subtype() == 'plain':
                         body = content
@@ -423,8 +447,12 @@ class mail_message(osv.osv):
                         res = part.get_payload(decode=True)
                         body += tools.ustr(res, encoding)
 
-            msg['body'] = body
-            msg['attachments'] = attachments
+            msg['body_text'] = body
+        msg['attachments'] = attachments
+
+        # for backwards compatibility:
+        msg['body'] = msg['body_text']
+        msg['sub_type'] = msg['subtype'] or 'plain'
         return msg
 
     def send(self, cr, uid, ids, auto_commit=False, context=None):