1
##############################################################################
3
# OpenERP, Open Source Management Solution
4
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU Affero General Public License as
8
# published by the Free Software Foundation, either version 3 of the
9
# License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU Affero General Public License for more details.
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
##############################################################################
26
from datetime import datetime
27
from datetime import timedelta
39
from email import Encoders
40
from optparse import OptionParser
41
from email.Message import Message
42
from email.MIMEBase import MIMEBase
43
from email.MIMEText import MIMEText
44
from email.MIMEMultipart import MIMEMultipart
45
from email.Utils import COMMASPACE, formatdate
51
from osv import fields
53
from tools.translate import _
56
'not_active' : "Please activate Email Server, without activating you can not send Email(s).",
57
'server_stop' : 'Please start Email Server, without starting you can not send Email(s).',
58
'server_not_confirm' : 'Please Verify Email Server, without verifying you can not send Email(s).'
61
logger = netsvc.Logger()
63
class smtpclient(osv.osv):
65
_name = 'email.smtpclient'
66
_description = 'Email Client'
69
'name' : fields.char('Server Name', size=256, required=True),
70
'from_email' : fields.char('Email From', size=256),
71
'email':fields.char('Email Address', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}),
72
'cc_to':fields.char('Send copy to', size=256, readonly=True, states={'new':[('readonly',False)]}, help="use comma to supply multiple address. email@domain.com, email2@domain.com"),
73
'bcc_to':fields.char('Send blind copy to', size=256, readonly=True, states={'new':[('readonly',False)]}, help="use comma to supply multiple address. email@domain.com, email2@domain.com"),
74
'user' : fields.char('User Name', size=256, readonly=True, states={'new':[('readonly',False)]}),
75
'password' : fields.char('Password', size=1024, invisible=True, readonly=True, states={'new':[('readonly',False)]}),
76
'server' : fields.char('SMTP Server', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}),
77
'auth' : fields.boolean("Use Auth", readonly=True, states={'new':[('readonly',False)]}),
78
'port' : fields.char('SMTP Port', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}),
79
'ssl' : fields.boolean("Use SSL?", readonly=True, states={'new':[('readonly',False)]}),
80
'users_id': fields.many2many('res.users', 'res_smtpserver_group_rel', 'sid', 'uid', 'Users Allowed'),
81
'state': fields.selection([
82
('new','Not Verified'),
83
('waiting','Waiting for Verification'),
84
('confirm','Verified'),
85
],'Server Status', select=True, readonly=True),
86
'auth_type':fields.selection([('gmail','Google Server'), ('yahoo','Yahoo!!! Server'), ('unknown','Other Mail Servers')], string="Server Type", readonly=True, states={'new':[('readonly',False)]}),
87
'active' : fields.boolean("Active"),
88
'date_create': fields.date('Date Create', required=True, readonly=True),
89
'test_email' : fields.text('Test Message', translate=True),
90
'body' : fields.text('Message', translate=True, help="The message text that will be send along with the email which is send through this server"),
91
'verify_email' : fields.text('Verify Message', translate=True, readonly=True, states={'new':[('readonly',False)]}),
92
'code' : fields.char('Verification Code', size=1024),
93
'type' : fields.selection([("default", "Default"),("account", "Account"),("sale","Sale"),("stock","Stock")], "Server Type",required=True),
94
'history_line': fields.one2many('email.smtpclient.history', 'server_id', 'History'),
95
'server_statistics': fields.one2many('report.smtp.server', 'server_id', 'Statistics'),
96
'delete_queue': fields.selection([
97
('never','Never Delete Message'),
98
('content','Delete Content After'),
99
('all','Clear All After'),
100
('after_send','Delete when Email Sent'),
101
],'Queue Option', select=True),
102
'priority': fields.integer('Server Priority', readonly=True, states={'new':[('readonly',False)]}, help="Priority between 0 to 10, will be used to define the MTA process priotiry"),
103
'header_ids':fields.one2many('email.headers', 'server_id', 'Default Headers'),
104
'disclaimers': fields.text('Disclaimers'),
105
'process_id': fields.many2one('ir.cron', 'MTA Process', readonly=True, help="Mail Transport Agent Process"),
106
'pstate': fields.selection([
107
('running','Running'),
109
],'Server Statue', select=True, readonly=True),
110
'delete_queue_period': fields.integer('Delete after', help="delete emails/contents from email queue after specified no of days"),
113
def _get_users(self, cr, uid, context={}):
114
return self.pool.get('res.users').search(cr, uid, [])
117
'date_create': lambda *a: time.strftime('%Y-%m-%d'),
118
'state': lambda *a: 'new',
119
'type': lambda *a: 'default',
120
'port': lambda *a: '25',
121
'pstate':lambda *a: 'stop',
122
'priority': lambda *a: 5,
123
'delete_queue_period': lambda *a: 30,
124
'auth': lambda *a: True,
125
'active': lambda *a: True,
126
'delete_queue': lambda *a: 'never',
127
'users_id': _get_users,
128
'verify_email': lambda *a: _("Verification Message. This is the code\n\n__code__\n\nyou must copy in the OpenERP Email Server (Verify Server wizard).\n\nCreated by user __user__"),
134
def create(self, cr, user, vals, context={}):
135
if vals.get('password', False) != False:
136
vals['password'] = base64.b64encode(vals.get('password'))
138
res_id = super(smtpclient, self).create(cr, user, vals, context)
141
def write(self, cr, user, ids, vals, context=None):
143
if vals.get('password', False) != False:
144
for pass_char in vals.get('password'):
150
vals['password'] = base64.b64encode(vals.get('password'))
154
res = super(smtpclient, self).write(cr, user, ids, vals, context)
157
def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'):
158
def override_password(o):
161
if field == 'password':
162
o[0][field] = '********'
165
result = super(smtpclient, self).read(cr, uid, ids, fields, context, load)
166
result = override_password(result)
169
def change_servertype(self, cr, uid, ids, server):
170
if server == 'gmail':
171
return {'value':{'server':'smtp.gmail.com', 'port':'25', 'ssl':True, 'auth':True}}
172
elif server== 'yahoo':
173
return {'value':{'server':'smtp.mail.yahoo.co.in', 'ssl':False, 'port':'587', 'auth':True}}
175
return {'value':{'server':'localhost', 'port':'25', 'ssl':False, 'auth':False}}
177
def change_email(self, cr, uid, ids, email):
178
email_from = self.pool.get('res.users').browse(cr, uid, uid).name
179
if len(email) > 0 and email.find('@') > -1 and email.index('@') > 0:
180
user = email[0:email.index('@')]
181
return {'value':{'user':user, 'from_email':email_from+' <'+email+'>'}}
183
return {'value':{'user':email, 'from_email':email_from+' <'+email+'>'}}
185
def check_permissions(self, cr, uid, ids):
188
cr.execute('select * from res_smtpserver_group_rel where sid=%s and uid=%s' % (ids[0], uid))
195
def gen_private_key(self, cr, uid, ids):
197
for i in time.strftime('%Y-%m-%d %H:%M:%S'):
199
if ky in (' ', '-', ':'):
200
keys = random.random()
201
key = str(keys).split('.')[1]
206
key = ''.join(new_key)
210
def _set_error(self, cr, uid, server_id, context={}):
211
server_obj = self.browse(cr, uid, server_id)
212
if not server_obj.active:
214
if server_obj.pstate == 'stop' :
216
if server_obj.state != 'confirm':
217
return 'server_not_confirm'
220
def test_verify_email(self, cr, uid, ids, toemail, test=False, code=False):
223
self.open_connection(cr, uid, ids, serverid)
226
if test and self.server[serverid]['state'] == 'confirm':
227
body = self.server[serverid]['test_email'] or ''
229
body = self.server[serverid]['verify_email'] or ''
231
key = self.gen_private_key(cr, uid, ids)
232
#md5(time.strftime('%Y-%m-%d %H:%M:%S') + toemail).hexdigest();
234
body = body.replace("__code__", key)
236
user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
237
body = body.replace("__user__", user.name)
239
if len(body.strip()) <= 0:
240
raise osv.except_osv(_('Message Error!'), _('Please configure Email Server Messages [Verification / Test]'))
243
msg = MIMEText(body.encode('utf8') or '',_subtype='plain',_charset='utf-8')
245
msg = MIMEText(body or '',_subtype='plain',_charset='utf-8')
247
if not test and not self.server[serverid]['state'] == 'confirm':
248
msg['Subject'] = _('OpenERP SMTP server Email Registration Code!')
250
msg['Subject'] = _('OpenERP Test Email!')
253
msg['From'] = tools.ustr(self.server[serverid]['from_email'])
255
message = msg.as_string()
257
if self.server[serverid]['disclaimers']:
258
body = body + "\n" + self.server[serverid]['disclaimers']
260
queue = pooler.get_pool(cr.dbname).get('email.smtpclient.queue')
261
queue.create(cr, uid, {
263
'server_id':serverid,
264
'name':msg['Subject'],
266
'serialized_message':message,
271
if self.server[serverid]['state'] != 'confirm':
272
self.write(cr, uid, ids, {'state':'waiting', 'code':key})
276
def getpassword(self, cr, uid, ids):
278
cr.execute("select * from email_smtpclient where id = %s" , (str(ids[0]),))
279
data = cr.dictfetchall()
282
def open_connection(self, cr, uid, ids, serverid=False, permission=True):
284
self.server[serverid] = self.getpassword(cr, uid, [serverid])[0]
286
raise osv.except_osv(_('Read Error!'), _('Unable to read Server Settings'))
289
if not self.check_permissions(cr, uid, [serverid]):
290
raise osv.except_osv(_('Permission Error!'), _('You have no permission to access SMTP Server : %s ') % (self.server[serverid]['name'],) )
292
if self.server[serverid]:
294
self.smtpServer[serverid] = smtplib.SMTP()
295
self.smtpServer[serverid].debuglevel = 0
296
self.smtpServer[serverid].connect(str(self.server[serverid]['server']),str(self.server[serverid]['port']))
298
if self.server[serverid]['ssl']:
299
self.smtpServer[serverid].ehlo()
300
self.smtpServer[serverid].starttls()
301
self.smtpServer[serverid].ehlo()
303
if self.server[serverid]['auth']:
304
password = self.server[serverid]['password']
306
password = base64.b64decode(password)
309
self.smtpServer[serverid].login(str(self.server[serverid]['user']), password)
312
logger.notifyChannel('imap', netsvc.LOG_WARNING, e)
316
def selectAddress(self, cr, uid, partner=None, contact=None, ):
317
email = 'none@none.com'
318
if partner is None and contact is None:
319
return 'none@none.com'
321
if partner is not None and contact is None:
322
pool = self.pool.get('res.partner')
323
data = pool.read(cr, uid, [partner])[0]
325
contact = data['address']
327
if contact is not None:
328
pool = self.pool.get('res.partner.address')
329
data = pool.read(cr, uid, contact)[0]
330
email = data['email']
334
def select(self, cr, uid, type):
335
pool = self.pool.get('email.smtpclient')
336
ids = pool.search(cr, uid, [('type','=',type)], context=False)
338
ids = pool.search(cr, uid, [('type','=','default')], context=False)
345
# Reports is a list of tuples,where first arguement of tuple is the name of the report,second is the list of ids of the object
346
def send_email(self, cr, uid, server_id, emailto, subject, body='', attachments=[], reports=[], ir_attach=[], charset='utf-8', headers={}, context={}):
349
raise osv.except_osv(_('SMTP Data Error !'), _('Email TO Address not Defined !'))
351
def createReport(cr, uid, report, ids, name=False):
355
service = netsvc.LocalService(report)
356
(result, format) = service.create(cr, uid, [id], {}, {})
358
report_file = '/tmp/reports'+ str(id) + '.pdf'
362
fp = open(report_file,'wb+')
365
files += [report_file]
370
smtp_server = self.browse(cr, uid, server_id)
371
if smtp_server.state != 'confirm':
372
raise osv.except_osv(_('SMTP Server Error !'), _('Server is not Verified, Please Verify the Server !'))
375
subject = "OpenERP Email: [Unknown Subject]"
378
subject = subject.encode(charset)
380
subject = subject.decode()
382
#attachment from Reports
385
rpt_file = createReport(cr, uid, rpt[0], rpt[1], rpt[2])
387
rpt_file = createReport(cr, uid, rpt[0], rpt[1])
388
attachments += rpt_file
390
if isinstance(emailto, str) or isinstance(emailto, unicode):
393
ir_pool = self.pool.get('ir.attachment')
396
msg = MIMEMultipart()
397
msg['Subject'] = tools.ustr(subject)
399
msg['From'] = context.get('email_from', smtp_server.from_email)
404
if smtp_server.disclaimers:
405
body = body + "\n" + smtp_server.disclaimers
408
msg.attach(MIMEText(body.encode(charset) or '', _charset=charset, _subtype="html"))
410
msg.attach(MIMEText(body or '', _charset=charset, _subtype="html"))
412
#add custom headers to email
413
for hk in headers.keys():
414
msg[hk] = headers[hk]
416
for hk in smtp_server.header_ids:
417
msg[hk.key] = hk.value
419
context_headers = context.get('headers', [])
420
for hk in context_headers:
421
msg[hk] = context_headers[hk]
423
# Add OpenERP Server information
424
msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
425
msg['X-OpenERP-Server-Host'] = socket.gethostname()
426
msg['X-OpenERP-Server-Version'] = release.version
427
msg['Message-Id'] = "<%s-openerp-@%s>" % (time.time(), socket.gethostname())
429
if smtp_server.cc_to:
430
msg['Cc'] = smtp_server.cc_to
432
if smtp_server.bcc_to:
433
msg['Bcc'] = smtp_server.bcc_to
435
#attach files from disc
436
for file in attachments:
437
part = MIMEBase('application', "octet-stream")
438
part.set_payload(open(file,"rb").read())
439
Encoders.encode_base64(part)
440
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
443
#attach files from ir_attachments
444
for ath in ir_pool.browse(cr, uid, ir_attach):
445
part = MIMEBase('application', "octet-stream")
446
datas = base64.decodestring(ath.datas)
447
part.set_payload(datas)
448
Encoders.encode_base64(part)
449
part.add_header('Content-Disposition', 'attachment; filename="%s"' %(ath.name))
452
message = msg.as_string()
455
'server_id':server_id,
456
'cc':smtp_server.cc_to or False,
457
'bcc':smtp_server.bcc_to or False,
460
'serialized_message':message,
461
'priority':smtp_server.priority,
463
self.create_queue_enrty(cr, uid, data, context)
467
def create_queue_enrty(self, cr, uid, data, context={}):
468
queue = pooler.get_pool(cr.dbname).get('email.smtpclient.queue')
469
return queue.create(cr, uid, data, context)
471
def _check_history(self, cr, uid, ids=False, context={}):
473
server = self.pool.get('email.smtpclient')
474
queue = self.pool.get('email.smtpclient.queue')
475
sids = self.search(cr, uid, [])
476
for server in self.browse(cr, uid, sids):
477
if server.delete_queue == 'never':
480
now = datetime.today()
481
days = timedelta(days=server.delete_queue_period)
483
kday = day.__str__().split(' ')[0]
485
if server.delete_queue == 'content':
486
qids = queue.search(cr, uid, [('server_id','=',server.id), ('date_create','<=',kday)])
487
queue.write(cr, uid, qids, {'serialized_message':False})
490
if server.delete_queue == 'all':
491
qids = queue.search(cr, uid, [('server_id','=',server.id), ('date_create','<=',kday)])
492
queue.unlink(cr, uid, qids)
496
def _send_emails(self, cr, uid, ids, context={}):
497
queue = self.pool.get('email.smtpclient.queue')
498
history = self.pool.get('email.smtpclient.history')
499
queue.write(cr, uid, ids, {'state':'sending'})
506
for email in queue.browse(cr, uid, ids):
508
if not email.server_id.id in open_server:
509
open_server.append(email.server_id.id)
510
self.open_connection(cr, uid, ids, email.server_id.id)
513
self.smtpServer[email.server_id.id].sendmail(email.server_id.email, [email.to, email.cc, email.bcc], tools.ustr(email.serialized_message))
514
message = "message sent successfully to %s from %s server" % (email.to, email.server_id.name)
515
logger.notifyChannel('smtp', netsvc.LOG_INFO, message)
517
queue.write(cr, uid, [email.id], {'error':e, 'state':'error'})
520
history.create(cr, uid, {
523
'server_id': email.server_id.id,
526
if email.server_id.delete_queue == 'after_send':
527
remove.append(email.id)
529
sent.append(email.id)
531
queue.unlink(cr, uid, remove)
532
queue.write(cr, uid, sent, {'state':'send'})
535
def _check_queue(self, cr, uid, ids=False):
536
queue = self.pool.get('email.smtpclient.queue')
539
sids = queue.search(cr, uid, [('state','not in',['send','sending']), ('type','=','system')], order="priority", limit=30)
542
sids = queue.search(cr, uid, [('state','not in',['send','sending']), ('server_id','in',ids)], order="priority", limit=30)
546
message = "sending %s emails from message queue !" % (len(sids))
547
logger.notifyChannel('smtp', netsvc.LOG_INFO, message)
549
result = self. _send_emails(cr, uid, sids, {})
552
def set_to_draft(self, cr, uid, ids, context={}):
553
self.stop_process(cr, uid, ids, context)
554
self.write(cr, uid, ids, {'state':'new', 'code':False})
557
def create_process(self, cr, uid, ids, context={}):
558
svr = self.browse(cr, uid, ids[0])
559
if not svr.process_id:
561
'name':'Process : ' + svr.name,
562
'model':'email.smtpclient',
564
'function':'_check_queue',
567
'interval_type':'minutes',
573
id = self.pool.get('ir.cron').create(cr, uid, res)
574
self.write(cr, uid, ids, {'process_id':id})
578
def start_process(self, cr, uid, ids, context={}):
579
process = self.browse(cr, uid, ids[0], context)
580
if not process.process_id or process.state != 'confirm':
581
raise osv.except_osv(_('SMTP Server Error !'), _('Server is not Verified, Please Verify the Server !'))
583
pid = process.process_id.id
584
self.pool.get('ir.cron').write(cr, uid, [pid], {'active':True})
585
self.write(cr, uid, ids, {'pstate':'running'})
588
def stop_process(self, cr, uid, ids, context={}):
589
pid = self.browse(cr, uid, ids[0], context).process_id.id
590
self.pool.get('ir.cron').write(cr, uid, [pid], {'active':False})
591
self.write(cr, uid, ids, {'pstate':'stop'})
596
class email_headers(osv.osv):
597
_name = 'email.headers'
598
_description = 'Email Headers'
600
'server_id':fields.many2one('email.smtpclient', 'SMTP Server'),
601
'key':fields.char('Header', size=64, required=True),
602
'value':fields.char('Value', size=1024, required=False),
606
class email_history(osv.osv):
607
_name = 'email.smtpclient.history'
608
_description = 'Email Client History'
612
'name' : fields.text('Description',required=True, readonly=True),
613
'date_create': fields.datetime('Date',readonly=True),
614
'user_id':fields.many2one('res.users', 'Username', readonly=True, select=True),
615
'server_id' : fields.many2one('email.smtpclient', 'Smtp Server', ondelete='set null', readonly=True, required=True),
616
'model':fields.many2one('ir.model', 'Model', readonly=True, select=True),
617
'resource_id':fields.integer('Resource ID', readonly=True),
618
'email':fields.char('Email',size=64,readonly=True),
622
'date_create': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
623
'user_id': lambda obj, cr, uid, context: uid,
626
def create(self, cr, uid, vals, context=None):
627
super(email_history,self).create(cr, uid, vals, context)
631
class message_queue(osv.osv):
632
_name = 'email.smtpclient.queue'
633
_description = 'Email Queue'
636
'to' : fields.char('Mail to', size=1024, readonly=True, states={'draft':[('readonly',False)], 'error':[('readonly',False)]}),
637
'server_id':fields.many2one('email.smtpclient', 'SMTP Server', readonly=True, states={'draft':[('readonly',False)]}),
638
'cc' : fields.char('CC to', size=1024, readonly=True, states={'draft':[('readonly',False)]}),
639
'bcc' : fields.char('BCC to', size=1024, readonly=True, states={'draft':[('readonly',False)]}),
640
'name' : fields.char('Subject', size=1024, readonly=True, states={'draft':[('readonly',False)]}),
641
'body' : fields.text('Email Text', readonly=True, states={'draft':[('readonly',False)]}),
642
'serialized_message':fields.text('Message', readonly=True, states={'draft':[('readonly',False)]}),
643
'state':fields.selection([
645
('sending','Waiting'),
648
],'Message Status', select=True, readonly=True),
649
'type':fields.selection([
650
('default','Default Message'),
651
('system','System Message'),
652
],'Message Type', select=True, readonly=True),
653
'error':fields.text('Last Error', size=256, readonly=True, states={'draft':[('readonly',False)]}),
654
'date_create': fields.datetime('Date', readonly=True),
655
'priority':fields.integer('Message Priority', readonly=True),
658
'date_create': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
659
'state': lambda *a: 'draft',
660
'priority': lambda *a: '10',
661
'type': lambda *a: 'default',
665
class report_smtp_server(osv.osv):
666
_name = "report.smtp.server"
667
_description = "Server Statistics"
670
'server_id':fields.many2one('email.smtpclient','Server ID',readonly=True),
671
'name': fields.char('Server',size=64,readonly=True),
672
'history':fields.char('History',size=64, readonly=True),
673
'no':fields.integer('Total No.',readonly=True),
677
create or replace view report_smtp_server as (
678
select min(h.id) as id, c.id as server_id, h.name as history, h.name as name, count(h.name) as no from email_smtpclient c inner join email_smtpclient_history h on c.id=h.server_id group by h.name, c.id
684
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: