From 59f9be67f791f40d698b7183b81f7a7abc34a654 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 25 Jun 2010 01:23:32 +0530 Subject: [PATCH] [IMP] mail_gateway: * introduce 'email.server.tools' to process_email and send acknowdgement after creating new record * use 'email.server.tools' model in script and fetchmail module to processing email * start mailgateway services on crm.lead, project.issue, hr.applicant bzr revid: hmo@tinyerp.com-20100624195332-7qci6vrimvzple5w --- addons/crm/__init__.py | 1 - addons/crm/crm_lead.py | 127 ++++++ addons/crm/crm_mailgate.py | 172 -------- addons/crm/security/ir.model.access.csv | 3 +- addons/fetchmail/__openerp__.py | 2 +- addons/fetchmail/fetchmail.py | 410 +++--------------- addons/fetchmail/fetchmail_view.xml | 72 +-- addons/fetchmail/security/ir.model.access.csv | 2 - addons/hr_recruitment/hr_recruitment.py | 130 ++++++ addons/mail_gateway/mail_gateway.py | 348 ++++++++++++++- addons/mail_gateway/mail_gateway_view.xml | 73 +++- .../openerp_mailgate/openerp_mailgate.py | 395 +---------------- addons/project_issue/project_issue.py | 127 ++++++ 13 files changed, 880 insertions(+), 982 deletions(-) delete mode 100644 addons/crm/crm_mailgate.py diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index 362444ccc2c..e56ed80af30 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -20,7 +20,6 @@ ############################################################################## import crm -import crm_mailgate import crm_action_rule import crm_segmentation import crm_meeting diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index e5e1c92daef..2700581d6cc 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -27,6 +27,9 @@ import time import mx.DateTime from tools.translate import _ from crm import crm_case +import collections +import binascii +import tools class crm_lead(osv.osv, crm_case): """ CRM Lead Case """ @@ -235,6 +238,130 @@ and users by email"), } return value + def message_new(self, cr, uid, msg, context): + """ + Automatically calls when new email message arrives + + @param self: The object pointer + @param cr: the current row, from the database cursor, + @param uid: the current user’s ID for security checks + """ + + mailgate_pool = self.pool.get('email.server.tools') + + subject = msg.get('subject') + body = msg.get('body') + msg_from = msg.get('from') + priority = msg.get('priority') + + vals = { + 'name': subject, + 'email_from': msg_from, + 'email_cc': msg.get('cc'), + 'description': body, + 'user_id': False, + } + if msg.get('priority', False): + vals['priority'] = priority + + res = mailgate_pool.get_partner(cr, uid, msg.get('from')) + 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 res + + def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}): + """ + @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 ids: List of update mail’s IDs + """ + + if isinstance(ids, (str, int, long)): + ids = [ids] + + msg_from = msg['from'] + vals.update({ + 'description': msg['body'] + }) + if msg.get('priority', False): + vals['priority'] = msg.get('priority') + + maps = { + 'cost':'planned_cost', + 'revenue': 'planned_revenue', + 'probability':'probability' + } + vls = { } + for line in msg['body'].split('\n'): + line = line.strip() + res = tools.misc.command_re.match(line) + if res and maps.get(res.group(1).lower(), False): + key = maps.get(res.group(1).lower()) + vls[key] = res.group(2).lower() + + vals.update(vls) + res = self.write(cr, uid, ids, vals) + return res + + def emails_get(self, cr, uid, ids, context=None): + + """ + Get Emails + @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 ids: List of email’s IDs + @param context: A standard dictionary for contextual values + """ + res = {} + + if isinstance(ids, (str, int, long)): + select = [long(ids)] + else: + select = ids + + for thread in self.browse(cr, uid, select, context=context): + values = collections.defaultdict(set) + + for message in thread.message_ids: + user_email = (message.user_id and message.user_id.address_id and message.user_id.address_id.email) or False + values['user_email'].add(user_email) + values['email_from'].add(message.email_from) + values['email_cc'].add(message.email_cc or False) + values['priority'] = thread.priority + + res[thread.id] = dict((key,list(values[key])) for key, value in values.iteritems()) + + return res + + def msg_send(self, cr, uid, id, *args, **argv): + + """ Send The Message + @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 ids: List of email’s IDs + @param *args: Return Tuple Value + @param **args: Return Dictionary of Keyword Value + """ + return True + crm_lead() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/crm_mailgate.py b/addons/crm/crm_mailgate.py deleted file mode 100644 index d39293e4716..00000000000 --- a/addons/crm/crm_mailgate.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import time -import re -import os - -import binascii -import mx.DateTime -import base64 - -from tools.translate import _ - -import tools -from osv import fields,osv,orm -from osv.orm import except_orm -import collections - -from tools import command_re - -class mailgate_thread(osv.osv): - """ mailgate_thread """ - _name = "mailgate.thread" - _inherit = "mailgate.thread" - - def message_new(self, cr, uid, msg, context): - """ - Automatically calls when new email message arrives - - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks - """ - - mailgate_pool = self.pool.get('email.server.tools') - - subject = msg.get('subject') - body = msg.get('body') - msg_from = msg.get('from') - priority = msg.get('priority') - - vals = { - 'name': subject, - 'email_from': msg_from, - 'email_cc': msg.get('cc'), - 'description': body, - 'user_id': False, - } - if msg.get('priority', False): - vals['priority'] = priority - - res = mailgate_pool.get_partner(cr, uid, msg.get('from')) - if res: - vals.update(res) - res = self.create(cr, uid, vals, context) - cases = self.browse(cr, uid, [res]) - self._history(cr, uid, cases, _('Receive'), history=True, details=body, email_from=msg_from, message_id=msg.get('id')) - - 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 res - - def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}): - """ - @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 ids: List of update mail’s IDs - """ - - if isinstance(ids, (str, int, long)): - ids = [ids] - - msg_from = msg['from'] - vals.update({ - 'description': msg['body'] - }) - if msg.get('priority', False): - vals['priority'] = msg.get('priority') - - maps = { - 'cost':'planned_cost', - 'revenue': 'planned_revenue', - 'probability':'probability' - } - vls = { } - for line in msg['body'].split('\n'): - line = line.strip() - res = command_re.match(line) - if res and maps.get(res.group(1).lower(), False): - key = maps.get(res.group(1).lower()) - vls[key] = res.group(2).lower() - - vals.update(vls) - res = self.write(cr, uid, ids, vals) - cases = self.browse(cr, uid, ids) - message_id = context.get('references_id', False) - self._history(cr, uid, cases, _('Receive'), history=True, details=msg['body'], email_from=msg_from, message_id=message_id) - #getattr(self, act)(cr, uid, select) - return res - - def emails_get(self, cr, uid, ids, context=None): - - """ - Get Emails - @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 ids: List of email’s IDs - @param context: A standard dictionary for contextual values - """ - res = {} - - if isinstance(ids, (str, int, long)): - select = [long(ids)] - else: - select = ids - - for thread in self.browse(cr, uid, select, context=context): - values = collections.defaultdict(set) - - for message in thread.message_ids: - user_email = (message.user_id and message.user_id.address_id and message.user_id.address_id.email) or False - values['user_email'].add(user_email) - values['email_from'].add(message.email_from) - values['email_cc'].add(message.email_cc or False) - - res[str(thread.id)] = dict((key,list(values[key])) for key, value in values.iteritems()) - - return res - - def msg_send(self, cr, uid, id, *args, **argv): - - """ Send The Message - @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 ids: List of email’s IDs - @param *args: Return Tuple Value - @param **args: Return Dictionary of Keyword Value - """ - return True - -mailgate_thread() diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index 8befbd8c084..595e63736bf 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -30,9 +30,8 @@ "access_crm_lead2opportunity_partner","crm.lead2opportunity.partner","model_crm_lead2opportunity_partner","crm.group_crm_user",1,1,1,1 "access_crm_installer","crm.installer.rule","model_crm_installer","base.group_system",1,1,1,1 "access_crm_lead_forward_to_partner","crm.lead.forward.to.partner","model_crm_lead_forward_to_partner","crm.group_crm_user",1,1,1,1 -"access_mailgate_thread","mailgate.thread","model_mailgate_thread","crm.group_crm_user",1,1,1,1 "access_res_partner","res.partner.crm.user","base.model_res_partner","crm.group_crm_user",1,0,0,0 "access_res_partner_address","res.partner.address.crm.user","base.model_res_partner_address","crm.group_crm_user",1,0,0,0 "access_res_partner_category","res.partner.category.crm.user","base.model_res_partner_category","crm.group_crm_user",1,0,0,0 "mail_gateway_mailgate_message","mail_gateway.mailgate.message","mail_gateway.model_mailgate_message","crm.group_crm_user",1,1,1,1 -"mail_gateway_mailgate_thread","mail_gateway.mailgate.thread","model_mailgate_thread","crm.group_crm_user",1,1,1,1 +"mail_gateway_mailgate_thread","mail_gateway.mailgate.thread","mail_gateway.model_mailgate_thread","crm.group_crm_user",1,1,1,1 diff --git a/addons/fetchmail/__openerp__.py b/addons/fetchmail/__openerp__.py index a1276c11643..31037cca63f 100644 --- a/addons/fetchmail/__openerp__.py +++ b/addons/fetchmail/__openerp__.py @@ -24,7 +24,7 @@ { "name" : "Fetchmail Server", "version" : "1.0", - "depends" : ["base"], + "depends" : ["base", 'mail_gateway'], "author" : "Tiny", "description": """Fetchmail: * Fetch email from Pop / IMAP server diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py index aa403cc0090..14981c13837 100644 --- a/addons/fetchmail/fetchmail.py +++ b/addons/fetchmail/fetchmail.py @@ -19,140 +19,57 @@ # ############################################################################## -import os -import re import time -import email -import binascii -import mimetypes - from imaplib import IMAP4 -from imaplib import IMAP4_SSL - +from imaplib import IMAP4_SSL from poplib import POP3 from poplib import POP3_SSL -from email.header import Header -from email.header import decode_header - import netsvc -from osv import osv -from osv import fields -from tools.translate import _ +from osv import osv, fields logger = netsvc.Logger() -def html2plaintext(html, body_id=None, encoding='utf-8'): - ## (c) Fry-IT, www.fry-it.com, 2007 - ## - ## download here: http://www.peterbe.com/plog/html2plaintext - """ from an HTML text, convert the HTML to plain text. - If @body_id is provided then this is the tag where the - body (not necessarily ) starts. - """ - try: - from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment - except: - return html - - urls = [] - if body_id is not None: - strainer = SoupStrainer(id=body_id) - else: - strainer = SoupStrainer('body') - - soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding) - for link in soup.findAll('a'): - title = link.renderContents() - for url in [x[1] for x in link.attrs if x[0]=='href']: - urls.append(dict(url=url, tag=str(link), title=title)) - - html = soup.__str__() - - url_index = [] - i = 0 - for d in urls: - if d['title'] == d['url'] or 'http://'+d['title'] == d['url']: - html = html.replace(d['tag'], d['url']) - else: - i += 1 - html = html.replace(d['tag'], '%s [%s]' % (d['title'], i)) - url_index.append(d['url']) - - html = html.replace('','*').replace('','*') - html = html.replace('','*').replace('','*') - html = html.replace('

