1 from osv import osv, fields
2 from html2text import html2text
6 from email import Encoders
7 from email.mime.base import MIMEBase
8 from email.mime.multipart import MIMEMultipart
9 from email.mime.text import MIMEText
10 from email.header import decode_header, Header
11 from email.utils import formatdate
17 import email_template_engines
18 from tools.translate import _
21 class email_template_account(osv.osv):
23 Object to store email account settings
25 _name = "email_template.account"
26 _known_content_types = ['multipart/mixed',
27 'multipart/alternative',
33 'name': fields.char('Email Account Desc',
34 size=64, required=True,
35 readonly=True, select=True,
36 states={'draft':[('readonly', False)]}),
37 'user':fields.many2one('res.users',
38 'Related User', required=True,
39 readonly=True, states={'draft':[('readonly', False)]}),
40 'email_id': fields.char('Email ID',
41 size=120, required=True,
42 readonly=True, states={'draft':[('readonly', False)]} ,
43 help=" eg:yourname@yourdomain.com "),
44 'smtpserver': fields.char('Server',
45 size=120, required=True,
46 readonly=True, states={'draft':[('readonly', False)]},
47 help="Enter name of outgoing server,eg:smtp.gmail.com "),
48 'smtpport': fields.integer('SMTP Port ',
49 size=64, required=True,
50 readonly=True, states={'draft':[('readonly', False)]},
51 help="Enter port number,eg:SMTP-587 "),
52 'smtpuname': fields.char('User Name',
53 size=120, required=False,
54 readonly=True, states={'draft':[('readonly', False)]}),
55 'smtppass': fields.char('Password',
56 size=120, invisible=True,
57 required=False, readonly=True,
58 states={'draft':[('readonly', False)]}),
59 'smtptls':fields.boolean('Use TLS',
60 states={'draft':[('readonly', False)]}, readonly=True),
62 'smtpssl':fields.boolean('Use SSL/TLS (only in python 2.6)',
63 states={'draft':[('readonly', False)]}, readonly=True),
64 'send_pref':fields.selection([
65 ('html', 'HTML otherwise Text'),
66 ('text', 'Text otherwise HTML'),
67 ('both', 'Both HTML & Text')
68 ], 'Mail Format', required=True),
69 'allowed_groups':fields.many2many(
71 'account_group_rel', 'templ_id', 'group_id',
72 string="Allowed User Groups",
73 help="Only users from these groups will be" \
74 "allowed to send mails from this ID"),
75 'company':fields.selection([
78 ], 'Company Mail A/c',
80 help="Select if this mail account does not belong" \
81 "to specific user but the organisation as a whole." \
82 "eg:info@somedomain.com",
83 required=True, states={
84 'draft':[('readonly', False)]
87 'state':fields.selection([
88 ('draft', 'Initiated'),
89 ('suspended', 'Suspended'),
90 ('approved', 'Approved')
92 'Account Status', required=True, readonly=True),
96 'name':lambda self, cursor, user, context:self.pool.get(
105 'smtpssl':lambda * a:True,
106 'state':lambda * a:'draft',
107 'user':lambda self, cursor, user, context:user,
108 'send_pref':lambda * a: 'html',
109 'smtptls':lambda * a:True,
116 'Another setting already exists with this email ID !')
119 def _constraint_unique(self, cursor, user, ids):
121 This makes sure that you dont give personal
122 users two accounts with same ID (Validated in sql constaints)
123 However this constraint exempts company accounts.
124 Any no of co accounts for a user is allowed
126 if self.read(cursor, user, ids, ['company'])[0]['company'] == 'no':
127 accounts = self.search(cursor, user, [
129 ('company', '=', 'no')
131 if len(accounts) > 1 :
140 'Error: You are not allowed to have more than 1 account.',
144 def on_change_emailid(self, cursor, user, ids, name=None, email_id=None, context=None):
146 Called when the email ID field changes.
149 Writes the same email value to the smtpusername
150 and incoming username
152 #TODO: Check and remove the write. Is it needed?
153 self.write(cursor, user, ids, {'state':'draft'}, context=context)
157 'smtpuname':email_id,
162 def get_outgoing_server(self, cursor, user, ids, context=None):
164 Returns the Out Going Connection (SMTP) object
166 @attention: DO NOT USE except_osv IN THIS METHOD
167 @param cursor: Database Cursor
168 @param user: ID of current user
169 @param ids: ID/list of ids of current object for
170 which connection is required
171 First ID will be chosen from lists
172 @param context: Context
174 @return: SMTP server object or Exception
176 #Type cast ids to integer
177 if type(ids) == list:
179 this_object = self.browse(cursor, user, ids, context)
181 if this_object.smtpserver and this_object.smtpport:
183 if this_object.smtpssl:
184 serv = smtplib.SMTP_SSL(this_object.smtpserver, this_object.smtpport)
186 serv = smtplib.SMTP(this_object.smtpserver, this_object.smtpport)
187 if this_object.smtptls:
191 except Exception, error:
194 if serv.has_extn('AUTH') or this_object.smtpuname or this_object.smtppass:
195 serv.login(this_object.smtpuname, this_object.smtppass)
196 except Exception, error:
199 raise Exception(_("SMTP SERVER or PORT not specified"))
200 raise Exception(_("Core connection for the given ID does not exist"))
202 def check_outgoing_connection(self, cursor, user, ids, context=None):
204 checks SMTP credentials and confirms if outgoing connection works
206 @param cursor: Database Cursor
207 @param user: ID of current user
208 @param ids: list of ids of current object for
209 which connection is required
210 @param context: Context
213 self.get_outgoing_server(cursor, user, ids, context)
214 raise osv.except_osv(_("SMTP Test Connection Was Successful"), '')
215 except osv.except_osv, success_message:
216 raise success_message
217 except Exception, error:
218 raise osv.except_osv(
219 _("Out going connection test failed"),
220 _("Reason: %s") % error
223 def do_approval(self, cr, uid, ids, context={}):
224 #TODO: Check if user has rights
225 self.write(cr, uid, ids, {'state':'approved'}, context=context)
226 # wf_service = netsvc.LocalService("workflow")
228 def smtp_connection(self, cursor, user, id, context=None):
230 This method should now wrap smtp_connection
232 #This function returns a SMTP server object
233 logger = netsvc.Logger()
234 core_obj = self.browse(cursor, user, id, context)
235 if core_obj.smtpserver and core_obj.smtpport and core_obj.state == 'approved':
237 serv = self.get_outgoing_server(cursor, user, id, context)
238 except Exception, error:
239 logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed on login. Probable Reason:Could not login to server\nError: %s") % (id, error))
241 #Everything is complete, now return the connection
244 logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
247 #**************************** MAIL SENDING FEATURES ***********************#
248 def split_to_ids(self, ids_as_str):
250 Identifies email IDs separated by separators
253 "a@b.com,c@bcom; d@b.com;e@b.com->['a@b.com',...]"
255 email_sep_by_commas = ids_as_str \
256 .replace('; ', ',') \
259 return email_sep_by_commas.split(',')
261 def get_ids_from_dict(self, addresses={}):
266 keys = ['To', 'CC', 'BCC']
268 ids_as_list = self.split_to_ids(addresses.get(each, u''))
269 while u'' in ids_as_list:
270 ids_as_list.remove(u'')
271 result[each] = ids_as_list
272 result['all'].extend(ids_as_list)
275 def send_mail(self, cr, uid, ids, addresses, subject='', body=None, payload=None, context=None):
276 #TODO: Replace all this crap with a single email object
283 logger = netsvc.Logger()
285 core_obj = self.browse(cr, uid, id, context)
286 serv = self.smtp_connection(cr, uid, id)
289 msg = MIMEMultipart()
291 msg['Subject'] = subject
292 sender_name = Header(core_obj.name, 'utf-8').encode()
293 msg['From'] = sender_name + " <" + core_obj.email_id + ">"
294 msg['Organization'] = tools.ustr(core_obj.user.company_id.name)
295 msg['Date'] = formatdate()
296 addresses_l = self.get_ids_from_dict(addresses)
297 if addresses_l['To']:
298 msg['To'] = u','.join(addresses_l['To'])
299 if addresses_l['CC']:
300 msg['CC'] = u','.join(addresses_l['CC'])
301 # if addresses_l['BCC']:
302 # msg['BCC'] = u','.join(addresses_l['BCC'])
303 if body.get('text', False):
304 temp_body_text = body.get('text', '')
305 l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', ''))
307 body['text'] = u'No Mail Message'
308 # Attach parts into message container.
309 # According to RFC 2046, the last part of a multipart message, in this case
310 # the HTML message, is best and preferred.
311 if core_obj.send_pref == 'text' or core_obj.send_pref == 'both':
312 body_text = body.get('text', u'No Mail Message')
313 body_text = tools.ustr(body_text)
314 msg.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8'))
315 if core_obj.send_pref == 'html' or core_obj.send_pref == 'both':
316 html_body = body.get('html', u'')
317 if len(html_body) == 0 or html_body == u'':
318 html_body = body.get('text', u'<p>No Mail Message</p>').replace('\n', '<br/>').replace('\r', '<br/>')
319 html_body = tools.ustr(html_body)
320 msg.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8'))
321 #Now add attachments if any
322 for file in payload.keys():
323 part = MIMEBase('application', "octet-stream")
324 part.set_payload(base64.decodestring(payload[file]))
325 part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
326 Encoders.encode_base64(part)
328 except Exception, error:
329 logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:MIME Error\nDescription: %s") % (id, error))
332 #print msg['From'],toadds
333 serv.sendmail(msg['From'], addresses_l['all'], msg.as_string())
334 except Exception, error:
335 logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Server Send Error\nDescription: %s") % (id, error))
337 #The mail sending is complete
339 logger.notifyChannel(_("Email Template"), netsvc.LOG_INFO, _("Mail from Account %s successfully Sent.") % (id))
342 logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
344 def extracttime(self, time_as_string):
348 logger = netsvc.Logger()
349 #The standard email dates are of format similar to:
350 #Thu, 8 Oct 2009 09:35:42 +0200
351 #print time_as_string
353 convertor = {'+':1, '-':-1}
355 time_as_string = time_as_string.replace(',', '')
356 date_list = time_as_string.split(' ')
357 date_temp_str = ' '.join(date_list[1:5])
358 if len(date_list) >= 6:
359 sign = convertor.get(date_list[5][0], False)
363 dt = datetime.datetime.strptime(
368 dt = datetime.datetime.strptime(
375 offset = datetime.timedelta(
383 except Exception, e2:
384 """Looks like UT or GMT, just forget decoding"""
387 offset = datetime.timedelta(hours=0)
389 date_as_date = dt.strftime('%Y-%m-%d %H:%M:%S')
392 logger.notifyChannel(
396 "Datetime Extraction failed.Date:%s \
403 def send_receive(self, cr, uid, ids, context=None):
404 self.get_mails(cr, uid, ids, context)
407 ctx['filters'] = [('account_id', '=', id)]
408 self.pool.get('email_template.mailbox').send_all_mail(cr, uid, [], context=ctx)
411 def decode_header_text(self, text):
412 """ Decode internationalized headers RFC2822.
413 To, CC, BCC, Subject fields can contain
414 text slices with different encodes, like:
415 =?iso-8859-1?Q?Enric_Mart=ED?= <enricmarti@company.com>,
416 =?Windows-1252?Q?David_G=F3mez?= <david@company.com>
417 Sometimes they include extra " character at the beginning/
418 end of the contact name, like:
419 "=?iso-8859-1?Q?Enric_Mart=ED?=" <enricmarti@company.com>
420 and decode_header() does not work well, so we use regular
421 expressions (?= ? ? ?=) to split the text slices
425 p = re.compile("(=\?.*?\?.\?.*?\?=)")
428 for t2 in p.split(text):
432 ) for (s, t) in decode_header(t2)]
438 email_template_account()
440 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: