[IMP] email_template: review + many improvements:

- Support for specifying Reply-To and Message-ID headers in templates
- Restored the proper GPLv3 copyright from Sharoon Thomas (mistakenly switched to AGPL)
- Minor refactoring/cleanup/renaming
- Support for multipart/alternative format when sending emails, including wrapped inside multipart/mixed if attachments are present
- Support for plain attachments (existing files) in addition to reports
- Support for specifying "From" email in "Name <email>" format in addition to using the label on the Email Account

bzr revid: odo@openerp.com-20100818152227-i823jbspioqwr1kk
This commit is contained in:
Olivier Dony 2010-08-18 17:22:27 +02:00
parent 195ac98256
commit 84aea26e88
11 changed files with 377 additions and 207 deletions

View File

@ -2,21 +2,21 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# 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.
# it under the terms of the GNU 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.
# GNU 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################

View File

@ -2,21 +2,21 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# 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.
# it under the terms of the GNU 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.
# GNU 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################

View File

@ -2,29 +2,28 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# 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.
# it under the terms of the GNU 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.
# GNU 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
import base64
import random
import time
import types
import netsvc
import re
LOGGER = netsvc.Logger()
@ -58,9 +57,7 @@ except:
_("Django templates not installed")
)
import email_template_engines
import tools
import report
import pooler
import logging
@ -128,29 +125,38 @@ class email_template(osv.osv):
'name' : fields.char('Name', size=100, required=True),
'object_name':fields.many2one('ir.model', 'Model'),
'model_int_name':fields.char('Model Internal Name', size=200,),
'enforce_from_account':fields.many2one(
'from_account':fields.many2one(
'email_template.account',
string="Enforce From Account",
help="Emails will be sent only from this account(which are approved)."),
'from_email' : fields.related('enforce_from_account', 'email_id',
type='char', string='From',
help='From Email (select mail account)',
readonly=True),
string="Email Account",
help="Emails will be sent from this approved account."),
'def_to':fields.char(
'Recipient (To)',
size=250,
help="The default recipient of email."
"Placeholders can be used here."),
'def_cc':fields.char(
'Default CC',
'CC',
size=250,
help="The default CC for the email."
help="Carbon Copy address(es), comma-separated."
" Placeholders can be used here."),
'def_bcc':fields.char(
'Default BCC',
'BCC',
size=250,
help="The default BCC for the email."
help="Blind Carbon Copy address(es), comma-separated."
" Placeholders can be used here."),
'reply_to':fields.char('Reply-To',
size=250,
help="The address recipients should reply to,"
" if different from the From address."
" Placeholders can be used here."),
'message_id':fields.char('Message-ID',
size=250,
help="The Message-ID header value, if you need to"
"specify it, for example to automatically recognize the replies later."
" Placeholders can be used here."),
'track_campaign_item':fields.boolean('Track campaign items',
help="Enable this if you want the outgoing e-mails to include a tracking"
" marker that makes it possible to identify the replies an link them back to the campaign item"),
'lang':fields.char(
'Language',
size=250,
@ -184,6 +190,14 @@ class email_template(osv.osv):
'report_template':fields.many2one(
'ir.actions.report.xml',
'Report to send'),
'attachment_ids': fields.many2many(
'ir.attachment',
'email_template_attachment_rel',
'email_template_id',
'attachment_id',
'Attached Files',
help="You may attach existing files to this template, "
"so they will be added in all emails created from this template"),
'ref_ir_act_window':fields.many2one(
'ir.actions.act_window',
'Window Action',
@ -430,7 +444,37 @@ class email_template(osv.osv):
result['sub_model_object_field'] = False
result['null_value'] = null_value
return {'value':result}
def _add_attachment(self, cursor, user, mailbox_id, name, data, filename, context=None):
"""
Add an attachment to a given mailbox entry.
:param data: base64 encoded attachment data to store
"""
attachment_obj = self.pool.get('ir.attachment')
attachment_data = {
'name': (name or '') + _(' (Email Attachment)'),
'datas': data,
'datas_fname': filename,
'description': name or _('No Description'),
'res_model':'email_template.mailbox',
'res_id': mailbox_id,
}
attachment_id = attachment_obj.create(cursor,
user,
attachment_data,
context)
if attachment_id:
self.pool.get('email_template.mailbox').write(
cursor,
user,
mailbox_id,
{
'attachments_ids':[(4, attachment_id)],
'mail_type':'multipart/mixed'
},
context)
def generate_attach_reports(self,
cursor,
user,
@ -440,7 +484,7 @@ class email_template(osv.osv):
context=None):
"""
Generate report to be attached and attach it
to the email
to the email, and add any directly attached files as well.
@param cursor: Database Cursor
@param user: ID of User
@ -452,54 +496,34 @@ class email_template(osv.osv):
@param mail: Browse record of email object
@return: True
"""
reportname = 'report.' + \
self.pool.get('ir.actions.report.xml').read(
cursor,
user,
template.report_template.id,
['report_name'],
context)['report_name']
service = netsvc.LocalService(reportname)
data = {}
data['model'] = template.model_int_name
(result, format) = service.create(cursor,
user,
[record_id],
data,
context)
attachment_obj = self.pool.get('ir.attachment')
fname = tools.ustr(get_value(cursor, user, record_id,
template.file_name, template, context)
or 'Report')
ext = '.' + format
if not fname.endswith(ext):
fname += ext
new_att_vals = {
'name':mail.subject + ' (Email Attachment)',
'datas':base64.b64encode(result),
'datas_fname': fname,
'description':mail.subject or "No Description",
'res_model':'email_template.mailbox',
'res_id':mail.id
}
attachment_id = attachment_obj.create(cursor,
if template.report_template:
reportname = 'report.' + \
self.pool.get('ir.actions.report.xml').read(
cursor,
user,
template.report_template.id,
['report_name'],
context)['report_name']
service = netsvc.LocalService(reportname)
data = {}
data['model'] = template.model_int_name
(result, format) = service.create(cursor,
user,
new_att_vals,
context)
if attachment_id:
self.pool.get('email_template.mailbox').write(
cursor,
user,
mail.id,
{
'attachments_ids':[
[6, 0, [attachment_id]]
],
'mail_type':'multipart/mixed'
},
context)
[record_id],
data,
context)
fname = tools.ustr(get_value(cursor, user, record_id,
template.file_name, template, context)
or 'Report')
ext = '.' + format
if not fname.endswith(ext):
fname += ext
self._add_attachment(cursor, user, mail.id, mail.subject, base64.b64encode(result), fname, context)
if template.attachment_ids:
for attachment in template.attachment_ids:
self._add_attachment(cursor, user, mail.id, attachment.name, attachment.datas, attachment.datas_fname, context)
return True
def _generate_mailbox_item_from_template(self,
@ -534,9 +558,9 @@ class email_template(osv.osv):
)
else:
from_account = {
'id':template.enforce_from_account.id,
'name':template.enforce_from_account.name,
'email_id':template.enforce_from_account.email_id
'id':template.from_account.id,
'name':template.from_account.name,
'email_id':template.from_account.email_id
}
lang = get_value(cursor,
user,
@ -548,9 +572,21 @@ class email_template(osv.osv):
ctx = context.copy()
ctx.update({'lang':lang})
template = self.browse(cursor, user, template.id, context=ctx)
# determine name of sender, either it is specified in email_id or we
# use the account name
email_id = from_account['email_id'].strip()
email_from = re.findall(r'([^ ,<@]+@[^> ,]+)', email_id)[0]
if email_from != email_id:
# we should keep it all, name is probably specified in the address
email_from = from_account['email_id']
else:
email_from = tools.ustr(from_account['name']) + "<" + tools.ustr('email_id') + ">",
# FIXME: should do this in a loop and rename template fields to the corresponding
# mailbox fields. (makes no sense to have different names I think.
mailbox_values = {
'email_from': tools.ustr(from_account['name']) + \
"<" + tools.ustr(from_account['email_id']) + ">",
'email_from': email_from,
'email_to':get_value(cursor,
user,
record_id,
@ -569,6 +605,12 @@ class email_template(osv.osv):
template.def_bcc,
template,
context),
'reply_to':get_value(cursor,
user,
record_id,
template.reply_to,
template,
context),
'subject':get_value(cursor,
user,
record_id,
@ -591,8 +633,13 @@ class email_template(osv.osv):
#This is a mandatory field when automatic emails are sent
'state':'na',
'folder':'drafts',
'mail_type':'multipart/alternative'
'mail_type':'multipart/alternative',
}
if template['track_campaign_item']:
# get appropriate message-id
mailbox_values.update(message_id=tools.misc.generate_tracking_message_id(record_id))
if not mailbox_values['account_id']:
raise Exception("Unable to send the mail. No account linked to the template.")
#Use signatures if allowed
@ -627,6 +674,7 @@ class email_template(osv.osv):
if not template:
raise Exception("The requested template could not be loaded")
result = True
mailbox_obj = self.pool.get('email_template.mailbox')
for record_id in record_ids:
mailbox_id = self._generate_mailbox_item_from_template(
cursor,
@ -634,13 +682,13 @@ class email_template(osv.osv):
template,
record_id,
context)
mail = self.pool.get('email_template.mailbox').browse(
cursor,
user,
mailbox_id,
context=context
)
if template.report_template:
mail = mailbox_obj.browse(
cursor,
user,
mailbox_id,
context=context
)
if template.report_template or template.attachment_ids:
self.generate_attach_reports(
cursor,
user,
@ -649,6 +697,7 @@ class email_template(osv.osv):
mail,
context
)
self.pool.get('email_template.mailbox').write(
cursor,
user,
@ -662,6 +711,10 @@ class email_template(osv.osv):
email_template()
## FIXME: this class duplicates a lot of features of the email template send wizard,
## one of the 2 should inherit from the other!
class email_template_preview(osv.osv_memory):
_name = "email_template.preview"
_description = "Email Template Preview"
@ -674,10 +727,15 @@ class email_template_preview(osv.osv_memory):
if 'template_id' in context.keys():
ref_obj_id = self.pool.get('email.template').read(cr, uid, context['template_id'], ['object_name'], context)
ref_obj_name = self.pool.get('ir.model').read(cr, uid, ref_obj_id['object_name'][0], ['model'], context)['model']
ref_obj_ids = self.pool.get(ref_obj_name).search(cr, uid, [], 0, 20, 'id desc', context=context)
ref_obj_recs = self.pool.get(ref_obj_name).name_get(cr, uid, ref_obj_ids, context)
return ref_obj_recs
model_obj = self.pool.get(ref_obj_name)
ref_obj_ids = model_obj.search(cr, uid, [], 0, 20, 'id desc', context=context)
# also add the default one if requested, otherwise it won't be available for selection:
default_id = context.get('default_rel_model_ref')
if default_id and default_id not in ref_obj_ids:
ref_obj_ids.insert(0, default_id)
return model_obj.name_get(cr, uid, ref_obj_ids, context)
def _default_model(self, cursor, user, context=None):
"""
Returns the default value for model field
@ -701,6 +759,16 @@ class email_template_preview(osv.osv_memory):
'to':fields.char('To', size=250, readonly=True),
'cc':fields.char('CC', size=250, readonly=True),
'bcc':fields.char('BCC', size=250, readonly=True),
'reply_to':fields.char('Reply-To',
size=250,
help="The address recipients should reply to,"
" if different from the From address."
" Placeholders can be used here."),
'message_id':fields.char('Message-ID',
size=250,
help="The Message-ID header value, if you need to"
"specify it, for example to automatically recognize the replies later."
" Placeholders can be used here."),
'subject':fields.char('Subject', size=200, readonly=True),
'body_text':fields.text('Body', readonly=True),
'body_html':fields.text('Body', readonly=True),
@ -728,6 +796,11 @@ class email_template_preview(osv.osv_memory):
vals['to'] = get_value(cr, uid, rel_model_ref, template.def_to, template, context)
vals['cc'] = get_value(cr, uid, rel_model_ref, template.def_cc, template, context)
vals['bcc'] = get_value(cr, uid, rel_model_ref, template.def_bcc, template, context)
vals['reply_to'] = get_value(cr, uid, rel_model_ref, template.reply_to, template, context)
if template.message_id:
vals['message_id'] = get_value(cr, uid, rel_model_ref, template.message_id, template, context)
elif template.track_campaign_item:
vals['message_id'] = tools.misc.generate_tracking_message_id(rel_model_ref)
vals['subject'] = get_value(cr, uid, rel_model_ref, template.def_subject, template, context)
vals['body_text'] = get_value(cr, uid, rel_model_ref, template.def_body_text, template, context)
vals['body_html'] = get_value(cr, uid, rel_model_ref, template.def_body_html, template, context)

View File

@ -2,26 +2,25 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# 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.
# it under the terms of the GNU 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.
# GNU 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv, fields
from html2text import html2text
import re
import smtplib
import base64
@ -31,12 +30,8 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import decode_header, Header
from email.utils import formatdate
import re
import netsvc
import string
import email
import time, datetime
import email_template_engines
import datetime
from tools.translate import _
import tools
@ -86,9 +81,10 @@ class email_template_account(osv.osv):
'smtpssl':fields.boolean('SSL/TLS (only in python 2.6)',
states={'draft':[('readonly', False)]}, readonly=True),
'send_pref':fields.selection([
('html', 'HTML otherwise Text'),
('text', 'Text otherwise HTML'),
('both', 'Both HTML & Text')
('html', 'HTML, otherwise Text'),
('text', 'Text, otherwise HTML'),
('alternative', 'Both HTML & Text (Alternative)'),
('mixed', 'Both HTML & Text (Mixed)')
], 'Mail Format', required=True),
'company':fields.selection([
('yes', 'Yes'),
@ -135,7 +131,10 @@ class email_template_account(osv.osv):
'unique (email_id)',
'Another setting already exists with this email ID !')
]
def name_get(self, cr, uid, ids, context=None):
return dict([(a["id"], "%s (%s)" % (a['email_id'], a['name'])) for a in self.read(cr, uid, ids, ['name', 'email_id'], context=context)])
def _constraint_unique(self, cursor, user, ids):
"""
This makes sure that you dont give personal
@ -283,7 +282,7 @@ class email_template_account(osv.osv):
TODO: Doc this
"""
result = {'all':[]}
keys = ['To', 'CC', 'BCC']
keys = ['To', 'CC', 'BCC', 'Reply-To']
for each in keys:
ids_as_list = self.split_to_ids(addresses.get(each, u''))
while u'' in ids_as_list:
@ -292,7 +291,7 @@ class email_template_account(osv.osv):
result['all'].extend(ids_as_list)
return result
def send_mail(self, cr, uid, ids, addresses, subject='', body=None, payload=None, context=None):
def send_mail(self, cr, uid, ids, addresses, subject='', body=None, payload=None, message_id=None, context=None):
#TODO: Replace all this with a single email object
if body is None:
body = {}
@ -306,18 +305,38 @@ class email_template_account(osv.osv):
serv = self.smtp_connection(cr, uid, id)
if serv:
try:
msg = MIMEMultipart()
# Prepare multipart containers depending on data
text_subtype = (core_obj.send_pref == 'alternative') and 'alternative' or 'mixed'
# Need a multipart/mixed wrapper for attachments if content is alternative
if payload and text_subtype == 'alternative':
payload_part = MIMEMultipart(_subtype='mixed')
text_part = MIMEMultipart(_subtype=text_subtype)
payload_part.attach(text_part)
else:
# otherwise a single multipart/mixed will do the whole job
payload_part = text_part = MIMEMultipart(_subtype=text_subtype)
if subject:
msg['Subject'] = subject
sender_name = Header(core_obj.name, 'utf-8').encode()
msg['From'] = sender_name + " <" + core_obj.email_id + ">"
msg['Organization'] = tools.ustr(core_obj.user.company_id.name)
msg['Date'] = formatdate()
payload_part['Subject'] = subject
from_email = core_obj.email_id
if '<' in from_email:
# We have a structured email address, keep it untouched
payload_part['From'] = Header(core_obj.email_id, 'utf-8').encode()
else:
# Plain email address, construct a structured one based on the name:
sender_name = Header(core_obj.name, 'utf-8').encode()
payload_part['From'] = sender_name + " <" + core_obj.email_id + ">"
payload_part['Organization'] = tools.ustr(core_obj.user.company_id.name)
payload_part['Date'] = formatdate()
addresses_l = self.get_ids_from_dict(addresses)
if addresses_l['To']:
msg['To'] = u','.join(addresses_l['To'])
payload_part['To'] = u','.join(addresses_l['To'])
if addresses_l['CC']:
msg['CC'] = u','.join(addresses_l['CC'])
payload_part['CC'] = u','.join(addresses_l['CC'])
if addresses_l['Reply-To']:
payload_part['Reply-To'] = addresses_l['Reply-To'][0]
if message_id:
payload_part['Message-ID'] = message_id
if body.get('text', False):
temp_body_text = body.get('text', '')
l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', ''))
@ -326,28 +345,30 @@ class email_template_account(osv.osv):
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
if core_obj.send_pref == 'text' or core_obj.send_pref == 'both':
body_text = body.get('text', u'No Mail Message')
if core_obj.send_pref in ('text', 'mixed', 'alternative'):
body_text = body.get('text', u'<Empty Message>')
body_text = tools.ustr(body_text)
msg.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8'))
if core_obj.send_pref == 'html' or core_obj.send_pref == 'both':
text_part.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8'))
if core_obj.send_pref in ('html', 'mixed', 'alternative'):
html_body = body.get('html', u'')
if len(html_body) == 0 or html_body == u'':
html_body = body.get('text', u'<p>No Mail Message</p>').replace('\n', '<br/>').replace('\r', '<br/>')
html_body = body.get('text', u'<p>&lt;Empty Message&gt;</p>').replace('\n', '<br/>').replace('\r', '<br/>')
html_body = tools.ustr(html_body)
msg.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8'))
#Now add attachments if any
for file in payload.keys():
part = MIMEBase('application', "octet-stream")
part.set_payload(base64.decodestring(payload[file]))
part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
Encoders.encode_base64(part)
msg.attach(part)
text_part.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8'))
#Now add attachments if any, wrapping into a container multipart/mixed if needed
if payload:
for file in payload:
part = MIMEBase('application', "octet-stream")
part.set_payload(base64.decodestring(payload[file]))
part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
Encoders.encode_base64(part)
payload_part.attach(part)
except Exception, error:
logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:MIME Error\nDescription: %s") % (id, error))
return {'error_msg': "Server Send Error\nDescription: %s"%error}
try:
serv.sendmail(msg['From'], addresses_l['all'], msg.as_string())
serv.sendmail(payload_part['From'], addresses_l['all'], payload_part.as_string())
except Exception, error:
logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Server Send Error\nDescription: %s") % (id, error))
return {'error_msg': "Server Send Error\nDescription: %s"%error}
@ -358,7 +379,7 @@ class email_template_account(osv.osv):
else:
logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
return {'error_msg':"Mail from Account %s failed. Probable Reason:Account not approved"% id}
def extracttime(self, time_as_string):
"""
TODO: DOC THis

View File

@ -2,29 +2,25 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# 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.
# it under the terms of the GNU 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.
# GNU 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
# To change this template, choose Tools | Templates
# and open the template in the editor.
from osv import fields,osv
import pooler
import netsvc
from osv import osv
import re
class email_template_engines(osv.osv):
@ -35,7 +31,7 @@ class email_template_engines(osv.osv):
pass
def strip_html(self,text):
#Removes HTML, Have to check if still relevent
#Removes HTML, Have to check if still relevant
if text:
def fixup(m):
text = m.group(0)
@ -69,7 +65,6 @@ class email_template_engines(osv.osv):
#templateid: the template id of the template
#context: TODO
if message:
logger = netsvc.Logger()
def merge(match):
template = self.pool.get("email.template").browse(cr,uid,templateid,context)
obj_pool = self.pool.get(template.object_name.model)

View File

@ -2,27 +2,26 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# 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.
# it under the terms of the GNU 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.
# GNU 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv, fields
import time
import email_template_engines
import netsvc
from tools.translate import _
import tools
@ -75,10 +74,15 @@ class email_template_mailbox(osv.osv):
payload[attachment.datas_fname] = attachment.datas
result = account_obj.send_mail(cr, uid,
[values['account_id'][0]],
{'To':values.get('email_to', u'') or u'', 'CC':values.get('email_cc', u'') or u'', 'BCC':values.get('email_bcc', u'') or u''},
{'To':values.get('email_to') or u'',
'CC':values.get('email_cc') or u'',
'BCC':values.get('email_bcc') or u'',
'Reply-To':values.get('reply_to') or u''},
values['subject'] or u'',
{'text':values.get('body_text', u'') or u'', 'html':values.get('body_html', u'') or u''},
payload=payload, context=context)
{'text':values.get('body_text') or u'', 'html':values.get('body_html') or u''},
payload=payload,
message_id=values['message_id'],
context=context)
if result == True:
self.write(cr, uid, id, {'folder':'sent', 'state':'na', 'date_mail':time.strftime("%Y-%m-%d %H:%M:%S")}, context)
self.historise(cr, uid, [id], "Email sent successfully", context)
@ -88,7 +92,7 @@ class email_template_mailbox(osv.osv):
except Exception, error:
logger = netsvc.Logger()
logger.notifyChannel(_("Power Email"), netsvc.LOG_ERROR, _("Sending of Mail %s failed. Probable Reason:Could not login to server\nError: %s") % (id, error))
logger.notifyChannel("email-template", netsvc.LOG_ERROR, _("Sending of Mail %s failed. Probable Reason:Could not login to server\nError: %s") % (id, error))
self.historise(cr, uid, [id], error, context)
self.write(cr, uid, id, {'state':'na'}, context)
return result
@ -103,21 +107,27 @@ class email_template_mailbox(osv.osv):
'From',
size=64),
'email_to':fields.char(
'Recepient (To)',
'Recipient (To)',
size=250,),
'email_cc':fields.char(
' CC',
'CC',
size=250),
'email_bcc':fields.char(
' BCC',
'BCC',
size=250),
'reply_to':fields.char(
'Reply-To',
size=250),
'message_id':fields.char(
'Message-ID',
size=250),
'subject':fields.char(
' Subject',
'Subject',
size=200,),
'body_text':fields.text(
'Standard Body (Text)'),
'body_html':fields.text(
'Body (Text-Web Client Only)'),
'Body (Rich Text Clients Only)'),
'attachments_ids':fields.many2many(
'ir.attachment',
'mail_attachments_rel',

View File

@ -8,14 +8,13 @@
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Outbox">
<group col="4" colspan="2">
<field name="email_from" colspan="4" select="1"/>
<field name="email_cc" colspan="4" select="1"/>
<field name="date_mail" colspan="4" select="2"/>
</group>
<group col="4" colspan="2">
<field name="email_to" colspan="4" required="1" select="1" />
<field name="email_bcc" colspan="4" select="2"/>
<group col="4" colspan="4" name="headers">
<field name="email_from" select="1"/>
<field name="email_to" required="1" select="1" />
<field name="reply_to" select="2"/>
<field name="email_cc" select="1"/>
<field name="email_bcc" select="2"/>
<field name="date_mail" select="2"/>
<field name="subject" colspan="4" select="1"/>
</group>
<notebook colspan="4">
@ -46,6 +45,7 @@
<field name="server_ref" colspan="2" />
<field name="mail_type" colspan="2" />
<field name="folder" colspan="2" select="2"/>
<field name="message_id" select="2"/>
<separator string="History" colspan="4" />
<field name="history" nolabel="1" colspan="4"/>
</group>
@ -83,7 +83,7 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Mailboxes">
<filter icon="terp-mail-message-new" string="Drafts" name="draft" domain="[('folder','=','drafts']"/>
<filter icon="terp-mail-message-new" string="Drafts" name="draft" domain="[('folder','=','drafts')]"/>
<filter icon="terp-mail-" string="Outbox" name="outbox" domain="[('folder','=','outbox')]"/>
<separator orientation="vertical"/>
<filter icon="terp-gtk-jump-to-ltr" string="Sent" domain="[('folder','=','sent')]"/>

View File

@ -16,7 +16,9 @@
<field name="to" />
<field name="cc" />
<field name="bcc" />
<field name="subject" />
<field name="reply_to" />
<field name="message_id" attrs="{'invisible':[('message_id','=',False)]}" groups="base.group_extended"/>
<field name="subject" colspan="8"/>
</group>
<group col="4" colspan="4">
<separator string= "Body(Text)" colspan="2"/>
@ -58,17 +60,18 @@
<page string="Mail Details">
<group col="2" colspan="2">
<separator string="Addresses" colspan="2"/>
<field name="from_email"/>
<field name="from_account" required="1"/>
<field name="def_to" required="1"/>
<field name="def_cc"/>
<field name="def_bcc"/>
<field name="reply_to"/>
</group>
<group col="2" colspan="2">
<separator string="Email Data" colspan="2"/>
<field name="enforce_from_account" required= "1"/>
<field name="def_subject" colspan="4" required="1" />
<field name="use_sign" colspan="4" />
<field name="lang" colspan="4" />
<field name="track_campaign_item" colspan="4"/>
</group>
<separator colspan="3" string="Standard Body" />
<separator colspan="1" string="Expression Builder" />
@ -116,10 +119,17 @@
<button name="delete_action" string="Delete Action" type="object" colspan="2" attrs="{'invisible':[('ref_ir_act_window','=',False), ('ref_ir_value','=',False)]}"/>
</group>
<group colspan="2" col="2">
<separator string="Attachments (Report to attach)" colspan="4"/>
<field name="file_name" colspan="2" />
<field name="report_template" colspan="2"
domain="[('model','=',model_int_name)]" />
<separator string="Attachments" colspan="2"/>
<notebook>
<page string="Existing files">
<field name="attachment_ids" colspan="4" nolabel="1"/>
</page>
<page string="Report">
<field name="file_name" colspan="4" />
<field name="report_template" colspan="4"
domain="[('model','=',model_int_name)]" />
</page>
</notebook>
</group>
</page>
</notebook>
@ -141,7 +151,7 @@
<field name="def_subject" colspan="4" select="2" />
<field name="use_sign" colspan="4" select="2" />
<field name="file_name" colspan="4" />
<field name="enforce_from_account" />
<field name="from_account" />
</tree>
</field>
</record>

View File

@ -9,7 +9,7 @@ __contributors__ = ["Martin 'Joey' Schulze", "Ricardo Reyes", "Kevin Jay North"]
# Support decoded entities with unifiable.
if not hasattr(__builtins__, 'True'): True, False = 1, 0
import re, sys, urllib, htmlentitydefs, codecs, StringIO, types
import re, sys, urllib, htmlentitydefs, codecs
import sgmllib
import urlparse
sgmllib.charref = re.compile('&#([xX]?[0-9a-fA-F]+)[^0-9a-fA-F]')

View File

@ -1 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
import email_template_send_wizard

View File

@ -1,13 +1,38 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv, fields
from mako.template import Template
from mako import exceptions
import netsvc
import base64
import time
from tools.translate import _
import tools
from email_template.email_template import get_value
## FIXME: this wizard duplicates a lot of features of the email template preview,
## one of the 2 should inherit from the other!
class email_template_send_wizard(osv.osv_memory):
_name = 'email_template.send.wizard'
_description = 'This is the wizard for sending mail'
@ -23,16 +48,16 @@ class email_template_send_wizard(osv.osv_memory):
logger = netsvc.Logger()
if template.enforce_from_account:
return [(template.enforce_from_account.id, '%s (%s)' % (template.enforce_from_account.name, template.enforce_from_account.email_id))]
if template.from_account:
return [(template.from_account.id, '%s (%s)' % (template.from_account.name, template.from_account.email_id))]
else:
account_id = self.pool.get('email_template.account').search(cr,uid,[('company','=','no'),('user','=',uid)], context=context)
if account_id:
account = self.pool.get('email_template.account').browse(cr,uid,account_id, context)
return [(r.id,r.name + " (" + r.email_id + ")") for r in account]
else:
logger.notifyChannel(_("Power Email"), netsvc.LOG_ERROR, _("No personal email accounts are configured for you. \nEither ask admin to enforce an account for this template or get yourself a personal power email account."))
raise osv.except_osv(_("Power Email"),_("No personal email accounts are configured for you. \nEither ask admin to enforce an account for this template or get yourself a personal power email account."))
logger.notifyChannel(_("email-template"), netsvc.LOG_ERROR, _("No personal email accounts are configured for you. \nEither ask admin to enforce an account for this template or get yourself a personal email account."))
raise osv.except_osv(_("Missing mail account"),_("No personal email accounts are configured for you. \nEither ask admin to enforce an account for this template or get yourself a personal email account."))
def get_value(self, cursor, user, template, message, context=None, id=None):
"""Gets the value of the message parsed with the content of object id (or the first 'src_rec_ids' if id is not given)"""
@ -91,6 +116,16 @@ class email_template_send_wizard(osv.osv_memory):
'to':fields.char('To',size=250,required=True),
'cc':fields.char('CC',size=250,),
'bcc':fields.char('BCC',size=250,),
'reply_to':fields.char('Reply-To',
size=250,
help="The address recipients should reply to,"
" if different from the From address."
" Placeholders can be used here."),
'message_id':fields.char('Message-ID',
size=250,
help="The Message-ID header value, if you need to"
"specify it, for example to automatically recognize the replies later."
" Placeholders can be used here."),
'subject':fields.char('Subject',size=200),
'body_text':fields.text('Body',),
'body_html':fields.text('Body',),
@ -103,6 +138,7 @@ class email_template_send_wizard(osv.osv_memory):
'attachment_ids': fields.many2many('ir.attachment','send_wizard_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
}
#FIXME: probably better by overriding default_get directly
_defaults = {
'state': lambda self,cr,uid,ctx: len(ctx['src_rec_ids']) > 1 and 'multi' or 'single',
'rel_model': lambda self,cr,uid,ctx: self.pool.get('ir.model').search(cr,uid,[('model','=',ctx['src_model'])],context=ctx)[0],
@ -116,8 +152,11 @@ class email_template_send_wizard(osv.osv_memory):
'report': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'file_name', ctx),
'signature': lambda self,cr,uid,ctx: self._get_template(cr, uid, ctx).use_sign,
'ref_template':lambda self,cr,uid,ctx: self._get_template(cr, uid, ctx).id,
'reply_to': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'reply_to', ctx),
'reply_to': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'reply_to', ctx),
'requested':lambda self,cr,uid,ctx: len(ctx['src_rec_ids']),
'full_success': lambda *a: False
'full_success': False,
'attachment_ids': [],
}
def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
@ -153,13 +192,13 @@ class email_template_send_wizard(osv.osv_memory):
mail_ids = self.save_to_mailbox(cr, uid, ids, context)
if mail_ids:
self.pool.get('email_template.mailbox').write(cr, uid, mail_ids, {'folder':'outbox'}, context)
logger.notifyChannel(_("Power Email"), netsvc.LOG_INFO, _("Emails for multiple items saved in outbox."))
logger.notifyChannel("email-template", netsvc.LOG_INFO, _("Emails for multiple items saved in outbox."))
self.write(cr, uid, ids, {
'generated':len(mail_ids),
'state':'done'
}, context)
else:
raise osv.except_osv(_("Power Email"),_("Email sending failed for one or more objects."))
raise osv.except_osv(_("Email Template"),_("Email sending failed for one or more objects."))
return True
def save_to_mailbox(self, cr, uid, ids, context=None):