','*').replace('

','*') - html = html.replace('

','**').replace('

','**') - html = html.replace('

','**').replace('

','**') - html = html.replace('','/').replace('','/') - - # the only line breaks we respect is those of ending tags and - # breaks - - html = html.replace('\n',' ') - html = html.replace('
', '\n') - html = html.replace('', '\n') - html = html.replace('

', '\n\n') - html = re.sub('', '\n', html) - html = html.replace(' ' * 2, ' ') - - # for all other tags we failed to clean up, just remove then and - # complain about them on the stderr - def desperate_fixer(g): - #print >>sys.stderr, "failed to clean up %s" % str(g.group()) - return ' ' - - html = re.sub('<.*?>', desperate_fixer, html) - - # lstrip all lines - html = '\n'.join([x.lstrip() for x in html.splitlines()]) - - for i, url in enumerate(url_index): - if i == 0: - html += '\n\n' - html += '[%s] %s\n' % (i+1, url) - return html - class email_server(osv.osv): - + _name = 'email.server' _description = "POP/IMAP Server" - + _columns = { - 'name':fields.char('Name', size=256, required=True, readonly=False), - 'active':fields.boolean('Active', required=False), + 'name':fields.char('Name', size=256, required=True, readonly=False), + 'active':fields.boolean('Active', required=False), 'state':fields.selection([ - ('draft','Not Confirmed'), - ('wating','Waiting for Verification'), - ('done','Confirmed'), - ],'State', select=True, readonly=True), - 'server' : fields.char('Server', size=256, required=True, readonly=True, states={'draft':[('readonly',False)]}), - 'port' : fields.integer('Port', required=True, readonly=True, states={'draft':[('readonly',False)]}), + ('draft', 'Not Confirmed'), + ('wating', 'Waiting for Verification'), + ('done', 'Confirmed'), + ], 'State', select=True, readonly=True), + 'server' : fields.char('Server', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}), + 'port' : fields.integer('Port', required=True, readonly=True, states={'draft':[('readonly', False)]}), 'type':fields.selection([ - ('pop','POP Server'), - ('imap','IMAP Server'), - ],'State', select=True, readonly=False), - 'is_ssl':fields.boolean('SSL ?', required=False), - 'attach':fields.boolean('Add Attachments ?', required=False), - 'date': fields.date('Date', readonly=True, states={'draft':[('readonly',False)]}), - 'user' : fields.char('User Name', size=256, required=True, readonly=True, states={'draft':[('readonly',False)]}), - 'password' : fields.char('Password', size=1024, invisible=True, required=True, readonly=True, states={'draft':[('readonly',False)]}), - 'note': fields.text('Description'), - 'action_id':fields.many2one('ir.actions.server', 'Reply Email', required=False, domain="[('state','=','email')]"), - 'object_id': fields.many2one('ir.model',"Model", required=True), - 'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly',False)]}, help="Priority between 0 to 10, select define the order of Processing"), - 'user_id':fields.many2one('res.users', 'User', required=False), + ('pop', 'POP Server'), + ('imap', 'IMAP Server'), + ], 'State', select=True, readonly=False), + 'is_ssl':fields.boolean('SSL ?', required=False), + 'attach':fields.boolean('Add Attachments ?', required=False), + 'date': fields.date('Date', readonly=True, states={'draft':[('readonly', False)]}), + 'user' : fields.char('User Name', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}), + 'password' : fields.char('Password', size=1024, invisible=True, required=True, readonly=True, states={'draft':[('readonly', False)]}), + 'note': fields.text('Description'), + 'action_id':fields.many2one('ir.actions.server', 'Reply Email', required=False, domain="[('state','=','email')]"), + 'object_id': fields.many2one('ir.model', "Model", required=True), + 'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Priority between 0 to 10, select define the order of Processing"), + 'user_id':fields.many2one('res.users', 'User', required=False), } _defaults = { - 'state': lambda *a: "draft", - 'active': lambda *a: True, - 'priority': lambda *a: 5, - 'date': lambda *a: time.strftime('%Y-%m-%d'), - 'user_id': lambda self, cr, uid, ctx: uid, + 'state': lambda *a: "draft", + 'active': lambda *a: True, + 'priority': lambda *a: 5, + 'date': lambda *a: time.strftime('%Y-%m-%d'), + 'user_id': lambda self, cr, uid, ctx: uid, } - + def check_duplicate(self, cr, uid, ids): vals = self.read(cr, uid, ids, ['user', 'password'])[0] cr.execute("select count(id) from email_server where user='%s' and password='%s'" % (vals['user'], vals['password'])) @@ -160,201 +77,41 @@ class email_server(osv.osv): if res: if res[0] > 1: return False - return True + return True _constraints = [ (check_duplicate, 'Warning! Can\'t have duplicate server configuration!', ['user', 'password']) ] - + def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False): port = 0 if server_type == 'pop': port = ssl and 995 or 110 elif server_type == 'imap': port = ssl and 993 or 143 - + return {'value':{'port':port}} - - def _process_email(self, cr, uid, server, message, context={}): - context.update({ - 'server_id':server.id - }) - history_pool = self.pool.get('mail.server.history') - msg_txt = email.message_from_string(message) - message_id = msg_txt.get('Message-ID', False) - - msg = {} - if not message_id: - return False - - fields = msg_txt.keys() - - msg['id'] = message_id - msg['message-id'] = message_id - - def _decode_header(txt): - txt = txt.replace('\r', '') - return ' '.join(map(lambda (x, y): unicode(x, y or 'ascii'), decode_header(txt))) - - if 'Subject' in fields: - msg['subject'] = _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'] = _decode_header(msg_txt.get('From')) - - if 'Delivered-To' in fields: - msg['to'] = _decode_header(msg_txt.get('Delivered-To')) - - if 'Cc' in fields: - msg['cc'] = _decode_header(msg_txt.get('Cc')) - - if 'Reply-To' in fields: - msg['reply'] = _decode_header(msg_txt.get('Reply-To')) - - if 'Date' in fields: - msg['date'] = 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 'X-openerp-caseid' in fields: - msg['caseid'] = msg_txt.get('X-openerp-caseid') - - 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', None): - encoding = msg_txt.get_content_charset() - msg['body'] = msg_txt.get_payload(decode=True) - if encoding: - msg['body'] = msg['body'].decode(encoding).encode('utf-8') - - attachents = {} - if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', None): - body = "" - counter = 1 - for part in msg_txt.walk(): - if part.get_content_maintype() == 'multipart': - continue - - encoding = part.get_content_charset() - - if part.get_content_maintype()=='text': - content = part.get_payload(decode=True) - filename = part.get_filename() - if filename : - attachents[filename] = content - else: - if encoding: - content = unicode(content, encoding) - if part.get_content_subtype() == 'html': - body = html2plaintext(content) - elif part.get_content_subtype() == 'plain': - body = content - elif part.get_content_maintype()=='application' or part.get_content_maintype()=='image' or part.get_content_maintype()=='text': - filename = part.get_filename(); - if filename : - attachents[filename] = part.get_payload(decode=True) - else: - res = part.get_payload(decode=True) - if encoding: - res = res.decode(encoding).encode('utf-8') - - body += res - - msg['body'] = body - msg['attachments'] = attachents - - - res_id = False - if msg.get('references', False): - id = False - ref = msg.get('references') - if '\r\n' in ref: - ref = msg.get('references').split('\r\n') - else: - ref = msg.get('references').split(' ') - - if ref: - hids = history_pool.search(cr, uid, [('name','=',ref[0].strip())]) - if hids: - id = hids[0] - history = history_pool.browse(cr, uid, id) - model_pool = self.pool.get(server.object_id.model) - context.update({ - 'references_id':ref[0] - }) - vals = { - - } - if hasattr(model_pool, 'message_update'): - model_pool.message_update(cr, uid, [history.res_id], vals, msg, context=context) - else: - logger.notifyChannel('imap', netsvc.LOG_WARNING, 'method def message_update is not define in model %s' % (model_pool._name)) - return False - res_id = id - else: - model_pool = self.pool.get(server.object_id.model) - if hasattr(model_pool, 'message_new'): - res_id = model_pool.message_new(cr, uid, msg, context) - else: - logger.notifyChannel('imap', netsvc.LOG_WARNING, 'method def message_new is not define in model %s' % (model_pool._name)) - return False - - if server.attach: - 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': server.object_id.model, - 'res_id': res_id, - } - self.pool.get('ir.attachment').create(cr, uid, data_attach) - - if server.action_id: - action_pool = self.pool.get('ir.actions.server') - action_pool.run(cr, uid, [server.action_id.id], {'active_id':res_id, 'active_ids':[res_id]}) - - res = { - 'name': message_id, - 'res_id': res_id, - 'server_id': server.id, - 'note': msg.get('body', msg.get('from')), - 'ref_id':msg.get('references', msg.get('id')), - 'type':server.type - } - his_id = history_pool.create(cr, uid, res) - - return res_id def set_draft(self, cr, uid, ids, context={}): self.write(cr, uid, ids , {'state':'draft'}) return True - + def button_fetch_mail(self, cr, uid, ids, context={}): self.fetch_mail(cr, uid, ids) # sendmail_thread = threading.Thread(target=self.fetch_mail, args=(cr, uid, ids)) # sendmail_thread.start() return True - + def _fetch_mails(self, cr, uid, ids=False, context={}): if not ids: ids = self.search(cr, uid, []) return self.fetch_mail(cr, uid, ids, context) - - def fetch_mail(self, cr, uid, ids, context={}): + def fetch_mail(self, cr, uid, ids, context={}): + email_tool = self.pool.get('email.server.tools') for server in self.browse(cr, uid, ids, context): logger.notifyChannel('imap', netsvc.LOG_INFO, 'fetchmail start checking for new emails on %s' % (server.name)) - + count = 0 try: if server.type == 'imap': @@ -363,17 +120,21 @@ class email_server(osv.osv): imap_server = IMAP4_SSL(server.server, int(server.port)) else: imap_server = IMAP4(server.server, int(server.port)) - + imap_server.login(server.user, server.password) imap_server.select() result, data = imap_server.search(None, '(UNSEEN)') for num in data[0].split(): result, data = imap_server.fetch(num, '(RFC822)') - if self._process_email(cr, uid, server, data[0][1], context): + res_id = email_tool.process_email(cr, uid, server.object_id.model, data[0][1], attach=server.attach, server_id=server.id, server_type=server.type, context=context) + if res_id and server.action_id: + action_pool = self.pool.get('ir.actions.server') + action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]}) + imap_server.store(num, '+FLAGS', '\\Seen') - count += 1 + count += 1 logger.notifyChannel('imap', netsvc.LOG_INFO, 'fetchmail fetch/process %s email(s) from %s' % (count, server.name)) - + imap_server.close() imap_server.logout() elif server.type == 'pop': @@ -382,7 +143,7 @@ class email_server(osv.osv): pop_server = POP3_SSL(server.server, int(server.port)) else: pop_server = POP3(server.server, int(server.port)) - + #TODO: use this to remove only unread messages #pop_server.user("recent:"+server.user) pop_server.user(server.user) @@ -393,79 +154,38 @@ class email_server(osv.osv): for num in range(1, numMsgs + 1): (header, msges, octets) = pop_server.retr(num) msg = '\n'.join(msges) - self._process_email(cr, uid, server, msg, context) + res_id = email_tool.process_email(cr, uid, server.object_id.model, data[0][1], attach=server.attach, server_id=server.id, server_type=server.type, context=context) + if res_id and server.action_id: + action_pool = self.pool.get('ir.actions.server') + action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]}) + pop_server.dele(num) pop_server.quit() - + logger.notifyChannel('imap', netsvc.LOG_INFO, 'fetchmail fetch %s email(s) from %s' % (numMsgs, server.name)) - + self.write(cr, uid, [server.id], {'state':'done'}) except Exception, e: logger.notifyChannel(server.type, netsvc.LOG_WARNING, '%s' % (e)) - + return True email_server() -class mail_server_history(osv.osv): +class mailgate_message(osv.osv): + + _inherit = "mailgate.message" - _name = "mail.server.history" - _description = "Mail Server History" - _columns = { - 'name': fields.char('Message Id', size=256, readonly=True, help="Message Id in Email Server.", select=True), - 'ref_id': fields.char('Referance Id', size=256, readonly=True, help="Message Id in Email Server.", select=True), - 'res_id': fields.integer("Resource ID", readonly=True, select=True), - 'server_id': fields.many2one('email.server',"Mail Server", readonly=True, select=True), - 'model_id':fields.related('server_id', 'object_id', type='many2one', relation='ir.model', string='Model', readonly=True, select=True), - 'note': fields.text('Notes', readonly=True), - 'create_date': fields.datetime('Created Date', readonly=True), + 'server_id': fields.many2one('email.server', "Mail Server", readonly=True, select=True), 'type':fields.selection([ - ('pop','POP Server'), - ('imap','IMAP Server'), - ],'State', select=True, readonly=True), + ('pop', 'POP Server'), + ('imap', 'IMAP Server'), + ], 'State', select=True, readonly=True), } _order = 'id desc' - -mail_server_history() -class fetchmail_tool(osv.osv): +mailgate_message() - _name = 'email.server.tools' - _description = "Email Tools" - _auto = False - - def to_email(self, text): - _email = re.compile(r'.*<.*@.*\..*>', re.UNICODE) - def record(path): - eml = path.group() - index = eml.index('<') - eml = eml[index:-1].replace('<','').replace('>','') - return eml - - bits = _email.sub(record, text) - return bits - - def get_partner(self, cr, uid, from_email, 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 from_email: email address based on that function will search for the correct - """ - - res = { - 'partner_address_id': False, - 'partner_id': False - } - from_email = self.to_email(from_email) - address_ids = self.pool.get('res.partner.address').search(cr, uid, [('email', '=', from_email)]) - if address_ids: - address = self.pool.get('res.partner.address').browse(cr, uid, address_ids[0]) - res['partner_address_id'] = address_ids[0] - res['partner_id'] = address.partner_id.id - - return res - -fetchmail_tool() +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/fetchmail/fetchmail_view.xml b/addons/fetchmail/fetchmail_view.xml index 510b31b6131..330f9fec349 100644 --- a/addons/fetchmail/fetchmail_view.xml +++ b/addons/fetchmail/fetchmail_view.xml @@ -103,82 +103,54 @@ action="action_email_server_tree_imap" /> - - mail.server.history.tree - mail.server.history - tree - - - - - - - - - - - - mail.server.history.form - mail.server.history - form - -
- + + mailgate.message.tree + mailgate.message + tree + + + - - - - - - - - - - - - - - - - -
- + + + + + res_model="mailgate.message" src_model="email.server"/> - + Received Email History - mail.server.history + mailgate.message form tree,form - + {'type':'imap'} [('type','=','imap')] + id="menu_action_mailgate_message_tree" + action="action_mailgate_message_tree"/> - + Received Email History - mail.server.history + mailgate.message form tree,form - + {'type':'pop'} [('type','=','pop')] + id="menu_action_mailgate_message_tree_pop" + action="action_mailgate_message_tree_pop"/> diff --git a/addons/fetchmail/security/ir.model.access.csv b/addons/fetchmail/security/ir.model.access.csv index 907aa187913..7aa6c84d22b 100644 --- a/addons/fetchmail/security/ir.model.access.csv +++ b/addons/fetchmail/security/ir.model.access.csv @@ -1,4 +1,2 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_email_server","email.server","model_email_server",,1,1,1,1 -"access_mail_server_history","mail.server.history","model_mail_server_history",,1,1,1,1 -"access_email_server_tools","email.server.tools","model_email_server_tools",,1,1,1,1 diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index cf194a60bb4..9d6c2cfbbbb 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -21,6 +21,11 @@ from osv import fields, osv from crm import crm +import tools +import collections +import binascii +import tools +from tools.translate import _ AVAILABLE_STATES = [ ('draft', 'New'), @@ -240,6 +245,131 @@ class hr_applicant(osv.osv, crm.crm_case): context.update({'survey_id': record.survey.id, 'response_id' : [record.response], 'response_no':0, }) value = self.pool.get("survey").action_print_survey(cr, uid, ids, context) return value + + def message_new(self, cr, uid, msg, context): + """ + Automatically calls when new email message arrives + + @param self: The object pointer + @param cr: the current row, from the database cursor, + @param uid: the current user’s ID for security checks + """ + + mailgate_pool = self.pool.get('email.server.tools') + + subject = msg.get('subject') + body = msg.get('body') + msg_from = msg.get('from') + priority = msg.get('priority') + + vals = { + 'name': subject, + 'email_from': msg_from, + 'email_cc': msg.get('cc'), + 'description': body, + 'user_id': False, + } + if msg.get('priority', False): + vals['priority'] = priority + + res = mailgate_pool.get_partner(cr, uid, msg.get('from')) + 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 res + + def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}): + """ + @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 ids: List of update mail’s IDs + """ + + if isinstance(ids, (str, int, long)): + ids = [ids] + + msg_from = msg['from'] + vals.update({ + 'description': msg['body'] + }) + if msg.get('priority', False): + vals['priority'] = msg.get('priority') + + maps = { + 'cost':'planned_cost', + 'revenue': 'planned_revenue', + 'probability':'probability' + } + vls = { } + for line in msg['body'].split('\n'): + line = line.strip() + res = tools.misc.command_re.match(line) + if res and maps.get(res.group(1).lower(), False): + key = maps.get(res.group(1).lower()) + vls[key] = res.group(2).lower() + + vals.update(vls) + res = self.write(cr, uid, ids, vals) + return res + + def emails_get(self, cr, uid, ids, context=None): + + """ + Get Emails + @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 ids: List of email’s IDs + @param context: A standard dictionary for contextual values + """ + res = {} + + if isinstance(ids, (str, int, long)): + select = [long(ids)] + else: + select = ids + + for thread in self.browse(cr, uid, select, context=context): + values = collections.defaultdict(set) + + for message in thread.message_ids: + user_email = (message.user_id and message.user_id.address_id and message.user_id.address_id.email) or False + values['user_email'].add(user_email) + values['email_from'].add(message.email_from) + values['email_cc'].add(message.email_cc or False) + values['priority'] = thread.priority + + res[thread.id] = dict((key,list(values[key])) for key, value in values.iteritems()) + + return res + + def msg_send(self, cr, uid, id, *args, **argv): + + """ Send The Message + @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 ids: List of email’s IDs + @param *args: Return Tuple Value + @param **args: Return Dictionary of Keyword Value + """ + return True + hr_applicant() class hr_job(osv.osv): diff --git a/addons/mail_gateway/mail_gateway.py b/addons/mail_gateway/mail_gateway.py index 7a7c00c9deb..edb723a3ba0 100644 --- a/addons/mail_gateway/mail_gateway.py +++ b/addons/mail_gateway/mail_gateway.py @@ -27,6 +27,7 @@ import email from email.header import decode_header import base64 import re +from tools.translate import _ class mailgate_thread(osv.osv): ''' @@ -37,14 +38,24 @@ class mailgate_thread(osv.osv): _rec_name = 'thread' _columns = { - 'thread': fields.char('Thread', size=32, required=False), - 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('history', '=', True)], readonly=True), - 'log_ids': fields.one2many('mailgate.message', 'res_id', 'Logs', domain=[('history', '=', False)], readonly=True), - 'model': fields.char('Model Name', size=64, required=False), - 'res_id': fields.integer('Resource ID'), - } - - def _history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, email_from=False, message_id=False, attach=None, context=None): + 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('history', '=', True)]), + 'log_ids': fields.one2many('mailgate.message', 'res_id', 'Logs', domain=[('history', '=', False)]), + } + + def message_new(self, cr, uid, msg, context): + raise Exception, _('Method is not implemented') + + def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}): + raise Exception, _('Method is not implemented') + + def emails_get(self, cr, uid, ids, context=None): + raise Exception, _('Method is not implemented') + + def msg_send(self, cr, uid, id, *args, **argv): + raise Exception, _('Method is not implemented') + + def _history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, \ + email_from=False, message_id=False, references=None, attach=None, context=None): """ @param self: The object pointer @param cr: the current row, from the database cursor, @@ -98,6 +109,7 @@ class mailgate_thread(osv.osv): (hasattr(case, 'user_id') and case.user_id and case.user_id.address_id and \ case.user_id.address_id.email) or tools.config.get('email_from', False), 'partner_id': hasattr(case, 'partner_id') and (case.partner_id and case.partner_id.id or False) or False, + 'references': references, 'message_id': message_id, 'attachment_ids': [(6, 0, attachments)] } @@ -125,7 +137,8 @@ class mailgate_message(osv.osv): 'email_to': fields.char('Email To', size=84), 'email_cc': fields.char('Email CC', size=84), 'email_bcc': fields.char('Email BCC', size=84), - 'message_id': fields.char('Message Id', size=1024, readonly=True, help="Message Id on Email Server.", select=True), + 'message_id': fields.char('Message Id', size=1024, readonly=True, help="Message Id on Email.", select=True), + 'references': fields.text('References', readonly=True, help="Referencess emails."), 'description': fields.text('Description'), 'partner_id': fields.many2one('res.partner', 'Partner', required=False), 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'), @@ -133,3 +146,320 @@ class mailgate_message(osv.osv): mailgate_message() +class mailgate_tool(osv.osv_memory): + + _name = 'email.server.tools' + _description = "Email Server Tools" + + def _to_decode(self, s, charsets): + for charset in charsets: + if charset: + try: + return s.decode(charset) + except UnicodeError: + pass + return s.decode('latin1') + + def _decode_header(self, text): + if text: + text = decode_header(text.replace('\r', '')) + return ''.join(map(lambda x:self._to_decode(x[0], [x[1]]), text or [])) + + def to_email(self, text): + _email = re.compile(r'.*<.*@.*\..*>', re.UNICODE) + def record(path): + eml = path.group() + index = eml.index('<') + eml = eml[index:-1].replace('<', '').replace('>', '') + return eml + + bits = _email.sub(record, text) + return bits + + def history(self, cr, uid, model, res_ids, msg, attach): + """This function creates history for mails fetched + @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 model: OpenObject Model + @param res_ids: Ids of the record of OpenObject model created + @param msg: Email details + @param attach: Email attachments + """ + if isinstance(res_ids, (int, long)): + res_ids = [res_ids] + + msg_pool = self.pool.get('mailgate.message') + for res_id in res_ids: + msg_data = { + 'name': msg.get('subject', 'No subject'), + 'date': msg.get('date') , + 'description': msg.get('body', msg.get('from')), + 'history': True, + 'res_model': model, + 'email_cc': msg.get('cc'), + 'email_from': msg.get('from'), + 'email_to': msg.get('to'), + 'message_id': msg.get('message-id'), + 'references': msg.get('references'), + 'res_id': res_id, + 'user_id': uid, + 'attachment_ids': [(6, 0, attach)] + } + msg_id = msg_pool.create(cr, uid, msg_data) + return True + + def email_send(self, cr, uid, model, res_id, msg, from_email=False, email_default=False): + """This function Sends return email on submission of Fetched email in OpenERP database + @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 model: OpenObject Model + @param res_id: Id of the record of OpenObject model created from the Email details + @param msg: Email details + @param email_default: Default Email address in case of any Problem + """ + history_pool = self.pool.get('mailgate.message') + model_pool = self.pool.get(model) + from_email = from_email or tools.config.get('email_from', None) + message = email.message_from_string(str(msg)) + subject = "[%s] %s" %(res_id, self._decode_header(message['Subject'])) + #msg_mails = [] + #mails = [self._decode_header(message['From']), self._decode_header(message['To'])] + #mails += self._decode_header(message.get('Cc', '')).split(',') + + values = {} + if hasattr(model_pool, 'emails_get'): + values = model_pool.emails_get(cr, uid, [res_id]) + emails = values.get(res_id, {}) + + priority = emails.get('priority', [3])[0] + em = emails['user_email'] + emails['email_from'] + emails['email_cc'] + msg_mails = map(self.to_email, filter(None, em)) + + #mm = [self._decode_header(message['From']), self._decode_header(message['To'])] + #mm += self._decode_header(message.get('Cc', '')).split(',') + + #msg_mails = map(self.to_email, filter(None, mm)) + + encoding = message.get_content_charset() + message['body'] = message.get_payload(decode=True) + if encoding: + message['body'] = tools.ustr(message['body'].decode(encoding)) + + body = _(""" +Hello %s, + + Your Request ID: %s + +Thanks + +-------- Original Message -------- +%s +""") %(self._decode_header(message['From']), res_id, message['body']) + res = None + try: + res = tools.email_send(from_email, msg_mails, subject, body, openobject_id=res_id) + except Exception, e: + if email_default: + temp_msg = '[%s] %s'%(res_id, self._decode_header(message['Subject'])) + del message['Subject'] + message['Subject'] = '[OpenERP-FetchError] %s' %(temp_msg) + tools.email_send(from_email, email_default, message.get('Subject'), message.get('body'), openobject_id=res_id) + return res + + def process_email(self, cr, uid, model, message, attach=True, context=None): + """This function Processes email and create record for given OpenERP model + @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 model: OpenObject Model + @param message: Email details + @param attach: Email attachments + @param context: A standard dictionary for contextual values""" + + model_pool = self.pool.get(model) + if not context: + context = {} + res_id = False + # Create New Record into particular model + def create_record(msg): + if hasattr(model_pool, 'message_new'): + res_id = model_pool.message_new(cr, uid, msg, context) + else: + data = { + 'name': msg.get('subject'), + 'email_from': msg.get('from'), + 'email_cc': msg.get('cc'), + 'user_id': False, + 'description': 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) + + att_ids = [] + 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 + + history_pool = self.pool.get('mailgate.message') + msg_txt = email.message_from_string(message) + message_id = msg_txt.get('Message-ID', False) + msg = {} + if not message_id: + return False + + 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')) + + 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'] = 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 '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', None): + encoding = msg_txt.get_content_charset() + msg['body'] = msg_txt.get_payload(decode=True) + if encoding: + msg['body'] = tools.ustr(msg['body']) + + attachments = {} + if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', None): + body = "" + counter = 1 + for part in msg_txt.walk(): + if part.get_content_maintype() == 'multipart': + continue + + encoding = part.get_content_charset() + + if part.get_content_maintype()=='text': + content = part.get_payload(decode=True) + filename = part.get_filename() + if filename : + attachments[filename] = content + else: + if encoding: + content = unicode(content, encoding) + if part.get_content_subtype() == 'html': + body = tools.html2plaintext(content) + elif part.get_content_subtype() == 'plain': + body = content + elif part.get_content_maintype()=='application' or part.get_content_maintype()=='image' or part.get_content_maintype()=='text': + filename = part.get_filename(); + if filename : + attachments[filename] = part.get_payload(decode=True) + else: + res = part.get_payload(decode=True) + if encoding: + res = tools.ustr(res) + + body += res + + msg['body'] = body + msg['attachments'] = attachments + res_ids = [] + new_res_id = False + if msg.get('references', False): + references = msg.get('references') + if '\r\n' in references: + references = msg.get('references').split('\r\n') + else: + references = msg.get('references').split(' ') + for ref in references: + ref = ref.strip() + res_id = tools.misc.reference_re.search(ref) + if res_id: + res_id = res_id.group(1) + else: + res_id = tools.misc.res_re.search(msg['subject']) + if res_id: + res_id = res_id.group(1) + if res_id: + res_id = int(res_id) + res_ids.append(res_id) + model_pool = self.pool.get(model) + + vals = {} + if hasattr(model_pool, 'message_update'): + model_pool.message_update(cr, uid, [res_id], vals, msg, context=context) + + if not len(res_ids): + new_res_id = create_record(msg) + res_ids = [new_res_id] + # Store messages + 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'), + message_id = msg.get('message-id'), + references = msg.get('references', False), + attach = msg.get('attachments', {}).items(), + context = {'model' : model}) + else: + self.history(cr, uid, model, res_ids, msg, att_ids) + return new_res_id + + def get_partner(self, cr, uid, from_email, context=None): + """This function returns partner Id based on email passed + @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 from_email: email address based on that function will search for the correct + """ + address_pool = self.pool.get('res.partner.address') + res = { + 'partner_address_id': False, + 'partner_id': False + } + from_email = self.to_email(from_email) + address_ids = address_pool.search(cr, uid, [('email', '=', from_email)]) + if address_ids: + address = address_pool.browse(cr, uid, address_ids[0]) + res['partner_address_id'] = address_ids[0] + res['partner_id'] = address.partner_id.id + + return res + +mailgate_tool() + + diff --git a/addons/mail_gateway/mail_gateway_view.xml b/addons/mail_gateway/mail_gateway_view.xml index 95dce8054c1..09a24f92772 100644 --- a/addons/mail_gateway/mail_gateway_view.xml +++ b/addons/mail_gateway/mail_gateway_view.xml @@ -29,7 +29,7 @@ - + @@ -76,28 +76,30 @@ form
- + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -163,10 +165,33 @@ - Mailgateway Threads - mailgate.thread + Mailgateway Threads + mailgate.thread + tree,form + form + + + + + + tree + + + + + + form + + + + + + + Mailgateway Messages + mailgate.message tree,form form + [('history', '=', True)] @@ -195,4 +220,4 @@ /> - \ No newline at end of file + diff --git a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py b/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py index b9a9eefee15..f294c819c78 100755 --- a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py +++ b/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py @@ -21,104 +21,15 @@ # ########################################################################################### -import re -import smtplib -import email -import mimetypes -from email.Header import decode_header -from email.MIMEText import MIMEText -import xmlrpclib -import os -import binascii -import time -import socket import logging -import sys import optparse - -email_re = re.compile(r"([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6})") -case_re = re.compile(r"\[([0-9]+)\]", re.UNICODE) -command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE) -reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE) - -priorities = { - '1': '1 (Highest)', - '2': '2 (High)', - '3': '3 (Normal)', - '4': '4 (Low)', - '5': '5 (Lowest)', -} - -def html2plaintext(html, body_id=None, encoding='utf-8'): - ## (c) Fry-IT, www.fry-it.com, 2007 - ## - ## download here: http://www.peterbe.com/plog/html2plaintext - - - """ from an HTML text, convert the HTML to plain text. - If @body_id is provided then this is the tag where the - body (not necessarily ) starts. - """ - try: - from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment - except: - return html - - urls = [] - if body_id is not None: - strainer = SoupStrainer(id=body_id) - else: - strainer = SoupStrainer('body') - - soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding) - for link in soup.findAll('a'): - title = unicode(link) - for url in [x[1] for x in link.attrs if x[0]=='href']: - urls.append(dict(url=url, tag=unicode(link), title=title)) - - html = unicode(soup) - - url_index = [] - i = 0 - for d in urls: - if d['title'] == d['url'] or 'http://'+d['title'] == d['url']: - html = html.replace(d['tag'], d['url']) - else: - i += 1 - html = html.replace(d['tag'], '%s [%s]' % (d['title'], i)) - url_index.append(d['url']) - - html = html.replace('', '*').replace('', '*') - html = html.replace('', '*').replace('', '*') - html = html.replace('

