diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 59f6b47e0bf..f6b3a20c138 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -486,19 +486,19 @@ class crm_case(object): attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas']) attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send) - # Send an email - subject = "Reminder: [%s] %s" % (str(case.id), case.name, ) - email_message_obj.schedule_with_attach(cr, uid, - src, - [dest], - subject, - body, - model='crm.case', - reply_to=case.section_id.reply_to, - openobject_id=str(case.id), - attach=attach_to_send - ) - self._history(cr, uid, [case], _('Send'), history=True, subject=subject, email=dest, details=body, email_from=src) + # Send an email + subject = "Reminder: [%s] %s" % (str(case.id), case.name, ) + email_message_obj.schedule_with_attach(cr, uid, + src, + [dest], + subject, + body, + model='crm.case', + reply_to=case.section_id.reply_to, + openobject_id=str(case.id), + attach=attach_to_send + ) + self._history(cr, uid, [case], _('Send'), history=True, subject=subject, email=dest, details=body, email_from=src) return True def _check(self, cr, uid, ids=False, context=None): @@ -538,7 +538,7 @@ class crm_case(object): def format_mail(self, obj, body): return self.pool.get('base.action.rule').format_mail(obj, body) - def message_followers(self, cr, uid, ids, context=None): + def thread_followers(self, cr, uid, ids, context=None): """ Get a list of emails of the people following this thread """ res = {} diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 7844a9e8037..e796cf7e654 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -348,22 +348,9 @@ class crm_lead(crm_case, osv.osv): if res: vals.update(res) - res = self.create(cr, uid, vals, context) - attachents = msg.get('attachments', []) - for attactment in attachents or []: - data_attach = { - 'name': attactment, - 'datas':binascii.b2a_base64(str(attachents.get(attactment))), - 'datas_fname': attactment, - 'description': 'Mail attachment', - 'res_model': self._name, - 'res_id': res, - } - self.pool.get('ir.attachment').create(cr, uid, data_attach) + return self.create(cr, uid, vals, context) - return res - - def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None): + def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index 3b3c02f980f..f5cceb59498 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -202,22 +202,9 @@ class crm_claim(crm.crm_case, osv.osv): if res: vals.update(res) - res = self.create(cr, uid, vals, context) - attachents = msg.get('attachments', []) - for attactment in attachents or []: - data_attach = { - 'name': attactment, - 'datas':binascii.b2a_base64(str(attachents.get(attactment))), - 'datas_fname': attactment, - 'description': 'Mail attachment', - 'res_model': self._name, - 'res_id': res, - } - self.pool.get('ir.attachment').create(cr, uid, data_attach) + return self.create(cr, uid, vals, context) - return res - - def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None): + def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index 6dce6cdd2ef..ff503a8fb3e 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -126,22 +126,9 @@ class crm_helpdesk(crm.crm_case, osv.osv): if res: vals.update(res) - res = self.create(cr, uid, vals, context) - attachents = msg.get('attachments', []) - for attactment in attachents or []: - data_attach = { - 'name': attactment, - 'datas':binascii.b2a_base64(str(attachents.get(attactment))), - 'datas_fname': attactment, - 'description': 'Mail attachment', - 'res_model': self._name, - 'res_id': res, - } - self.pool.get('ir.attachment').create(cr, uid, data_attach) + return self.create(cr, uid, vals, context) - return res - - def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None): + def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 10df9798b0b..476174db32f 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -175,7 +175,7 @@ This is useful for CRM leads for example"), data_obj = self.pool.get('ir.model.data') for template in self.browse(cr, uid, ids, context=context): src_obj = template.model_id.model - model_data_id = data_obj._get_id(cr, uid, 'email', 'email_compose_message_wizard_form') + model_data_id = data_obj._get_id(cr, uid, 'mail', 'email_compose_message_wizard_form') res_id = data_obj.browse(cr, uid, model_data_id, context=context).res_id vals['ref_ir_act_window'] = action_obj.create(cr, uid, { 'name': template.name, @@ -394,7 +394,7 @@ This is useful for CRM leads for example"), elif template['track_campaign_item']: # get appropriate message-id - values.update({'message_id': smtp_pool.generate_tracking_message_id(record_id)}) + values.update({'message_id': tools.generate_tracking_message_id(record_id)}) #Use signatures if allowed if template.user_signature: diff --git a/addons/email_template/wizard/email_compose_message.py b/addons/email_template/wizard/email_compose_message.py index 77ec0aed5d3..0c584c98990 100644 --- a/addons/email_template/wizard/email_compose_message.py +++ b/addons/email_template/wizard/email_compose_message.py @@ -96,7 +96,7 @@ class email_compose_message(osv.osv_memory): result.update({'subject' : vals.get('name', False)}) if 'body' in fields: - result.update({'body' : vals.get('description', False)}) + result.update({'body' : vals.get('body', False)}) if 'reply_to' in fields: result.update({'reply_to' : vals.get('reply_to', False)}) diff --git a/addons/email_template/wizard/email_template_preview.py b/addons/email_template/wizard/email_template_preview.py index f1636a64de8..00e20b98cf3 100644 --- a/addons/email_template/wizard/email_template_preview.py +++ b/addons/email_template/wizard/email_template_preview.py @@ -93,8 +93,7 @@ class email_template_preview(osv.osv_memory): if template.message_id: vals['message_id'] = self.get_template_value(cr, uid, message_id, model, res_id, context) elif template.track_campaign_item: - smtp_server_obj = self.pool.get('ir.mail_server') - vals['message_id'] = smtp_server_obj.generate_tracking_message_id(res_id) + vals['message_id'] = tools.generate_tracking_message_id(res_id) vals['subject'] = self.get_template_value(cr, uid, template.subject, model, res_id, context) description = self.get_template_value(cr, uid, template.body, model, res_id, context) or '' if template.user_signature: diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py index 5749fd423ca..3e479b76e3d 100644 --- a/addons/fetchmail/fetchmail.py +++ b/addons/fetchmail/fetchmail.py @@ -82,21 +82,9 @@ class email_server(osv.osv): return False return True - def check_model(self, cr, uid, ids, context = None): - if context is None: - context = {} - current_rec = self.read(cr, uid, ids, context) - if current_rec: - current_rec = current_rec[0] - model_name = self.pool.get('ir.model').browse(cr, uid, current_rec.get('object_id')[0]).model - model = self.pool.get(model_name) - if hasattr(model, 'message_new'): - return True - return False - _constraints = [ (check_duplicate, 'Warning! Can\'t have duplicate server configuration!', ['user', 'password']), - (check_model, 'Warning! Record for selected Model can not be created\nPlease choose valid Model', ['object_id']) + ] def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False): diff --git a/addons/fetchmail/fetchmail_view.xml b/addons/fetchmail/fetchmail_view.xml index 785cec55c5d..883c816bf7f 100644 --- a/addons/fetchmail/fetchmail_view.xml +++ b/addons/fetchmail/fetchmail_view.xml @@ -143,7 +143,7 @@ diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 7d210f8b685..24b226f922e 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -309,8 +309,6 @@ class hr_applicant(crm.crm_case, osv.osv): @param uid: the current user’s ID for security checks """ thread_pool = self.pool.get('email.thread') - attach_obj = self.pool.get('ir.attachment') - subject = msg.get('subject') body = msg.get('body') msg_from = msg.get('from') @@ -329,23 +327,9 @@ class hr_applicant(crm.crm_case, osv.osv): res = thread_pool.get_partner(cr, uid, msg.get('from')) if res: vals.update(res) - res = self.create(cr, uid, vals, context=context) + return self.create(cr, uid, vals, context=context) - attachents = msg.get('attachments', []) - for attactment in attachents or []: - data_attach = { - 'name': attactment, - 'datas':binascii.b2a_base64(str(attachents.get(attactment))), - 'datas_fname': attactment, - 'description': 'Mail attachment', - 'res_model': self._name, - 'res_id': res, - } - attach_obj.create(cr, uid, data_attach, context=context) - - return res - - def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None): + def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, diff --git a/addons/mail/email_message.py b/addons/mail/email_message.py index 4f4978ee6a4..a32c29ff759 100644 --- a/addons/mail/email_message.py +++ b/addons/mail/email_message.py @@ -26,6 +26,10 @@ import tools import netsvc import base64 import time +import logging +import re +import email +from email.header import decode_header #import binascii #import email #from email.header import decode_header @@ -57,6 +61,8 @@ import time #] LOGGER = netsvc.Logger() +_logger = logging.getLogger('mail') + def format_date_tz(date, tz=None): if not date: return 'n/a' @@ -182,6 +188,7 @@ class email_message(osv.osv): ('sent', 'Sent'), ('received', 'Received'), ('exception', 'Exception'), + ('cancel', 'Cancelled'), ], 'State', readonly=True), 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"), } @@ -272,6 +279,119 @@ class email_message(osv.osv): return False return res + def _decode_header(self, text): + """Returns unicode() string conversion of the the given encoded smtp header""" + if text: + text = decode_header(text.replace('\r', '')) + return ''.join([tools.ustr(x[0], x[1]) for x in text]) + + def to_email(self,text): + return re.findall(r'([^ ,<@]+@[^> ,]+)',text) + + def parse_message(self, message): + """Return Dictionary Object after parse EML Message String + @param message: email.message.Message object or string or unicode object + """ + 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) + + msg_txt = message + message_id = msg_txt.get('message-id', False) + msg = {} + + 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) + + + fields = msg_txt.keys() + msg['id'] = message_id + msg['message-id'] = message_id + + if 'Subject' in fields: + msg['subject'] = self._decode_header(msg_txt.get('Subject')) + + if 'Content-Type' in fields: + msg['content-type'] = msg_txt.get('Content-Type') + + if 'From' in fields: + msg['from'] = self._decode_header(msg_txt.get('From') or msg_txt.get_unixfrom()) + + if 'Delivered-To' in fields: + msg['to'] = self._decode_header(msg_txt.get('Delivered-To')) + + if 'CC' in fields: + msg['cc'] = self._decode_header(msg_txt.get('CC')) + + if 'Reply-to' in fields: + msg['reply'] = self._decode_header(msg_txt.get('Reply-To')) + + if 'Date' in fields: + msg['date'] = self._decode_header(msg_txt.get('Date')) + + if 'Content-Transfer-Encoding' in fields: + msg['encoding'] = msg_txt.get('Content-Transfer-Encoding') + + if 'References' in fields: + msg['references'] = msg_txt.get('References') + + if 'In-Reply-To' in fields: + msg['in-reply-to'] = msg_txt.get('In-Reply-To') + + if 'X-Priority' in fields: + msg['priority'] = msg_txt.get('X-Priority', '3 (Normal)').split(' ')[0] #TOFIX: + + 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', ''): + body = tools.html2plaintext(body) + msg['body'] = tools.ustr(body, encoding) + + attachments = {} + has_plain_text = False + 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[filename] = content + elif not has_plain_text: + # main content parts should have 'text' maintype + # and no filename. we ignore the html part if + # there is already a plaintext part without filename, + # because presumably these are alternatives. + content = tools.ustr(content, encoding) + if part.get_content_subtype() == 'html': + body = tools.ustr(tools.html2plaintext(content)) + elif part.get_content_subtype() == 'plain': + body = content + has_plain_text = True + elif part.get_content_maintype() in ('application', 'image'): + if filename : + attachments[filename] = part.get_payload(decode=True) + else: + res = part.get_payload(decode=True) + body += tools.ustr(res, encoding) + + msg['body'] = body + msg['attachments'] = attachments + return msg + def send_email(self, cr, uid, ids, auto_commit=False, context=None): """ send email message @@ -302,8 +422,7 @@ class email_message(osv.osv): subtype=message.sub_type, x_headers=message.headers and eval(message.headers) or {}, priority=message.priority) - res = smtp_server_obj.send_email(cr, uid, message.email_from, - message.email_to and message.email_to.split(',') or [], + res = smtp_server_obj.send_email(cr, uid, msg, mail_server_id = message.smtp_server_id.id or None, smtp_server=smtp_server and smtp_server.smtp_host or None, @@ -336,6 +455,13 @@ class email_message(osv.osv): self.write(cr, uid, [message.id], {'state':'exception'}, context) return False return True + + def do_cancel(self, cr, uid, ids, context=None): + ''' + Cancel the email to be send + ''' + self.write(cr, uid, ids, {'state':'cancel'}, context) + return True # OLD Code. # def send_all_mail(self, cr, uid, ids=None, context=None): # if ids is None: diff --git a/addons/mail/email_thread.py b/addons/mail/email_thread.py index e48ff9d27df..78d5b18886f 100644 --- a/addons/mail/email_thread.py +++ b/addons/mail/email_thread.py @@ -24,15 +24,15 @@ import time import tools import binascii import email -from email.header import decode_header + from email.utils import parsedate import base64 -import re + from tools.translate import _ import logging import xmlrpclib -_logger = logging.getLogger('mailgate') +_logger = logging.getLogger('mail') class email_thread(osv.osv): ''' @@ -60,16 +60,70 @@ class email_thread(osv.osv): default.update({ 'message_ids': [], - 'date_closed': False, - 'date_open': False }) return super(email_thread, self).copy(cr, uid, id, default, context=context) - def message_new(self, cr, uid, msg, context): - raise Exception, _('Method is not implemented') + def message_new(self, cr, uid, msg, context=None): + """ + Called by process_email() to create a new record + corresponding to an incoming message for a new thread. + @param msg: Dictionary Object to contain email message data + """ + if context is None: + context = {} + model = context.get('thread_model', False) + if not model: + model = self._name + model_pool = self.pool.get(model) + if hasattr(model_pool, 'message_new'): + res_id = model_pool.message_new(cr, uid, msg, context) + else: + fields = model_pool.fields_get(cr, uid, context=context) + data = model_pool.default_get(cr, uid, fields, context=context) + if 'name' in fields and not data.get('name',False): + data['name'] = msg.get('from','') + res_id = model_pool.create(cr, uid, data, context=context) - def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None): - raise Exception, _('Method is not implemented') + attachments = msg.get('attachments', {}) + self.history(cr, uid, [res_id], _('receive'), history=True, + subject = msg.get('subject'), + email = msg.get('to'), + details = msg.get('body'), + email_from = msg.get('from'), + email_cc = msg.get('cc'), + message_id = msg.get('message-id'), + references = msg.get('references', False) or msg.get('in-reply-to', False), + attach = attachments.items(), + email_date = msg.get('date'), + context = context) + return res_id + + def message_update(self, cr, uid, ids, msg, vals={}, default_act=None, context=None): + """ + Called by process_email() to add a new incoming message for an existing thread + @param msg: Dictionary Object to contain email message data + """ + if context is None: + context = {} + model = context.get('thread_model', False) + if not model: + model = self._name + model_pool = self.pool.get(model) + if hasattr(model_pool, 'message_update'): + model_pool.message_update(cr, uid, ids, msg, vals=vals, default_act=default_act, context=context) + attachments = msg.get('attachments', {}) + self.history(cr, uid, ids, _('receive'), history=True, + subject = msg.get('subject'), + email = msg.get('to'), + details = msg.get('body'), + email_from = msg.get('from'), + email_cc = msg.get('cc'), + message_id = msg.get('message-id'), + references = msg.get('references', False) or msg.get('in-reply-to', False), + attach = attachments.items(), + email_date = msg.get('date'), + context = context) + return True def thread_followers(self, cr, uid, ids, context=None): """ Get a list of emails of the people following this thread @@ -86,59 +140,72 @@ class email_thread(osv.osv): res[thread.id] = l return res - def history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, \ + def history(self, cr, uid, threads, keyword, history=False, subject=None, email=False, details=None, \ email_from=False, message_id=False, references=None, attach=None, email_cc=None, \ email_bcc=None, email_date=None, context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user’s ID for security checks, - @param cases: a browse record list - @param keyword: Case action keyword e.g.: If case is closed "Close" keyword is used + @param threads: a browse record list + @param keyword: Thread action keyword e.g.: If thread is closed "Close" keyword is used @param history: Value True/False, If True it makes entry as a Emails Messages otherwise Log Messages @param email: 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 @param email_bcc: Comma-Separated list of Blind Carbon Copy Emails To addresses if any @param email_date: Email Date string if different from now, in server Timezone - @param details: Description, Details of case history if any - @param atach: Attachment sent in email + @param details: Description, Details of thread history if any + @param attach: Attachment sent in email @param context: A standard dictionary for contextual values""" if context is None: context = {} if attach is None: attach = [] + model = context.get('thread_model', False) + if not model: + model = self._name + model_pool = self.pool.get(model) + if email_date: edate = parsedate(email_date) if edate is not None: email_date = time.strftime('%Y-%m-%d %H:%M:%S', edate) - # The mailgate sends the ids of the cases and not the object list + # The script sends the ids of the threads and not the object list - if all(isinstance(case_id, (int, long)) for case_id in cases): - cases = self.browse(cr, uid, cases, context=context) + if all(isinstance(thread_id, (int, long)) for thread_id in threads): + threads = model_pool.browse(cr, uid, threads, context=context) att_obj = self.pool.get('ir.attachment') obj = self.pool.get('email.message') - for case in cases: + for thread in threads: attachments = [] - for att in attach: - attachments.append(att_obj.create(cr, uid, {'res_model':case._name,'res_id':case.id, 'name': att[0], 'datas': base64.encodestring(att[1])})) + for attachment in attach: + data_attach = { + 'name': attachment[0], + 'datas': binascii.b2a_base64(str(attachment[1])), + 'datas_fname': attachment[0], + 'description': _('Mail attachment'), + 'res_model': thread._name, + 'res_id': thread.id, + } + attachments.append(att_obj.create(cr, uid, data_attach)) - partner_id = hasattr(case, 'partner_id') and (case.partner_id and case.partner_id.id or False) or False - if not partner_id and case._name == 'res.partner': - partner_id = case.id + partner_id = hasattr(thread, 'partner_id') and (thread.partner_id and thread.partner_id.id or False) or False + if not partner_id and thread._name == 'res.partner': + partner_id = thread.id data = { 'subject': keyword, 'user_id': uid, - 'model' : case._name, + 'model' : thread._name, 'partner_id': partner_id, - 'res_id': case.id, + 'res_id': thread.id, 'date': time.strftime('%Y-%m-%d %H:%M:%S'), 'message_id': message_id, - 'body': details or (hasattr(case, 'description') and case.description or False), + 'body': details or (hasattr(thread, 'description') and thread.description or False), 'attachment_ids': [(6, 0, attachments)] } @@ -151,49 +218,44 @@ class email_thread(osv.osv): 'subject': subject or _('History'), 'history': True, 'user_id': uid, - 'model' : case._name, - 'res_id': case.id, + 'model' : thread._name, + 'res_id': thread.id, 'date': email_date or time.strftime('%Y-%m-%d %H:%M:%S'), 'body': details, 'email_to': email, 'email_from': email_from or \ - (hasattr(case, 'user_id') and case.user_id and case.user_id.address_id and \ - case.user_id.address_id.email), + (hasattr(thread, 'user_id') and thread.user_id and thread.user_id.address_id and \ + thread.user_id.address_id.email), 'email_cc': email_cc, 'email_bcc': email_bcc, 'partner_id': partner_id, 'references': references, 'message_id': message_id, - 'attachment_ids': [(6, 0, attachments)] + 'attachment_ids': [(6, 0, attachments)], + 'state' : 'received', } obj.create(cr, uid, data, context=context) return True - def _decode_header(self, text): - """Returns unicode() string conversion of the the given encoded smtp header""" - if text: - text = decode_header(text.replace('\r', '')) - return ''.join([tools.ustr(x[0], x[1]) for x in text]) - - def to_email(self,text): - return re.findall(r'([^ ,<@]+@[^> ,]+)',text) - def email_forward(self, cr, uid, model, res_ids, msg, email_error=False, context=None): """Sends an email to all people following the thread @param res_id: Id of the record of OpenObject model created from the email message - @param msg: email.message.Message to forward + @param msg: email.message.Message object to forward @param email_error: Default Email address in case of any Problem """ model_pool = self.pool.get(model) smtp_server_obj = self.pool.get('ir.mail_server') - + email_message_obj = self.pool.get('email.message') + _decode_header = email_message_obj._decode_header for res in model_pool.browse(cr, uid, res_ids, context=context): - thread_followers = model_pool.thread_followers(cr, uid, [res.id])[res.id] - message_followers_emails = self.to_email(','.join(filter(None, thread_followers))) - message_recipients = self.to_email(','.join(filter(None, - [self._decode_header(msg['from']), - self._decode_header(msg['to']), - self._decode_header(msg['cc'])]))) + if hasattr(model_pool, 'thread_followers'): + self.thread_followers = model_pool.thread_followers + thread_followers = self.thread_followers(cr, uid, [res.id])[res.id] + message_followers_emails = email_message_obj.to_email(','.join(filter(None, thread_followers))) + message_recipients = email_message_obj.to_email(','.join(filter(None, + [_decode_header(msg['from']), + _decode_header(msg['to']), + _decode_header(msg['cc'])]))) message_forward = [i for i in message_followers_emails if (i and (i not in message_recipients))] if message_forward: @@ -202,16 +264,17 @@ class email_thread(osv.osv): del msg['reply-to'] msg['reply-to'] = res.section_id.reply_to - smtp_from = self.to_email(msg['from']) + smtp_from = email_message_obj.to_email(msg['from']) msg['from'] = smtp_from msg['to'] = message_forward - msg['Message-Id'] = tools.generate_tracking_message_id(res.id) + msg['message-id'] = tools.generate_tracking_message_id(res.id) if not smtp_server_obj.send_email(cr, uid, msg) and email_error: subj = msg['subject'] del msg['subject'], msg['to'], msg['cc'], msg['bcc'] - msg['subject'] = '[OpenERP-Forward-Failed] %s' % subj - msg['To'] = email_error + msg['subject'] = _('[OpenERP-Forward-Failed] %s') % subj + msg['to'] = email_error smtp_server_obj.send_email(cr, uid, msg) + return True def process_email(self, cr, uid, model, message, custom_values=None, attach=True, context=None): """This function Processes email and create record for given OpenERP model @@ -236,136 +299,29 @@ class email_thread(osv.osv): custom_values = {} model_pool = self.pool.get(model) + if self._name != model: + context.update({'thread_model':model}) + + email_message_pool = self.pool.get('email.message') res_id = False - # Create New Record into particular model - def create_record(msg): - att_ids = [] - if hasattr(model_pool, 'message_new'): - res_id = model_pool.message_new(cr, uid, msg, context=context) - if custom_values: - model_pool.write(cr, uid, [res_id], custom_values, context=context) - else: - data = { - 'subject': msg.get('subject'), - 'email_from': msg.get('from'), - 'email_cc': msg.get('cc'), - 'user_id': False, - 'body': msg.get('body'), - #'state' : 'draft', - } - data.update(self.get_partner(cr, uid, msg.get('from'), context=context)) - res_id = model_pool.create(cr, uid, data, context=context) - - if attach: - for attachment in msg.get('attachments', []): - data_attach = { - 'name': attachment, - 'datas': binascii.b2a_base64(str(attachments.get(attachment))), - 'datas_fname': attachment, - 'description': 'Mail attachment', - 'res_model': model, - 'res_id': res_id, - } - att_ids.append(self.pool.get('ir.attachment').create(cr, uid, data_attach)) - - return res_id, att_ids + # Parse 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 = {} + msg = email_message_pool.parse_message(msg_txt) - 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('Message without message-id, generating a random one: %s', message_id) + # Create New Record into particular model + def create_record(msg): + new_res_id = self.message_new(cr, uid, msg, context=context) + if custom_values: + model_pool.write(cr, uid, [res_id], custom_values, context=context) + return new_res_id - fields = msg_txt.keys() - msg['id'] = message_id - msg['message-id'] = message_id - - if 'Subject' in fields: - msg['subject'] = self._decode_header(msg_txt.get('Subject')) - - if 'Content-Type' in fields: - msg['content-type'] = msg_txt.get('Content-Type') - - if 'From' in fields: - msg['from'] = self._decode_header(msg_txt.get('From') or msg_txt.get_unixfrom()) - - if 'Delivered-To' in fields: - msg['to'] = self._decode_header(msg_txt.get('Delivered-To')) - - if 'CC' in fields: - msg['cc'] = self._decode_header(msg_txt.get('CC')) - - if 'Reply-to' in fields: - msg['reply'] = self._decode_header(msg_txt.get('Reply-To')) - - if 'Date' in fields: - msg['date'] = self._decode_header(msg_txt.get('Date')) - - if 'Content-Transfer-Encoding' in fields: - msg['encoding'] = msg_txt.get('Content-Transfer-Encoding') - - if 'References' in fields: - msg['references'] = msg_txt.get('References') - - if 'In-Reply-To' in fields: - msg['in-reply-to'] = msg_txt.get('In-Reply-To') - - if 'X-Priority' in fields: - msg['priority'] = msg_txt.get('X-Priority', '3 (Normal)').split(' ')[0] - - 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_txt.get('Content-Type', ''): - body = tools.html2plaintext(body) - msg['body'] = tools.ustr(body, encoding) - - attachments = {} - has_plain_text = False - 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[filename] = content - elif not has_plain_text: - # main content parts should have 'text' maintype - # and no filename. we ignore the html part if - # there is already a plaintext part without filename, - # because presumably these are alternatives. - content = tools.ustr(content, encoding) - if part.get_content_subtype() == 'html': - body = tools.ustr(tools.html2plaintext(content)) - elif part.get_content_subtype() == 'plain': - body = content - has_plain_text = True - elif part.get_content_maintype() in ('application', 'image'): - if filename : - attachments[filename] = part.get_payload(decode=True) - else: - res = part.get_payload(decode=True) - body += tools.ustr(res, encoding) - - msg['body'] = body - msg['attachments'] = attachments - res_ids = [] - attachment_ids = [] - new_res_id = False + res_id = False if msg.get('references') or msg.get('in-reply-to'): references = msg.get('references') or msg.get('in-reply-to') if '\r\n' in references: @@ -374,45 +330,24 @@ class email_thread(osv.osv): references = references.split(' ') for ref in references: ref = ref.strip() - res_id = tools.misc.reference_re.search(ref) + res_id = tools.reference_re.search(ref) if res_id: res_id = res_id.group(1) else: - res_id = tools.misc.res_re.search(msg['subject']) + res_id = tools.res_re.search(msg['subject']) if res_id: res_id = res_id.group(1) if res_id: res_id = int(res_id) - model_pool = self.pool.get(model) if model_pool.exists(cr, uid, res_id): - res_ids.append(res_id) - if hasattr(model_pool, 'message_update'): - model_pool.message_update(cr, uid, [res_id], {}, msg, context=context) - else: - raise NotImplementedError('model %s does not support updating records, mailgate API method message_update() is missing'%model) + self.message_update(cr, uid, [res_id], msg, {}, context=context) - if not len(res_ids): - new_res_id, attachment_ids = create_record(msg) - res_ids = [new_res_id] + if not res_id: + res_id = create_record(msg) - # Store messages - context.update({'model' : model}) - if hasattr(model_pool, 'history'): - model_pool.history(cr, uid, res_ids, _('receive'), history=True, - subject = msg.get('subject'), - email = msg.get('to'), - details = msg.get('body'), - email_from = msg.get('from'), - email_cc = msg.get('cc'), - message_id = msg.get('message-id'), - references = msg.get('references', False) or msg.get('in-reply-to', False), - attach = attachments.items(), - email_date = msg.get('date'), - context = context) - else: - self.history(cr, uid, model, res_ids, msg, attachment_ids, context=context) - self.email_forward(cr, uid, model, res_ids, msg_txt) - return new_res_id + #To forward the email to other followers + self.email_forward(cr, uid, model, [res_id], msg_txt) + return res_id def get_partner(self, cr, uid, from_email, context=None): """This function returns partner Id based on email passed @@ -422,11 +357,12 @@ class email_thread(osv.osv): @param from_email: email address based on that function will search for the correct """ address_pool = self.pool.get('res.partner.address') + email_message_pool = self.pool.get('email.message') res = { 'partner_address_id': False, 'partner_id': False } - from_email = self.to_email(from_email)[0] + from_email = email_message_pool.to_email(from_email)[0] address_ids = address_pool.search(cr, uid, [('email', 'like', from_email)]) if address_ids: address = address_pool.browse(cr, uid, address_ids[0]) @@ -435,14 +371,5 @@ class email_thread(osv.osv): return res - email_thread() - -def format_date_tz(date, tz=None): - if not date: - return 'n/a' - format = tools.DEFAULT_SERVER_DATETIME_FORMAT - return tools.server_to_local_timestamp(date, format, format, tz) - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail/email_view.xml b/addons/mail/email_view.xml index 9f933200b18..6f0a17be442 100644 --- a/addons/mail/email_view.xml +++ b/addons/mail/email_view.xml @@ -40,12 +40,16 @@ - - - -