', '*').replace('

', '*') - html = html.replace('

', '**').replace('

', '**') - html = html.replace('

', '**').replace('

', '**') - html = html.replace('', '/').replace('', '/') - - # the only line breaks we respect is those of ending tags and - # breaks - - html = html.replace('\n', ' ') - html = html.replace('
', '\n') - html = html.replace('', '\n') - html = html.replace('

', '\n\n') - html = re.sub('', '\n', html) - html = html.replace(' ' * 2, ' ') - - html = re.sub('<.*?>', ' ', html) - - # lstrip all lines - html = '\n'.join([x.lstrip() for x in html.splitlines()]) - - for i, url in enumerate(url_index): - if i == 0: - html += '\n\n' - html += '[%s] %s\n' % (i+1, url) - return html +import sys +import xmlrpclib +import email class rpc_proxy(object): - def __init__(self, uid, passwd, host='localhost', port=8069, path='object', dbname='terp'): - self.rpc = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (host, port, path)) + def __init__(self, uid, passwd, host='localhost', port=8069, path='object', dbname='terp'): + self.rpc = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (host, port, path), allow_none=True) self.user_id = uid self.passwd = passwd self.dbname = dbname @@ -130,296 +41,28 @@ class email_parser(object): def __init__(self, uid, password, model, email, email_default, dbname, host, port): self.rpc = rpc_proxy(uid, password, host=host, port=port, dbname=dbname) try: + self.model_id = int(model) self.model = str(model) except: + self.model_id = self.rpc('ir.model', 'search', [('model', '=', model)])[0] self.model = str(model) self.email = email self.email_default = email_default - self.canal_id = False - - def email_get(self, email_from): - res = email_re.search(email_from) - return res and res.group(1) - - def partner_get(self, email): - mail = self.email_get(email) - adr_ids = self.rpc('res.partner.address', 'search', [('email', '=', mail)]) - if not adr_ids: - return {} - adr = self.rpc('res.partner.address', 'read', adr_ids, ['partner_id']) - return { - 'partner_address_id': adr[0]['id'], - 'partner_id': adr[0].get('partner_id', False) and adr[0]['partner_id'][0] or False - } - - def _to_decode(self, s, charsets): - for charset in charsets: - if charset: - try: - return s.decode(charset) - except UnicodeError: - pass - return s.decode('latin1') - - def _decode_header(self, text): - if text: - text = decode_header(text.replace('\r', '')) - return ''.join(map(lambda x:self._to_decode(x[0], [x[1]]), text or [])) - - def msg_new(self, msg): - message = self.msg_body_get(msg) - msg_subject = self._decode_header(msg['Subject']) - msg_from = self._decode_header(msg['From']) - msg_to = self._decode_header(msg['To']) - msg_cc = self._decode_header(msg['Cc'] or '') - data = { - 'name': msg_subject, - 'email_from': msg_from, - 'email_cc': msg_cc, - 'user_id': False, - 'description': message['body'], - 'state' : 'draft', - } - data.update(self.partner_get(msg_from)) - - values = { - 'message_ids' : [ - (0, 0, { - 'res_model' : self.model, - 'date' : time.strftime('%Y-%m-%d %H:%M:%S'), - 'description' : message['body'], - 'email_from' : msg_from, - 'email_to' : msg_to, - 'name' : 'Receive', - 'history' : True, - 'user_id' : self.rpc.user_id, - } - ) - ] - } - oid = self.rpc(self.model, 'create', data) - - attachments = message['attachment'] - for attach in attachments or []: - data_attach = { - 'name': str(attach), - 'datas': binascii.b2a_base64(str(attachments[attach])), - 'datas_fname': str(attach), - 'description': 'Mail attachment', - 'res_model': self.model, - 'res_id': oid - } - self.rpc('ir.attachment', 'create', data_attach) - - return (oid, ) - - def msg_body_get(self, msg): - message = { - 'body' : '', - 'attachment' : {}, - } - attachment = message['attachment']; - counter = 1; - - for part in msg.walk(): - if part.get_content_maintype() == 'multipart': - continue - - if part.get_content_maintype()=='text': - buf = part.get_payload(decode=True) - if buf: - txt = self._to_decode(buf, part.get_charsets()) - txt = re.sub("<\/?(\w)>", '', txt) - if txt and part.get_content_subtype() == 'plain': - message['body'] += txt - elif txt and part.get_content_subtype() == 'html': - message['body'] += html2plaintext(txt) - - filename = part.get_filename(); - if filename : - attachment[filename] = part.get_payload(decode=True); - - elif part.get_content_maintype() in ('application', 'image', 'text'): - filename = part.get_filename(); - if not filename : - filename = 'attach_file'+str(counter); - counter += 1; - - attachment[filename] = part.get_payload(decode=True); - message['attachment'] = attachment - #end for - return message - - def msg_user(self, msg, id): - body = self.msg_body_get(msg) - - # handle email body commands (ex: Set-State: Draft) - actions = {} - body_data='' - for line in body['body'].split('\n'): - res = command_re.match(line) - if res: - actions[res.group(1).lower()] = res.group(2).lower() - else: - body_data += line+'\n' - body['body'] = body_data - - data = { -# 'description': body['body'], - } - act = 'case_open' - if 'state' in actions: - if actions['state'] in ['draft', 'close', 'cancel', 'open', 'pending']: - act = 'case_' + actions['state'] - - for k1, k2 in [('cost', 'planned_cost'), ('revenue', 'planned_revenue'), ('probability', 'probability')]: - try: - data[k2] = float(actions[k1]) - except: - pass - - if 'priority' in actions: - if actions['priority'] in ('1', '2', '3', '4', '5'): - data['priority'] = actions['priority'] - - if 'partner' in actions: - data['email_from'] = actions['partner'][:128] - - if 'user' in actions: - uids = self.rpc('res.users', 'name_search', actions['user']) - if uids: - data['user_id'] = uids[0][0] - - self.rpc(self.model, act, [id]) - self.rpc(self.model, 'write', [id], data) - attachments = body['attachment'] - for attach in attachments or []: - data_attach = { - 'name': str(attach), - 'datas': binascii.b2a_base64(str(attachments[attach])), - 'datas_fname': str(attach), - 'description': 'Mail attachment', - 'res_model': self.model, - 'res_id': id - } - self.rpc('ir.attachment', 'create', data_attach) - - self.create_message(id, msg['From'], msg['To'], 'Send', message['body']) - - return id - - def create_message(self, oid, email_from, email_to, name, body): - """ - This functions creates a message in the thread - - > create_message(id, msg['From'], msg['To'], 'Send', message['body']) - """ - values = { - 'res_model' : self.model, - 'res_id' : oid, - 'date' : time.strftime('%Y-%m-%d %H:%M:%S'), - 'description' : body, - 'email_from' : self._decode_header(email_from), - 'email_to' : self._decode_header(email_to), - 'name' : name, - 'history' : True, - 'user_id' : self.rpc.user_id, - } - return self.rpc('mailgate.message', 'create', values) - - def msg_send(self, msg, emails, priority=None): - if not emails: - return False - - msg['To'] = emails[0] - if len(emails) > 1: - msg['Cc'] = ','.join(emails[1:]) - msg['Reply-To'] = self.email - - if priority: - msg['X-Priority'] = priorities.get(priority, '3 (Normal)') - - s = smtplib.SMTP() - s.connect() - s.sendmail(self.email, emails, msg.as_string()) - s.close() - return True - - def msg_partner(self, msg, id): - message = self.msg_body_get(msg) - body = message['body'] - act = 'case_open' - self.rpc(self.model, act, [id]) - attachments = message['attachment'] - for attach in attachments or []: - data_attach = { - 'name': str(attach), - 'datas': binascii.b2a_base64(str(attachments[attach])), - 'datas_fname': str(attach), - 'description': 'Mail attachment', - 'res_model': self.model, - 'res_id': id - } - self.rpc('ir.attachment', 'create', data_attach) - - self.create_message(id, msg['From'], msg['To'], 'Send', message['body']) - return id - - def msg_test(self, msg, case_str): - if not case_str: - return (False, False) - case_id = int(case_str) - emails = self.rpc(self.model, 'emails_get', [case_id]) - return (case_id, emails) + - def parse(self, msg): - case_str = reference_re.search(msg.get('References', '')) - if case_str: - case_str = case_str.group(1) - else: - case_str = case_re.search(msg.get('Subject', '')) - if case_str: - case_str = case_str.group(1) - - logging.info("email: %s -> %s -- %s", msg['From'], self.email, msg['Subject']) - (case_id, emails) = self.msg_test(msg, case_str) - if case_id: - user_email = filter(None, emails['user_email'])[0] - - if user_email and self.email_get(user_email) == self.email_get(self._decode_header(msg['From'])): - self.msg_user(msg, case_id) - else: - self.msg_partner(msg, case_id) - else: - case_id = self.msg_new(msg) - subject = self._decode_header(msg['Subject']) - msg['Subject'] = "[%s] %s" % (case_id, subject,) - msg['Message-Id'] = "<%s-openerpcrm-%s@%s>" % (time.time(), case_id, socket.gethostname(),) - - logging.info(" case: %r", case_id) - - values = self.rpc(self.model, 'emails_get', [case_id]) - - emails = values[str(thread_id)] - - priority = emails.get('piority', [3])[0] - em = emails['user_email'] + emails['email_from'] + emails['email_cc'] - emails = map(self.email_get, filter(None, em)) - - mm = [self._decode_header(msg['From']), self._decode_header(msg['To'])]+self._decode_header(msg.get('Cc', '')).split(',') - msg_mails = map(self.email_get, filter(None, mm)) - - emails = filter(lambda m: m and m not in msg_mails, emails) + def parse(self, message): try: - self.msg_send(msg, emails, priority) - except: - if self.email_default: - a = self._decode_header(msg['Subject']) - msg['Subject'] = '[OpenERP-CaseError] ' + a - self.msg_send(msg, self.email_default.split(',')) - return case_id, emails + res_id = self.rpc('email.server.tools', 'process_email', self.model, message) + except Exception, e: + res_id = False + +# Reply mail + if res_id: + self.rpc('email.server.tools', 'email_send', self.model, res_id, message, self.email, self.email_default) + + return res_id if __name__ == '__main__': parser = optparse.OptionParser(usage='usage: %prog [options]', version='%prog v1.0') @@ -442,7 +85,7 @@ if __name__ == '__main__': parser = email_parser(options.userid, options.password, options.model, options.email, options.default, dbname=options.dbname, host=options.host, port=options.port) - msg_txt = email.message_from_file(sys.stdin) + msg_txt = sys.stdin.read() parser.parse(msg_txt) diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 5a709876f1a..3523485f542 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -31,6 +31,9 @@ from crm import crm from osv import fields,osv,orm from osv.orm import except_orm from tools.translate import _ +import collections +import binascii +import tools class project_issue(osv.osv, crm.crm_case): _name = "project.issue" @@ -299,5 +302,129 @@ class project_issue(osv.osv, crm.crm_case): self.write(cr, uid, [case.id], data) return True + def message_new(self, cr, uid, msg, context): + """ + Automatically calls when new email message arrives + + @param self: The object pointer + @param cr: the current row, from the database cursor, + @param uid: the current user’s ID for security checks + """ + + mailgate_pool = self.pool.get('email.server.tools') + + subject = msg.get('subject') + body = msg.get('body') + msg_from = msg.get('from') + priority = msg.get('priority') + + vals = { + 'name': subject, + 'email_from': msg_from, + 'email_cc': msg.get('cc'), + 'description': body, + 'user_id': False, + } + if msg.get('priority', False): + vals['priority'] = priority + + res = mailgate_pool.get_partner(cr, uid, msg.get('from')) + 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 res + + def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}): + """ + @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 ids: List of update mail’s IDs + """ + + if isinstance(ids, (str, int, long)): + ids = [ids] + + msg_from = msg['from'] + vals.update({ + 'description': msg['body'] + }) + if msg.get('priority', False): + vals['priority'] = msg.get('priority') + + maps = { + 'cost':'planned_cost', + 'revenue': 'planned_revenue', + 'probability':'probability' + } + vls = { } + for line in msg['body'].split('\n'): + line = line.strip() + res = tools.misc.command_re.match(line) + if res and maps.get(res.group(1).lower(), False): + key = maps.get(res.group(1).lower()) + vls[key] = res.group(2).lower() + + vals.update(vls) + res = self.write(cr, uid, ids, vals) + return res + + def emails_get(self, cr, uid, ids, context=None): + + """ + Get Emails + @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 ids: List of email’s IDs + @param context: A standard dictionary for contextual values + """ + res = {} + + if isinstance(ids, (str, int, long)): + select = [long(ids)] + else: + select = ids + + for thread in self.browse(cr, uid, select, context=context): + values = collections.defaultdict(set) + + for message in thread.message_ids: + user_email = (message.user_id and message.user_id.address_id and message.user_id.address_id.email) or False + values['user_email'].add(user_email) + values['email_from'].add(message.email_from) + values['email_cc'].add(message.email_cc or False) + values['priority'] = thread.priority + + res[thread.id] = dict((key,list(values[key])) for key, value in values.iteritems()) + + return res + + def msg_send(self, cr, uid, id, *args, **argv): + + """ Send The Message + @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 ids: List of email’s IDs + @param *args: Return Tuple Value + @param **args: Return Dictionary of Keyword Value + """ + return True + project_issue()