[NEW] email_template (extracted from poweremail) just to send emails -specially for the marketing_campaign
bzr revid: dsh@tinyerp.com-20100602143627-n0v33g88homb8p70
This commit is contained in:
parent
fbf74a6937
commit
394e10020d
|
@ -0,0 +1,5 @@
|
|||
import email_template_account
|
||||
import email_template
|
||||
import email_template_mailbox
|
||||
import wizard
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name" : "Email Template for Open ERP",
|
||||
"version" : "0.7 RC",
|
||||
"author" : "Open ERP",
|
||||
"website" : "http://openerp.com",
|
||||
"category" : "Added functionality",
|
||||
"depends" : ['base'],
|
||||
"description": """
|
||||
Email Template is extraction of Power Email basically just to send the emails.
|
||||
""",
|
||||
"init_xml": ['email_template_scheduler_data.xml'],
|
||||
"update_xml": [
|
||||
'security/email_template_security.xml',
|
||||
'email_template_workflow.xml',
|
||||
'email_template_account_view.xml',
|
||||
'email_template_view.xml',
|
||||
'email_template_mailbox_view.xml',
|
||||
'wizard/email_template_send_wizard_view.xml',
|
||||
],
|
||||
"installable": True,
|
||||
"active": False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,698 @@
|
|||
import base64
|
||||
import random
|
||||
import time
|
||||
import types
|
||||
import netsvc
|
||||
|
||||
LOGGER = netsvc.Logger()
|
||||
|
||||
TEMPLATE_ENGINES = []
|
||||
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
from mako.template import Template #For backward combatibility
|
||||
try:
|
||||
from mako.template import Template as MakoTemplate
|
||||
from mako import exceptions
|
||||
TEMPLATE_ENGINES.append(('mako', 'Mako Templates'))
|
||||
except:
|
||||
LOGGER.notifyChannel(
|
||||
_("Email Template"),
|
||||
netsvc.LOG_ERROR,
|
||||
_("Mako templates not installed")
|
||||
)
|
||||
try:
|
||||
from django.template import Context, Template as DjangoTemplate
|
||||
#Workaround for bug:
|
||||
#http://code.google.com/p/django-tagging/issues/detail?id=110
|
||||
from django.conf import settings
|
||||
settings.configure()
|
||||
#Workaround ends
|
||||
TEMPLATE_ENGINES.append(('django', 'Django Template'))
|
||||
except:
|
||||
LOGGER.notifyChannel(
|
||||
_("Email Template"),
|
||||
netsvc.LOG_ERROR,
|
||||
_("Django templates not installed")
|
||||
)
|
||||
|
||||
import email_template_engines
|
||||
import tools
|
||||
import report
|
||||
import pooler
|
||||
|
||||
|
||||
def get_value(cursor, user, recid, message=None, template=None, context=None):
|
||||
"""
|
||||
Evaluates an expression and returns its value
|
||||
@param cursor: Database Cursor
|
||||
@param user: ID of current user
|
||||
@param recid: ID of the target record under evaluation
|
||||
@param message: The expression to be evaluated
|
||||
@param template: BrowseRecord object of the current template
|
||||
@param context: Open ERP Context
|
||||
@return: Computed message (unicode) or u""
|
||||
"""
|
||||
pool = pooler.get_pool(cursor.dbname)
|
||||
if message is None:
|
||||
message = {}
|
||||
#Returns the computed expression
|
||||
if message:
|
||||
try:
|
||||
message = tools.ustr(message)
|
||||
object = pool.get(template.model_int_name).browse(cursor, user, recid, context)
|
||||
env = {
|
||||
'user':pool.get('res.users').browse(cursor, user, user, context),
|
||||
'db':cursor.dbname
|
||||
}
|
||||
if template.template_language == 'mako':
|
||||
templ = MakoTemplate(message, input_encoding='utf-8')
|
||||
reply = MakoTemplate(message).render_unicode(object=object,
|
||||
peobject=object,
|
||||
env=env,
|
||||
format_exceptions=True)
|
||||
elif template.template_language == 'django':
|
||||
templ = DjangoTemplate(message)
|
||||
env['object'] = object
|
||||
env['peobject'] = object
|
||||
reply = templ.render(Context(env))
|
||||
return reply or False
|
||||
except Exception:
|
||||
return u""
|
||||
else:
|
||||
return message
|
||||
|
||||
class email_template(osv.osv):
|
||||
"Templates for sending Email"
|
||||
|
||||
_name = "email.template"
|
||||
_description = 'Email Templates for Models'
|
||||
|
||||
def change_model(self, cursor, user, ids, object_name, context=None):
|
||||
if object_name:
|
||||
mod_name = self.pool.get('ir.model').read(
|
||||
cursor,
|
||||
user,
|
||||
object_name,
|
||||
['model'], context)['model']
|
||||
else:
|
||||
mod_name = False
|
||||
return {
|
||||
'value':{'model_int_name':mod_name}
|
||||
}
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char('Name of Template', size=100, required=True),
|
||||
'object_name':fields.many2one('ir.model', 'Model'),
|
||||
'model_int_name':fields.char('Model Internal Name', size=200,),
|
||||
'def_to':fields.char(
|
||||
'Recepient (To)',
|
||||
size=250,
|
||||
help="The default recepient of email."
|
||||
"Placeholders can be used here."),
|
||||
'def_cc':fields.char(
|
||||
'Default CC',
|
||||
size=250,
|
||||
help="The default CC for the email."
|
||||
" Placeholders can be used here."),
|
||||
'def_bcc':fields.char(
|
||||
'Default BCC',
|
||||
size=250,
|
||||
help="The default BCC for the email."
|
||||
" Placeholders can be used here."),
|
||||
'lang':fields.char(
|
||||
'Language',
|
||||
size=250,
|
||||
help="The default language for the email."
|
||||
" Placeholders can be used here. "
|
||||
"eg. ${object.partner_id.lang}"),
|
||||
'def_subject':fields.char(
|
||||
'Default Subject',
|
||||
size=200,
|
||||
help="The default subject of email."
|
||||
" Placeholders can be used here.",
|
||||
translate=True),
|
||||
'def_body_text':fields.text(
|
||||
'Standard Body (Text)',
|
||||
help="The text version of the mail",
|
||||
translate=True),
|
||||
'def_body_html':fields.text(
|
||||
'Body (Text-Web Client Only)',
|
||||
help="The text version of the mail",
|
||||
translate=True),
|
||||
'use_sign':fields.boolean(
|
||||
'Use Signature',
|
||||
help="the signature from the User details"
|
||||
"will be appened to the mail"),
|
||||
'file_name':fields.char(
|
||||
'File Name Pattern',
|
||||
size=200,
|
||||
help="File name pattern can be specified with placeholders."
|
||||
"eg. 2009_SO003.pdf",
|
||||
translate=True),
|
||||
'report_template':fields.many2one(
|
||||
'ir.actions.report.xml',
|
||||
'Report to send'),
|
||||
'ref_ir_act_window':fields.many2one(
|
||||
'ir.actions.act_window',
|
||||
'Window Action',
|
||||
readonly=True),
|
||||
'ref_ir_value':fields.many2one(
|
||||
'ir.values',
|
||||
'Wizard Button',
|
||||
readonly=True),
|
||||
'allowed_groups':fields.many2many(
|
||||
'res.groups',
|
||||
'template_group_rel',
|
||||
'templ_id', 'group_id',
|
||||
string="Allowed User Groups",
|
||||
help="Only users from these groups will be"
|
||||
" allowed to send mails from this Template"),
|
||||
'enforce_from_account':fields.many2one(
|
||||
'email_template.account',
|
||||
string="Enforce From Account",
|
||||
help="Emails will be sent only from this account.",
|
||||
domain="[('company','=','yes')]"),
|
||||
'model_object_field':fields.many2one(
|
||||
'ir.model.fields',
|
||||
string="Field",
|
||||
help="Select the field from the model you want to use."
|
||||
"\nIf it is a relationship field you will be able to "
|
||||
"choose the nested values in the box below\n(Note:If "
|
||||
"there are no values make sure you have selected the"
|
||||
" correct model)",
|
||||
store=False),
|
||||
'sub_object':fields.many2one(
|
||||
'ir.model',
|
||||
'Sub-model',
|
||||
help='When a relation field is used this field'
|
||||
' will show you the type of field you have selected',
|
||||
store=False),
|
||||
'sub_model_object_field':fields.many2one(
|
||||
'ir.model.fields',
|
||||
'Sub Field',
|
||||
help="When you choose relationship fields "
|
||||
"this field will specify the sub value you can use.",
|
||||
store=False),
|
||||
'null_value':fields.char(
|
||||
'Null Value',
|
||||
help="This Value is used if the field is empty",
|
||||
size=50, store=False),
|
||||
'copyvalue':fields.char(
|
||||
'Expression',
|
||||
size=100,
|
||||
help="Copy and paste the value in the "
|
||||
"location you want to use a system value.",
|
||||
store=False),
|
||||
'table_html':fields.text(
|
||||
'HTML code',
|
||||
help="Copy this html code to your HTML message"
|
||||
" body for displaying the info in your mail.",
|
||||
store=False),
|
||||
#Template language(engine eg.Mako) specifics
|
||||
'template_language':fields.selection(
|
||||
TEMPLATE_ENGINES,
|
||||
'Templating Language',
|
||||
required=True
|
||||
)
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name', 'unique (name)', _('The template name must be unique !'))
|
||||
]
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
id = super(email_template, self).create(cr, uid, vals, context)
|
||||
src_obj = self.pool.get('ir.model').read(cr, uid, vals['object_name'], ['model'], context)['model']
|
||||
vals['ref_ir_act_window'] = self.pool.get('ir.actions.act_window').create(cr, uid, {
|
||||
'name': _("%s Mail Form") % vals['name'],
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'email_template.send.wizard',
|
||||
'src_model': src_obj,
|
||||
'view_type': 'form',
|
||||
'context': "{'src_model':'%s','template_id':'%d','src_rec_id':active_id,'src_rec_ids':active_ids}" % (src_obj, id),
|
||||
'view_mode':'form,tree',
|
||||
'view_id': self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'email_template.send.wizard.form')], context=context)[0],
|
||||
'target': 'new',
|
||||
'auto_refresh':1
|
||||
}, context)
|
||||
vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, {
|
||||
'name': _('Send Mail (%s)') % vals['name'],
|
||||
'model': src_obj,
|
||||
'key2': 'client_action_multi',
|
||||
'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
|
||||
'object': True,
|
||||
}, context)
|
||||
self.write(cr, uid, id, {
|
||||
'ref_ir_act_window': vals['ref_ir_act_window'],
|
||||
'ref_ir_value': vals['ref_ir_value'],
|
||||
}, context)
|
||||
return id
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
for template in self.browse(cr, uid, ids, context):
|
||||
obj = self.pool.get(template.object_name.model)
|
||||
try:
|
||||
if template.ref_ir_act_window:
|
||||
self.pool.get('ir.actions.act_window').unlink(cr, uid, template.ref_ir_act_window.id, context)
|
||||
if template.ref_ir_value:
|
||||
self.pool.get('ir.values').unlink(cr, uid, template.ref_ir_value.id, context)
|
||||
except:
|
||||
raise osv.except_osv(_("Warning"), _("Deletion of Record failed"))
|
||||
return super(email_template, self).unlink(cr, uid, ids, context)
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default = default.copy()
|
||||
old = self.read(cr, uid, id, ['name'], context=context)
|
||||
new_name = _("Copy of template ") + old.get('name', 'No Name')
|
||||
check = self.search(cr, uid, [('name', '=', new_name)], context=context)
|
||||
if check:
|
||||
new_name = new_name + '_' + random.choice('abcdefghij') + random.choice('lmnopqrs') + random.choice('tuvwzyz')
|
||||
default.update({'name':new_name})
|
||||
return super(email_template, self).copy(cr, uid, id, default, context)
|
||||
|
||||
def compute_pl(self,
|
||||
model_object_field,
|
||||
sub_model_object_field,
|
||||
null_value, template_language='mako'):
|
||||
"""
|
||||
Returns the expression based on data provided
|
||||
@param model_object_field: First level field
|
||||
@param sub_model_object_field: Second level drilled down field (M2O)
|
||||
@param null_value: What has to be returned if the value is empty
|
||||
@param template_language: The language used for templating
|
||||
@return: computed expression
|
||||
"""
|
||||
#Configure for MAKO
|
||||
copy_val = ''
|
||||
if template_language == 'mako':
|
||||
if model_object_field:
|
||||
copy_val = "${object." + model_object_field
|
||||
if sub_model_object_field:
|
||||
copy_val += "." + sub_model_object_field
|
||||
if null_value:
|
||||
copy_val += " or '" + null_value + "'"
|
||||
if model_object_field:
|
||||
copy_val += "}"
|
||||
elif template_language == 'django':
|
||||
if model_object_field:
|
||||
copy_val = "{{object." + model_object_field
|
||||
if sub_model_object_field:
|
||||
copy_val += "." + sub_model_object_field
|
||||
if null_value:
|
||||
copy_val = copy_val + '|default:"' + null_value + '"'
|
||||
copy_val = copy_val + "}}"
|
||||
return copy_val
|
||||
|
||||
def onchange_model_object_field(self, cr, uid, ids, model_object_field, template_language, context=None):
|
||||
if not model_object_field:
|
||||
return {}
|
||||
result = {}
|
||||
field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
|
||||
#Check if field is relational
|
||||
if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
|
||||
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
|
||||
if res_ids:
|
||||
result['sub_object'] = res_ids[0]
|
||||
result['copyvalue'] = self.compute_pl(False,
|
||||
False,
|
||||
False,
|
||||
template_language)
|
||||
result['sub_model_object_field'] = False
|
||||
result['null_value'] = False
|
||||
else:
|
||||
#Its a simple field... just compute placeholder
|
||||
result['sub_object'] = False
|
||||
result['copyvalue'] = self.compute_pl(field_obj.name,
|
||||
False,
|
||||
False,
|
||||
template_language
|
||||
)
|
||||
result['sub_model_object_field'] = False
|
||||
result['null_value'] = False
|
||||
return {'value':result}
|
||||
|
||||
def onchange_sub_model_object_field(self, cr, uid, ids, model_object_field, sub_model_object_field, template_language, context=None):
|
||||
if not model_object_field or not sub_model_object_field:
|
||||
return {}
|
||||
result = {}
|
||||
field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
|
||||
if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
|
||||
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
|
||||
sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
|
||||
if res_ids:
|
||||
result['sub_object'] = res_ids[0]
|
||||
result['copyvalue'] = self.compute_pl(field_obj.name,
|
||||
sub_field_obj.name,
|
||||
False,
|
||||
template_language
|
||||
)
|
||||
result['sub_model_object_field'] = sub_model_object_field
|
||||
result['null_value'] = False
|
||||
else:
|
||||
#Its a simple field... just compute placeholder
|
||||
result['sub_object'] = False
|
||||
result['copyvalue'] = self.compute_pl(field_obj.name,
|
||||
False,
|
||||
False,
|
||||
template_language
|
||||
)
|
||||
result['sub_model_object_field'] = False
|
||||
result['null_value'] = False
|
||||
return {'value':result}
|
||||
|
||||
def onchange_null_value(self, cr, uid, ids, model_object_field, sub_model_object_field, null_value, template_language, context=None):
|
||||
if not model_object_field and not null_value:
|
||||
return {}
|
||||
result = {}
|
||||
field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
|
||||
if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
|
||||
res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
|
||||
sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
|
||||
if res_ids:
|
||||
result['sub_object'] = res_ids[0]
|
||||
result['copyvalue'] = self.compute_pl(field_obj.name,
|
||||
sub_field_obj.name,
|
||||
null_value,
|
||||
template_language
|
||||
)
|
||||
result['sub_model_object_field'] = sub_model_object_field
|
||||
result['null_value'] = null_value
|
||||
else:
|
||||
#Its a simple field... just compute placeholder
|
||||
result['sub_object'] = False
|
||||
result['copyvalue'] = self.compute_pl(field_obj.name,
|
||||
False,
|
||||
null_value,
|
||||
template_language
|
||||
)
|
||||
result['sub_model_object_field'] = False
|
||||
result['null_value'] = null_value
|
||||
return {'value':result}
|
||||
|
||||
def generate_attach_reports(self,
|
||||
cursor,
|
||||
user,
|
||||
template,
|
||||
record_id,
|
||||
mail,
|
||||
context=None):
|
||||
"""
|
||||
Generate report to be attached and attach it
|
||||
to the email
|
||||
|
||||
@param cursor: Database Cursor
|
||||
@param user: ID of User
|
||||
@param template: Browse record of
|
||||
template
|
||||
@param record_id: ID of the target model
|
||||
for which this mail has
|
||||
to be generated
|
||||
@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')
|
||||
new_att_vals = {
|
||||
'name':mail.subject + ' (Email Attachment)',
|
||||
'datas':base64.b64encode(result),
|
||||
'datas_fname':tools.ustr(
|
||||
get_value(
|
||||
cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.file_name,
|
||||
template,
|
||||
context
|
||||
) or 'Report') + "." + format,
|
||||
'description':mail.subject or "No Description",
|
||||
'res_model':'email_template.mailbox',
|
||||
'res_id':mail.id
|
||||
}
|
||||
attachment_id = attachment_obj.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)
|
||||
return True
|
||||
|
||||
def generate_mailbox_item_from_template(self,
|
||||
cursor,
|
||||
user,
|
||||
template,
|
||||
record_id,
|
||||
context=None):
|
||||
"""
|
||||
Generates an email from the template for
|
||||
record record_id of target object
|
||||
|
||||
@param cursor: Database Cursor
|
||||
@param user: ID of User
|
||||
@param template: Browse record of
|
||||
template
|
||||
@param record_id: ID of the target model
|
||||
for which this mail has
|
||||
to be generated
|
||||
@return: ID of created object
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
#If account to send from is in context select it, else use enforced account
|
||||
if 'account_id' in context.keys():
|
||||
from_account = self.pool.get('email_template.account').read(
|
||||
cursor,
|
||||
user,
|
||||
context.get('account_id'),
|
||||
['name', 'email_id'],
|
||||
context
|
||||
)
|
||||
else:
|
||||
from_account = {
|
||||
'id':template.enforce_from_account.id,
|
||||
'name':template.enforce_from_account.name,
|
||||
'email_id':template.enforce_from_account.email_id
|
||||
}
|
||||
lang = get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.lang,
|
||||
template,
|
||||
context)
|
||||
if lang:
|
||||
ctx = context.copy()
|
||||
ctx.update({'lang':lang})
|
||||
template = self.browse(cursor, user, template_id, context=ctx)
|
||||
mailbox_values = {
|
||||
'email_from': tools.ustr(from_account['name']) + \
|
||||
"<" + tools.ustr(from_account['email_id']) + ">",
|
||||
'email_to':get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.def_to,
|
||||
template,
|
||||
context),
|
||||
'email_cc':get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.def_cc,
|
||||
template,
|
||||
context),
|
||||
'email_bcc':get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.def_bcc,
|
||||
template,
|
||||
context),
|
||||
'subject':get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.def_subject,
|
||||
template,
|
||||
context),
|
||||
'body_text':get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.def_body_text,
|
||||
template,
|
||||
context),
|
||||
'body_html':get_value(cursor,
|
||||
user,
|
||||
record_id,
|
||||
template.def_body_html,
|
||||
template,
|
||||
context),
|
||||
'account_id' :from_account['id'],
|
||||
#This is a mandatory field when automatic emails are sent
|
||||
'state':'na',
|
||||
'folder':'drafts',
|
||||
'mail_type':'multipart/alternative'
|
||||
}
|
||||
#Use signatures if allowed
|
||||
if template.use_sign:
|
||||
sign = self.pool.get('res.users').read(cursor,
|
||||
user,
|
||||
user,
|
||||
['signature'],
|
||||
context)['signature']
|
||||
if mailbox_values['body_text']:
|
||||
mailbox_values['body_text'] += sign
|
||||
if mailbox_values['body_html']:
|
||||
mailbox_values['body_html'] += sign
|
||||
mailbox_id = self.pool.get('email_template.mailbox').create(
|
||||
cursor,
|
||||
user,
|
||||
mailbox_values,
|
||||
context)
|
||||
return mailbox_id
|
||||
|
||||
def generate_mail(self,
|
||||
cursor,
|
||||
user,
|
||||
template_id,
|
||||
record_ids,
|
||||
context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
template = self.browse(cursor, user, template_id, context=context)
|
||||
if not template:
|
||||
raise Exception("The requested template could not be loaded")
|
||||
for record_id in record_ids:
|
||||
mailbox_id = self._generate_mailbox_item_from_template(
|
||||
cursor,
|
||||
user,
|
||||
template,
|
||||
record_id,
|
||||
context)
|
||||
mail = self.pool.get('email_template.mailbox').browse(
|
||||
cursor,
|
||||
user,
|
||||
mailbox_id,
|
||||
context=context
|
||||
)
|
||||
if template.report_template:
|
||||
self._generate_attach_reports(
|
||||
cursor,
|
||||
user,
|
||||
template,
|
||||
record_id,
|
||||
mail,
|
||||
context
|
||||
)
|
||||
self.pool.get('email_template.mailbox').write(
|
||||
cursor,
|
||||
user,
|
||||
mailbox_id,
|
||||
{'folder':'outbox'},
|
||||
context=context
|
||||
)
|
||||
return True
|
||||
|
||||
email_template()
|
||||
|
||||
class email_template_preview(osv.osv_memory):
|
||||
_name = "email_template.preview"
|
||||
_description = "Email Template Preview"
|
||||
|
||||
def _get_model_recs(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
#Fills up the selection box which allows records from the selected object to be displayed
|
||||
self.context = context
|
||||
if 'active_id' in context.keys():
|
||||
# context['active_id'] = 5
|
||||
ref_obj_id = self.pool.get('email.template').read(cr, uid, context['active_id'], ['object_name'], context)
|
||||
ref_obj_name = self.pool.get('ir.model').read(cr, uid, ref_obj_id[0], ['model'], context)['model']
|
||||
ref_obj_ids = self.pool.get(ref_obj_name).search(cr, uid, [], context=context)
|
||||
ref_obj_recs = self.pool.get(ref_obj_name).name_get(cr, uid, ref_obj_ids, context)
|
||||
return ref_obj_recs
|
||||
|
||||
def _default_model(self, cursor, user, context=None):
|
||||
"""
|
||||
Returns the default value for model field
|
||||
@param cursor: Database Cursor
|
||||
@param user: ID of current user
|
||||
@param context: Open ERP Context
|
||||
"""
|
||||
return self.pool.get('email.template').read(
|
||||
cursor,
|
||||
user,
|
||||
context['active_id'],
|
||||
['object_name'],
|
||||
context)['object_name']
|
||||
|
||||
_columns = {
|
||||
'ref_template':fields.many2one(
|
||||
'email.template',
|
||||
'Template', readonly=True),
|
||||
'rel_model':fields.many2one('ir.model', 'Model', readonly=True),
|
||||
'rel_model_ref':fields.selection(_get_model_recs, 'Referred Document'),
|
||||
'to':fields.char('To', size=250, readonly=True),
|
||||
'cc':fields.char('CC', size=250, readonly=True),
|
||||
'bcc':fields.char('BCC', size=250, readonly=True),
|
||||
'subject':fields.char('Subject', size=200, readonly=True),
|
||||
'body_text':fields.text('Body', readonly=True),
|
||||
'body_html':fields.text('Body', readonly=True),
|
||||
'report':fields.char('Report Name', size=100, readonly=True),
|
||||
}
|
||||
_defaults = {
|
||||
'ref_template': lambda self, cr, uid, ctx:ctx['active_id'],
|
||||
'rel_model': _default_model
|
||||
}
|
||||
|
||||
def on_change_ref(self, cr, uid, ids, rel_model_ref, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not rel_model_ref:
|
||||
return {}
|
||||
vals = {}
|
||||
if context == {}:
|
||||
context = self.context
|
||||
template = self.pool.get('email.template').browse(cr, uid, context['active_id'], context)
|
||||
#Search translated template
|
||||
lang = get_value(cr, uid, rel_model_ref, template.lang, template, context)
|
||||
if lang:
|
||||
ctx = context.copy()
|
||||
ctx.update({'lang':lang})
|
||||
template = self.pool.get('email.template').browse(cr, uid, context['active_id'], ctx)
|
||||
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['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)
|
||||
vals['report'] = get_value(cr, uid, rel_model_ref, template.file_name, template, context)
|
||||
return {'value':vals}
|
||||
|
||||
email_template_preview()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,440 @@
|
|||
from osv import osv, fields
|
||||
from html2text import html2text
|
||||
import re
|
||||
import smtplib
|
||||
import base64
|
||||
from email import Encoders
|
||||
from email.mime.base import MIMEBase
|
||||
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
|
||||
from tools.translate import _
|
||||
import tools
|
||||
|
||||
class email_template_account(osv.osv):
|
||||
"""
|
||||
Object to store email account settings
|
||||
"""
|
||||
_name = "email_template.account"
|
||||
_known_content_types = ['multipart/mixed',
|
||||
'multipart/alternative',
|
||||
'multipart/related',
|
||||
'text/plain',
|
||||
'text/html'
|
||||
]
|
||||
_columns = {
|
||||
'name': fields.char('Email Account Desc',
|
||||
size=64, required=True,
|
||||
readonly=True, select=True,
|
||||
states={'draft':[('readonly', False)]}),
|
||||
'user':fields.many2one('res.users',
|
||||
'Related User', required=True,
|
||||
readonly=True, states={'draft':[('readonly', False)]}),
|
||||
'email_id': fields.char('Email ID',
|
||||
size=120, required=True,
|
||||
readonly=True, states={'draft':[('readonly', False)]} ,
|
||||
help=" eg:yourname@yourdomain.com "),
|
||||
'smtpserver': fields.char('Server',
|
||||
size=120, required=True,
|
||||
readonly=True, states={'draft':[('readonly', False)]},
|
||||
help="Enter name of outgoing server,eg:smtp.gmail.com "),
|
||||
'smtpport': fields.integer('SMTP Port ',
|
||||
size=64, required=True,
|
||||
readonly=True, states={'draft':[('readonly', False)]},
|
||||
help="Enter port number,eg:SMTP-587 "),
|
||||
'smtpuname': fields.char('User Name',
|
||||
size=120, required=False,
|
||||
readonly=True, states={'draft':[('readonly', False)]}),
|
||||
'smtppass': fields.char('Password',
|
||||
size=120, invisible=True,
|
||||
required=False, readonly=True,
|
||||
states={'draft':[('readonly', False)]}),
|
||||
'smtptls':fields.boolean('Use TLS',
|
||||
states={'draft':[('readonly', False)]}, readonly=True),
|
||||
|
||||
'smtpssl':fields.boolean('Use 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')
|
||||
], 'Mail Format', required=True),
|
||||
'allowed_groups':fields.many2many(
|
||||
'res.groups',
|
||||
'account_group_rel', 'templ_id', 'group_id',
|
||||
string="Allowed User Groups",
|
||||
help="Only users from these groups will be" \
|
||||
"allowed to send mails from this ID"),
|
||||
'company':fields.selection([
|
||||
('yes', 'Yes'),
|
||||
('no', 'No')
|
||||
], 'Company Mail A/c',
|
||||
readonly=True,
|
||||
help="Select if this mail account does not belong" \
|
||||
"to specific user but the organisation as a whole." \
|
||||
"eg:info@somedomain.com",
|
||||
required=True, states={
|
||||
'draft':[('readonly', False)]
|
||||
}),
|
||||
|
||||
'state':fields.selection([
|
||||
('draft', 'Initiated'),
|
||||
('suspended', 'Suspended'),
|
||||
('approved', 'Approved')
|
||||
],
|
||||
'Account Status', required=True, readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'name':lambda self, cursor, user, context:self.pool.get(
|
||||
'res.users'
|
||||
).read(
|
||||
cursor,
|
||||
user,
|
||||
user,
|
||||
['name'],
|
||||
context
|
||||
)['name'],
|
||||
'smtpssl':lambda * a:True,
|
||||
'state':lambda * a:'draft',
|
||||
'user':lambda self, cursor, user, context:user,
|
||||
'send_pref':lambda * a: 'html',
|
||||
'smtptls':lambda * a:True,
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
'email_uniq',
|
||||
'unique (email_id)',
|
||||
'Another setting already exists with this email ID !')
|
||||
]
|
||||
|
||||
def _constraint_unique(self, cursor, user, ids):
|
||||
"""
|
||||
This makes sure that you dont give personal
|
||||
users two accounts with same ID (Validated in sql constaints)
|
||||
However this constraint exempts company accounts.
|
||||
Any no of co accounts for a user is allowed
|
||||
"""
|
||||
if self.read(cursor, user, ids, ['company'])[0]['company'] == 'no':
|
||||
accounts = self.search(cursor, user, [
|
||||
('user', '=', user),
|
||||
('company', '=', 'no')
|
||||
])
|
||||
if len(accounts) > 1 :
|
||||
return False
|
||||
else :
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_constraint_unique,
|
||||
'Error: You are not allowed to have more than 1 account.',
|
||||
[])
|
||||
]
|
||||
|
||||
def on_change_emailid(self, cursor, user, ids, name=None, email_id=None, context=None):
|
||||
"""
|
||||
Called when the email ID field changes.
|
||||
|
||||
UI enhancement
|
||||
Writes the same email value to the smtpusername
|
||||
and incoming username
|
||||
"""
|
||||
#TODO: Check and remove the write. Is it needed?
|
||||
self.write(cursor, user, ids, {'state':'draft'}, context=context)
|
||||
return {
|
||||
'value': {
|
||||
'state': 'draft',
|
||||
'smtpuname':email_id,
|
||||
'isuser':email_id
|
||||
}
|
||||
}
|
||||
|
||||
def get_outgoing_server(self, cursor, user, ids, context=None):
|
||||
"""
|
||||
Returns the Out Going Connection (SMTP) object
|
||||
|
||||
@attention: DO NOT USE except_osv IN THIS METHOD
|
||||
@param cursor: Database Cursor
|
||||
@param user: ID of current user
|
||||
@param ids: ID/list of ids of current object for
|
||||
which connection is required
|
||||
First ID will be chosen from lists
|
||||
@param context: Context
|
||||
|
||||
@return: SMTP server object or Exception
|
||||
"""
|
||||
#Type cast ids to integer
|
||||
if type(ids) == list:
|
||||
ids = ids[0]
|
||||
this_object = self.browse(cursor, user, ids, context)
|
||||
if this_object:
|
||||
if this_object.smtpserver and this_object.smtpport:
|
||||
try:
|
||||
if this_object.smtpssl:
|
||||
serv = smtplib.SMTP_SSL(this_object.smtpserver, this_object.smtpport)
|
||||
else:
|
||||
serv = smtplib.SMTP(this_object.smtpserver, this_object.smtpport)
|
||||
if this_object.smtptls:
|
||||
serv.ehlo()
|
||||
serv.starttls()
|
||||
serv.ehlo()
|
||||
except Exception, error:
|
||||
raise error
|
||||
try:
|
||||
if serv.has_extn('AUTH') or this_object.smtpuname or this_object.smtppass:
|
||||
serv.login(this_object.smtpuname, this_object.smtppass)
|
||||
except Exception, error:
|
||||
raise error
|
||||
return serv
|
||||
raise Exception(_("SMTP SERVER or PORT not specified"))
|
||||
raise Exception(_("Core connection for the given ID does not exist"))
|
||||
|
||||
def check_outgoing_connection(self, cursor, user, ids, context=None):
|
||||
"""
|
||||
checks SMTP credentials and confirms if outgoing connection works
|
||||
(Attached to button)
|
||||
@param cursor: Database Cursor
|
||||
@param user: ID of current user
|
||||
@param ids: list of ids of current object for
|
||||
which connection is required
|
||||
@param context: Context
|
||||
"""
|
||||
try:
|
||||
self.get_outgoing_server(cursor, user, ids, context)
|
||||
raise osv.except_osv(_("SMTP Test Connection Was Successful"), '')
|
||||
except osv.except_osv, success_message:
|
||||
raise success_message
|
||||
except Exception, error:
|
||||
raise osv.except_osv(
|
||||
_("Out going connection test failed"),
|
||||
_("Reason: %s") % error
|
||||
)
|
||||
|
||||
def do_approval(self, cr, uid, ids, context={}):
|
||||
#TODO: Check if user has rights
|
||||
self.write(cr, uid, ids, {'state':'approved'}, context=context)
|
||||
# wf_service = netsvc.LocalService("workflow")
|
||||
|
||||
def smtp_connection(self, cursor, user, id, context=None):
|
||||
"""
|
||||
This method should now wrap smtp_connection
|
||||
"""
|
||||
#This function returns a SMTP server object
|
||||
logger = netsvc.Logger()
|
||||
core_obj = self.browse(cursor, user, id, context)
|
||||
if core_obj.smtpserver and core_obj.smtpport and core_obj.state == 'approved':
|
||||
try:
|
||||
serv = self.get_outgoing_server(cursor, user, id, context)
|
||||
except Exception, error:
|
||||
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))
|
||||
return False
|
||||
#Everything is complete, now return the connection
|
||||
return serv
|
||||
else:
|
||||
logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
|
||||
return False
|
||||
|
||||
#**************************** MAIL SENDING FEATURES ***********************#
|
||||
def split_to_ids(self, ids_as_str):
|
||||
"""
|
||||
Identifies email IDs separated by separators
|
||||
and returns a list
|
||||
TODO: Doc this
|
||||
"a@b.com,c@bcom; d@b.com;e@b.com->['a@b.com',...]"
|
||||
"""
|
||||
email_sep_by_commas = ids_as_str \
|
||||
.replace('; ', ',') \
|
||||
.replace(';', ',') \
|
||||
.replace(', ', ',')
|
||||
return email_sep_by_commas.split(',')
|
||||
|
||||
def get_ids_from_dict(self, addresses={}):
|
||||
"""
|
||||
TODO: Doc this
|
||||
"""
|
||||
result = {'all':[]}
|
||||
keys = ['To', 'CC', 'BCC']
|
||||
for each in keys:
|
||||
ids_as_list = self.split_to_ids(addresses.get(each, u''))
|
||||
while u'' in ids_as_list:
|
||||
ids_as_list.remove(u'')
|
||||
result[each] = ids_as_list
|
||||
result['all'].extend(ids_as_list)
|
||||
return result
|
||||
|
||||
def send_mail(self, cr, uid, ids, addresses, subject='', body=None, payload=None, context=None):
|
||||
#TODO: Replace all this crap with a single email object
|
||||
if body is None:
|
||||
body = {}
|
||||
if payload is None:
|
||||
payload = {}
|
||||
if context is None:
|
||||
context = {}
|
||||
logger = netsvc.Logger()
|
||||
for id in ids:
|
||||
core_obj = self.browse(cr, uid, id, context)
|
||||
serv = self.smtp_connection(cr, uid, id)
|
||||
if serv:
|
||||
try:
|
||||
msg = MIMEMultipart()
|
||||
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()
|
||||
addresses_l = self.get_ids_from_dict(addresses)
|
||||
if addresses_l['To']:
|
||||
msg['To'] = u','.join(addresses_l['To'])
|
||||
if addresses_l['CC']:
|
||||
msg['CC'] = u','.join(addresses_l['CC'])
|
||||
# if addresses_l['BCC']:
|
||||
# msg['BCC'] = u','.join(addresses_l['BCC'])
|
||||
if body.get('text', False):
|
||||
temp_body_text = body.get('text', '')
|
||||
l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', ''))
|
||||
if l == 0:
|
||||
body['text'] = u'No Mail Message'
|
||||
# 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')
|
||||
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':
|
||||
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 = 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)
|
||||
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
|
||||
try:
|
||||
#print msg['From'],toadds
|
||||
serv.sendmail(msg['From'], addresses_l['all'], msg.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
|
||||
#The mail sending is complete
|
||||
serv.close()
|
||||
logger.notifyChannel(_("Email Template"), netsvc.LOG_INFO, _("Mail from Account %s successfully Sent.") % (id))
|
||||
return True
|
||||
else:
|
||||
logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
|
||||
|
||||
def extracttime(self, time_as_string):
|
||||
"""
|
||||
TODO: DOC THis
|
||||
"""
|
||||
logger = netsvc.Logger()
|
||||
#The standard email dates are of format similar to:
|
||||
#Thu, 8 Oct 2009 09:35:42 +0200
|
||||
#print time_as_string
|
||||
date_as_date = False
|
||||
convertor = {'+':1, '-':-1}
|
||||
try:
|
||||
time_as_string = time_as_string.replace(',', '')
|
||||
date_list = time_as_string.split(' ')
|
||||
date_temp_str = ' '.join(date_list[1:5])
|
||||
if len(date_list) >= 6:
|
||||
sign = convertor.get(date_list[5][0], False)
|
||||
else:
|
||||
sign = False
|
||||
try:
|
||||
dt = datetime.datetime.strptime(
|
||||
date_temp_str,
|
||||
"%d %b %Y %H:%M:%S")
|
||||
except:
|
||||
try:
|
||||
dt = datetime.datetime.strptime(
|
||||
date_temp_str,
|
||||
"%d %b %Y %H:%M")
|
||||
except:
|
||||
return False
|
||||
if sign:
|
||||
try:
|
||||
offset = datetime.timedelta(
|
||||
hours=sign * int(
|
||||
date_list[5][1:3]
|
||||
),
|
||||
minutes=sign * int(
|
||||
date_list[5][3:5]
|
||||
)
|
||||
)
|
||||
except Exception, e2:
|
||||
"""Looks like UT or GMT, just forget decoding"""
|
||||
return False
|
||||
else:
|
||||
offset = datetime.timedelta(hours=0)
|
||||
dt = dt + offset
|
||||
date_as_date = dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
#print date_as_date
|
||||
except Exception, e:
|
||||
logger.notifyChannel(
|
||||
_("Email Template"),
|
||||
netsvc.LOG_WARNING,
|
||||
_(
|
||||
"Datetime Extraction failed.Date:%s \
|
||||
\tError:%s") % (
|
||||
time_as_string,
|
||||
e)
|
||||
)
|
||||
return date_as_date
|
||||
|
||||
def send_receive(self, cr, uid, ids, context=None):
|
||||
self.get_mails(cr, uid, ids, context)
|
||||
for id in ids:
|
||||
ctx = context.copy()
|
||||
ctx['filters'] = [('account_id', '=', id)]
|
||||
self.pool.get('email_template.mailbox').send_all_mail(cr, uid, [], context=ctx)
|
||||
return True
|
||||
|
||||
def decode_header_text(self, text):
|
||||
""" Decode internationalized headers RFC2822.
|
||||
To, CC, BCC, Subject fields can contain
|
||||
text slices with different encodes, like:
|
||||
=?iso-8859-1?Q?Enric_Mart=ED?= <enricmarti@company.com>,
|
||||
=?Windows-1252?Q?David_G=F3mez?= <david@company.com>
|
||||
Sometimes they include extra " character at the beginning/
|
||||
end of the contact name, like:
|
||||
"=?iso-8859-1?Q?Enric_Mart=ED?=" <enricmarti@company.com>
|
||||
and decode_header() does not work well, so we use regular
|
||||
expressions (?= ? ? ?=) to split the text slices
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
p = re.compile("(=\?.*?\?.\?.*?\?=)")
|
||||
text2 = ''
|
||||
try:
|
||||
for t2 in p.split(text):
|
||||
text2 += ''.join(
|
||||
[s.decode(
|
||||
t or 'ascii'
|
||||
) for (s, t) in decode_header(t2)]
|
||||
).encode('utf-8')
|
||||
except:
|
||||
return text
|
||||
return text2
|
||||
|
||||
email_template_account()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<menuitem name="Tools" id="base.menu_tools" icon="STOCK_PREFERENCES" sequence="15"/>
|
||||
<menuitem name="Email Template" id="menu_email_template" parent="base.menu_tools"/>
|
||||
|
||||
<record model="ir.ui.view" id="email_template_account_form">
|
||||
<field name="name">email_template.account.form</field>
|
||||
<field name="model">email_template.account</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Email Account Configuration">
|
||||
<group colspan="2">
|
||||
<field name="name" select="1" />
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Outgoing">
|
||||
<separator string="Server Information" colspan="4" />
|
||||
<group colspan="4">
|
||||
<field name="smtpserver" select="1" colspan="2" />
|
||||
<field name="smtpport" select="2" colspan="2" />
|
||||
<field name="smtpssl" select="2" colspan="2" />
|
||||
<field name="smtptls" select="2" colspan="2" />
|
||||
</group>
|
||||
<button name="check_outgoing_connection" type="object" string="Check Outgoing Connection" />
|
||||
<separator string="User Information" colspan="4" />
|
||||
<group col="2" colspan="2">
|
||||
<field name="email_id" select="1" on_change="on_change_emailid(name,email_id)" colspan="2" />
|
||||
<field name="smtppass" password="True" colspan="2" />
|
||||
<field name="company" select="2" colspan="2" />
|
||||
</group>
|
||||
<group col="2" colspan="2">
|
||||
<field name="smtpuname" select="1" colspan="2" />
|
||||
<field name="user" select="2" colspan="2" />
|
||||
<field name="send_pref" colspan="2" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="security" attrs="{'invisible':[('company','!=','yes')]}">
|
||||
<field name="allowed_groups" attrs="{'required':[('company','=','yes')]}" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<group colspan="4" col="10">
|
||||
<field name="state" select="1"/>
|
||||
<button string="Approve Account" name="button_approval" states="draft" type="workflow"/>
|
||||
<button string="Suspend Account" name="button_suspended" states="approved" type="workflow" />
|
||||
<button string="Request Re-activation" name="get_reapprove" states="suspended" type="workflow" />
|
||||
<button string="Send/Receive" name="send_receive" states="approved" type="object" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="email_template_account_tree">
|
||||
<field name="name">email_template.account.tree</field>
|
||||
<field name="model">email_template.account</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="SMTP Server">
|
||||
<field name="name" select="2" />
|
||||
<field name="email_id" select="2" />
|
||||
<field name="smtpuname" select="2" />
|
||||
<field name="user" select="2" />
|
||||
<field name="smtpserver" select="2" />
|
||||
<field name="smtpport" select="2" />
|
||||
<field name="state" select="2" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_email_template_account_search" model="ir.ui.view">
|
||||
<field name="name">email_template.account.search</field>
|
||||
<field name="model">email_template.account</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Accounts">
|
||||
<filter icon="terp-crm" string="My Accounts" name="my" domain="[('user','=',uid)]"/>
|
||||
<filter icon="terp-crm" string="Personal Accounts" domain="[('company','=','no')]"/>
|
||||
<filter icon="terp-crm" string="Company Accounts" domain="[('company','=','yes')]"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter icon="terp-crm" string="Draft" name="draft" domain="[('state','=','draft')]"/>
|
||||
<filter icon="terp-crm" string="Suspended" domain="[('state','=','suspended')]"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select="1"/>
|
||||
<field name="user" select="1"/>
|
||||
<field name="email_id" select="1"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_email_template_account_tree_all">
|
||||
<field name="name">Accounts</field>
|
||||
<field name="res_model">email_template.account</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" ref="email_template_account_tree" />
|
||||
<field name="context">{'group_by': [], 'search_default_draft': 1, 'search_default_my': 1}</field>
|
||||
<field name="search_view_id" ref="view_email_template_account_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Configuration" id="menu_email_template_configuration" parent="menu_email_template" />
|
||||
|
||||
<menuitem name="All Accounts" id="menu_email_template_account_all" parent="menu_email_template_configuration" action="action_email_template_account_tree_all" groups="res_groups_email_template_manager" />
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# To change this template, choose Tools | Templates
|
||||
# and open the template in the editor.
|
||||
from osv import fields,osv
|
||||
import pooler
|
||||
import netsvc
|
||||
import re
|
||||
|
||||
class email_template_engines(osv.osv):
|
||||
_name = "email_template.engines"
|
||||
_description = "Email Template Engine"
|
||||
|
||||
# def __init__(self):
|
||||
# print "Started Engine"
|
||||
|
||||
def check(self):
|
||||
print "Start self check"
|
||||
|
||||
def strip_html(self,text):
|
||||
#Removes HTML, Have to check if still relevent
|
||||
if text:
|
||||
def fixup(m):
|
||||
text = m.group(0)
|
||||
if text[:1] == "<":
|
||||
return "" # ignore tags
|
||||
if text[:2] == "&#":
|
||||
try:
|
||||
if text[:3] == "&#x":
|
||||
return unichr(int(text[3:-1], 16))
|
||||
else:
|
||||
return unichr(int(text[2:-1]))
|
||||
except ValueError:
|
||||
pass
|
||||
elif text[:1] == "&":
|
||||
import htmlentitydefs
|
||||
entity = htmlentitydefs.entitydefs.get(text[1:-1])
|
||||
if entity:
|
||||
if entity[:2] == "&#":
|
||||
try:
|
||||
return unichr(int(entity[2:-1]))
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
return unicode(entity, "iso-8859-1")
|
||||
return text # leave as is
|
||||
return re.sub("(?s)<[^>]*>|&#?\w+;", fixup, text)
|
||||
|
||||
def parsevalue(self,cr,uid,id,message,templateid,context):
|
||||
#id: ID of the template's model's record to be used
|
||||
#message: the complete text including placeholders
|
||||
#templateid: the template id of the template
|
||||
#context: TODO
|
||||
#print cr,uid,id,message,templateid,context
|
||||
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)
|
||||
obj = obj_pool.browse(cr, uid, id, context)
|
||||
exp = str(match.group()[2:-2]).strip()
|
||||
#print "level 1:",exp
|
||||
exp_spl = exp.split('/')
|
||||
#print "level 2:",exp_spl
|
||||
try:
|
||||
result = eval(exp_spl[0], {'object':obj,})
|
||||
except:
|
||||
result = "Rendering Error"
|
||||
#print "result:",result
|
||||
try:
|
||||
if result in (None, False):
|
||||
if len(exp_spl)>1:
|
||||
return exp_spl[1]
|
||||
else:
|
||||
return 'Not Available'
|
||||
return str(result)
|
||||
except:
|
||||
return "Rendering Error"
|
||||
if message:
|
||||
com = re.compile('(\[\[.+?\]\])')
|
||||
retmessage = com.sub(merge, message)
|
||||
else:
|
||||
retmessage=""
|
||||
return retmessage
|
||||
|
||||
email_template_engines()
|
|
@ -0,0 +1,182 @@
|
|||
from osv import osv, fields
|
||||
import time
|
||||
import email_template_engines
|
||||
import netsvc
|
||||
from tools.translate import _
|
||||
import tools
|
||||
|
||||
LOGGER = netsvc.Logger()
|
||||
|
||||
class email_template_mailbox(osv.osv):
|
||||
_name = "email_template.mailbox"
|
||||
_description = 'Email Mailbox'
|
||||
_rec_name = "subject"
|
||||
_order = "date_mail desc"
|
||||
|
||||
def run_mail_scheduler(self, cursor, user, context=None):
|
||||
"""
|
||||
This method is called by Open ERP Scheduler
|
||||
to periodically send emails
|
||||
"""
|
||||
try:
|
||||
self.send_all_mail(cursor, user, context)
|
||||
except Exception, e:
|
||||
LOGGER.notifyChannel(
|
||||
_("Email Template"),
|
||||
netsvc.LOG_ERROR,
|
||||
_("Error sending mail: %s" % str(e)))
|
||||
|
||||
def send_all_mail(self, cr, uid, ids=None, context=None):
|
||||
if ids is None:
|
||||
ids = []
|
||||
if context is None:
|
||||
context = {}
|
||||
filters = [('folder', '=', 'outbox'), ('state', '!=', 'sending')]
|
||||
if 'filters' in context.keys():
|
||||
for each_filter in context['filters']:
|
||||
filters.append(each_filter)
|
||||
ids = self.search(cr, uid, filters, context=context)
|
||||
self.write(cr, uid, ids, {'state':'sending'}, context)
|
||||
self.send_this_mail(cr, uid, ids, context)
|
||||
return True
|
||||
|
||||
def send_this_mail(self, cr, uid, ids=None, context=None):
|
||||
if ids is None:
|
||||
ids = []
|
||||
for id in ids:
|
||||
try:
|
||||
account_obj = self.pool.get('email_template.account')
|
||||
values = self.read(cr, uid, id, [], context)
|
||||
payload = {}
|
||||
if values['attachments_ids']:
|
||||
for attid in values['attachments_ids']:
|
||||
attachment = self.pool.get('ir.attachment').browse(cr, uid, attid, context)#,['datas_fname','datas'])
|
||||
payload[attachment.datas_fname] = attachment.datas
|
||||
print "233333333333333"
|
||||
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''},
|
||||
values['subject'] or u'',
|
||||
{'text':values.get('body_text', u'') or u'', 'html':values.get('body_html', u'') or u''},
|
||||
payload=payload, 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)
|
||||
else:
|
||||
self.historise(cr, uid, [id], result, context)
|
||||
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))
|
||||
self.historise(cr, uid, [id], error, context)
|
||||
self.write(cr, uid, id, {'state':'na'}, context)
|
||||
return True
|
||||
|
||||
def historise(self, cr, uid, ids, message='', context=None):
|
||||
for id in ids:
|
||||
history = self.read(cr, uid, id, ['history'], context).get('history', '')
|
||||
self.write(cr, uid, id, {'history':history or '' + "\n" + time.strftime("%Y-%m-%d %H:%M:%S") + ": " + tools.ustr(message)}, context)
|
||||
|
||||
_columns = {
|
||||
'email_from':fields.char(
|
||||
'From',
|
||||
size=64),
|
||||
'email_to':fields.char(
|
||||
'Recepient (To)',
|
||||
size=250,),
|
||||
'email_cc':fields.char(
|
||||
' CC',
|
||||
size=250),
|
||||
'email_bcc':fields.char(
|
||||
' BCC',
|
||||
size=250),
|
||||
'subject':fields.char(
|
||||
' Subject',
|
||||
size=200,),
|
||||
'body_text':fields.text(
|
||||
'Standard Body (Text)'),
|
||||
'body_html':fields.text(
|
||||
'Body (Text-Web Client Only)'),
|
||||
'attachments_ids':fields.many2many(
|
||||
'ir.attachment',
|
||||
'mail_attachments_rel',
|
||||
'mail_id',
|
||||
'att_id',
|
||||
'Attachments'),
|
||||
'account_id' :fields.many2one(
|
||||
'email_template.account',
|
||||
'User account',
|
||||
required=True),
|
||||
'user':fields.related(
|
||||
'account_id',
|
||||
'user',
|
||||
type="many2one",
|
||||
relation="res.users",
|
||||
string="User"),
|
||||
'server_ref':fields.integer(
|
||||
'Server Reference of mail',
|
||||
help="Applicable for inward items only"),
|
||||
'mail_type':fields.selection([
|
||||
('multipart/mixed',
|
||||
'Has Attachments'),
|
||||
('multipart/alternative',
|
||||
'Plain Text & HTML with no attachments'),
|
||||
('multipart/related',
|
||||
'Intermixed content'),
|
||||
('text/plain',
|
||||
'Plain Text'),
|
||||
('text/html',
|
||||
'HTML Body'),
|
||||
], 'Mail Contents'),
|
||||
#I like GMAIL which allows putting same mail in many folders
|
||||
#Lets plan it for 0.9
|
||||
'folder':fields.selection([
|
||||
('drafts', 'Drafts'),
|
||||
('outbox', 'Outbox'),
|
||||
('trash', 'Trash'),
|
||||
('sent', 'Sent Items'),
|
||||
], 'Folder', required=True),
|
||||
'state':fields.selection([
|
||||
('na', 'Not Applicable'),
|
||||
('sending', 'Sending'),
|
||||
], 'Status', required=True),
|
||||
'date_mail':fields.datetime(
|
||||
'Rec/Sent Date'),
|
||||
'history':fields.text(
|
||||
'History',
|
||||
readonly=True,
|
||||
store=True)
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': lambda * a: 'na',
|
||||
'folder': lambda * a: 'outbox',
|
||||
}
|
||||
|
||||
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||
if context is None:
|
||||
context = {}
|
||||
if context.get('company', False):
|
||||
users_groups = self.pool.get('res.users').browse(cr, uid, uid, context).groups_id
|
||||
group_acc_rel = {}
|
||||
#get all accounts and get a table of {group1:[account1,account2],group2:[account1]}
|
||||
for each_account_id in self.pool.get('email_template.account').search(cr, uid, [('state', '=', 'approved'), ('company', '=', 'yes')], context=context):
|
||||
account = self.pool.get('email_template.account').browse(cr, uid, each_account_id, context)
|
||||
for each_group in account.allowed_groups:
|
||||
if not account.id in group_acc_rel.get(each_group, []):
|
||||
groups = group_acc_rel.get(each_group, [])
|
||||
groups.append(account.id)
|
||||
group_acc_rel[each_group] = groups
|
||||
users_company_accounts = []
|
||||
for each_group in group_acc_rel.keys():
|
||||
if each_group in users_groups:
|
||||
for each_account in group_acc_rel[each_group]:
|
||||
if not each_account in users_company_accounts:
|
||||
users_company_accounts.append(each_account)
|
||||
args.append(('account_id', 'in', users_company_accounts))
|
||||
return super(osv.osv, self).search(cr, uid, args, offset, limit,
|
||||
order, context=context, count=count)
|
||||
|
||||
email_template_mailbox()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Email Template-->
|
||||
<record model="ir.ui.view" id="email_template_mailbox_form">
|
||||
<field name="name">email_template.mailbox.form</field>
|
||||
<field name="model">email_template.mailbox</field>
|
||||
<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"/>
|
||||
<field name="subject" colspan="4" select="1"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Standard Body">
|
||||
<separator colspan="4" string="Standard Body" />
|
||||
<notebook colspan="4">
|
||||
<page string="Standard Body (Text)">
|
||||
<field name="body_text" nolabel="1" colspan="4" select="1"/>
|
||||
</page>
|
||||
<page string="Body (HTML-Web Client Only)">
|
||||
<field name="body_html" nolabel="1" colspan="4" />
|
||||
<!--<label string="Note: HTML body can't be edited with GTK desktop client." colspan="4"/>
|
||||
--></page>
|
||||
|
||||
</notebook>
|
||||
</page>
|
||||
|
||||
|
||||
<page string="Attachments">
|
||||
<group col="4">
|
||||
<separator colspan="4" string="Attachments" />
|
||||
<field name="attachments_ids" colspan="4" nolabel="1" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="Advanced">
|
||||
<group col="4">
|
||||
<field name="account_id" colspan="2" />
|
||||
<field name="server_ref" colspan="2" />
|
||||
<field name="mail_type" colspan="2" />
|
||||
<field name="folder" colspan="2" select="2"/>
|
||||
<separator string="History" colspan="4" />
|
||||
<field name="history" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
<separator colspan="4" string="" />
|
||||
<group col="4" colspan="4">
|
||||
<field name="state" colspan="2" readonly="1" />
|
||||
<button name="complete_mail" type="object" string="Download Full Mail" colspan="2" states="read,unread" />
|
||||
<button name="send_this_mail" type="object" string="Send Mail" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--============================================= TREE VIEWS =============================================-->
|
||||
<!--DRAFTS-->
|
||||
<record model="ir.ui.view" id="email_template_drafts_tree">
|
||||
<field name="name">email_template.mailbox.draftstree</field>
|
||||
<field name="model">email_template.mailbox</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Drafts">
|
||||
<field name="user" />
|
||||
<field name="email_from" select="1" />
|
||||
<field name="subject" select="1" />
|
||||
<field name="attachments_ids" select="2" />
|
||||
<field name="date_mail" select="2" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!--OUTBOX-->
|
||||
<record model="ir.ui.view" id="email_template_outbox_tree">
|
||||
<field name="name">email_template.mailbox.outboxtree</field>
|
||||
<field name="model">email_template.mailbox</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Outbox">
|
||||
<field name="user" />
|
||||
<field name="email_from" select="1" />
|
||||
<field name="subject" select="1" />
|
||||
<field name="attachments_ids" select="2" />
|
||||
<field name="date_mail" select="2" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!--SENT-->
|
||||
<record model="ir.ui.view" id="email_template_sentbox_tree">
|
||||
<field name="name">email_template.mailbox.sentboxtree</field>
|
||||
<field name="model">email_template.mailbox</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Sent">
|
||||
<field name="user" />
|
||||
<field name="email_from" select="1" />
|
||||
<field name="subject" select="1" />
|
||||
<field name="attachments_ids" select="2" />
|
||||
<field name="date_mail" select="2" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!--TRASH-->
|
||||
<record model="ir.ui.view" id="email_template_trashbox_tree">
|
||||
<field name="name">email_template.mailbox.trashboxtree</field>
|
||||
<field name="model">email_template.mailbox</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Trash">
|
||||
<field name="user" />
|
||||
<field name="email_from" select="1" />
|
||||
<field name="subject" select="1" />
|
||||
<field name="attachments_ids" select="2" />
|
||||
<field name="date_mail" select="2" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_email_template_mailbox_search" model="ir.ui.view">
|
||||
<field name="name">email_template.mailbox.search</field>
|
||||
<field name="model">email_template.mailbox</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mailboxes">
|
||||
<filter icon="terp-crm" string="Drafts" name="draft" domain="[('folder','=','drafts']"/>
|
||||
<filter icon="terp-crm" string="Outbox" name="outbox" domain="[('folder','=','outbox')]"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter icon="terp-crm" string="Sent" domain="[('folder','=','sent')]"/>
|
||||
<filter icon="terp-crm" string="Trash" domain="[('folder','=','trash')]"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter icon="terp-crm" string="Not Applicable" domain="[('state','=','na')]"/>
|
||||
<filter icon="terp-crm" string="Sending" domain="[('state','=','sending')]"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="email_from" select="1"/>
|
||||
<field name="email_to" select="1"/>
|
||||
<field name="subject" select="1"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_email_template_mailbox">
|
||||
<field name="name">Mailbox</field>
|
||||
<field name="res_model">email_template.mailbox</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" ref="email_template_outbox_tree" />
|
||||
<field name="context">{'group_by': [], 'search_default_draft': 1, 'search_default_outbox': 1}</field>
|
||||
<field name="search_view_id" ref="view_email_template_mailbox_search"/>
|
||||
</record>
|
||||
|
||||
<!--======================================== MENUS ========================================-->
|
||||
<menuitem name="MailBox" id="menu_email_template_mailbox_all_main2" parent="menu_email_template" />
|
||||
<menuitem name="Personal" id="menu_email_template_personal" parent="menu_email_template_mailbox_all_main2" />
|
||||
<menuitem name="Mails" id="menu_email_template_personal_mails" parent="menu_email_template_personal" action="action_email_template_mailbox"/>
|
||||
<menuitem name="Company" id="menu_email_template_company" parent="menu_email_template_mailbox_all_main2" />
|
||||
<menuitem name="Mails" id="menu_email_template_company_mails" parent="menu_email_template_company" action="action_email_template_mailbox"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
<record forcecreate="True" id="ir_cron_mail_scheduler_action" model="ir.cron">
|
||||
<field name="name">Email Template scheduler</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">12</field>
|
||||
<field eval="False" name="doall"/>
|
||||
<field eval="'email_template.mailbox'" name="model"/>
|
||||
<field eval="'run_mail_scheduler'" name="function"/>
|
||||
<field eval="'()'" name="args"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,188 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Email Template PReview -->
|
||||
<record model="ir.ui.view" id="email_template_preview_form">
|
||||
<field name="name">email_template.preview.form</field>
|
||||
<field name="model">email_template.preview</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Email Preview">
|
||||
<field name="rel_model" />
|
||||
<newline />
|
||||
<field name="rel_model_ref" on_change="on_change_ref(rel_model_ref, context)" />
|
||||
<newline />
|
||||
<field name="to" />
|
||||
<newline />
|
||||
<field name="cc" />
|
||||
<newline />
|
||||
<field name="bcc" />
|
||||
<newline />
|
||||
<field name="subject" />
|
||||
<newline />
|
||||
<field name="body_text" />
|
||||
<newline />
|
||||
<!-- <field name="body_html" widget="text_html" />-->
|
||||
<!--
|
||||
Removed text_html because it doesnt work in GTK
|
||||
And TinyMCE messes up the HTML in Web Client
|
||||
-->
|
||||
<field name="body_html"/>
|
||||
<newline />
|
||||
<field name="report" />
|
||||
<group>
|
||||
<button icon="gtk-ok" special="cancel" string="OK" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="wizard_email_template_preview" model="ir.actions.act_window">
|
||||
<field name="name">Template Preview</field>
|
||||
<field name="res_model">email_template.preview</field>
|
||||
<field name="src_model">email_template.preview</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="auto_refresh" eval="1" />
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'ids':active_id}</field>
|
||||
</record>
|
||||
<!--EMail client Form view -->
|
||||
|
||||
<record model="ir.ui.view" id="email_template_form">
|
||||
<field name="name">email.template.form</field>
|
||||
<field name="model">email.template</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Email Templates">
|
||||
<field name="name" />
|
||||
<field name="object_name" required="1"
|
||||
on_change="change_model(object_name)" />
|
||||
<field name="model_int_name" invisible="1" />
|
||||
<notebook colspan="4">
|
||||
<page string="Mail Details">
|
||||
<group col="4" colspan="2">
|
||||
<field name="def_to" colspan="4" required="1" />
|
||||
<field name="def_cc" colspan="4" />
|
||||
<field name="def_bcc" colspan="4" />
|
||||
</group>
|
||||
<group col="2" colspan="2">
|
||||
<field name="def_subject" colspan="4" required="1" />
|
||||
<field name="use_sign" colspan="4" />
|
||||
<field name="lang" colspan="4" />
|
||||
</group>
|
||||
<separator colspan="3" string="Standard Body" />
|
||||
<separator colspan="1" string="Expression Builder" />
|
||||
|
||||
<notebook>
|
||||
<page string="Body (Text)">
|
||||
<field name="def_body_text" colspan="4" nolabel="1" />
|
||||
</page>
|
||||
<page string="Body (Raw HTML)">
|
||||
<field name="def_body_html" colspan="4" nolabel="1" />
|
||||
<label string="Note: This is Raw HTML." colspan="4" />
|
||||
</page>
|
||||
</notebook>
|
||||
<group col="4">
|
||||
<field name="template_language"
|
||||
on_change="onchange_null_value(model_object_field,sub_model_object_field,null_value,template_language,context)" />
|
||||
<notebook>
|
||||
<page string="Insert Simple Field">
|
||||
|
||||
<field name="model_object_field"
|
||||
domain="[('model_id','=',object_name),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
||||
on_change="onchange_model_object_field(model_object_field, template_language,context)"
|
||||
colspan="4" />
|
||||
<field name="sub_object" readonly="1" colspan="4" />
|
||||
<field name="sub_model_object_field"
|
||||
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
||||
colspan="4"
|
||||
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
|
||||
on_change="onchange_sub_model_object_field(model_object_field,sub_model_object_field,template_language,context)" />
|
||||
<field name="null_value" colspan="4"
|
||||
on_change="onchange_null_value(model_object_field,sub_model_object_field,null_value,template_language,context)" />
|
||||
<field name="copyvalue" colspan="4" />
|
||||
</page>
|
||||
</notebook>
|
||||
<button name="%(wizard_email_template_preview)d" string="Preview Template"
|
||||
type="action" colspan="4" target="new" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="Security">
|
||||
<separator colspan="4" string="Allowed User Groups" />
|
||||
<field name="allowed_groups" string="Allowed User Groups"
|
||||
nolabel="1" />
|
||||
</page>
|
||||
<page string="Advanced">
|
||||
<separator string="Automatic Email" colspan="4" />
|
||||
<field name="enforce_from_account" attrs="{'required':[('auto_email','=',True)]}" />
|
||||
<newline/>
|
||||
<field name="ref_ir_act_window" />
|
||||
<field name="ref_ir_value" />
|
||||
<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)]" />
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="email_template_tree">
|
||||
<field name="name">email.template.tree</field>
|
||||
<field name="model">email.template</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Email Templates">
|
||||
<field name="name" select="1" />
|
||||
<field name="object_name" required="1" select="1" />
|
||||
<field name="def_to" colspan="4" select="2" />
|
||||
<field name="def_cc" colspan="4" select="2" />
|
||||
<field name="def_bcc" colspan="4" select="2" />
|
||||
<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" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_email_template_search" model="ir.ui.view">
|
||||
<field name="name">email.template.search</field>
|
||||
<field name="model">email.template</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Templates">
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select="1"/>
|
||||
<field name="object_name" select="1"/>
|
||||
<field name="def_to" select="1"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="lang" select="1"/>
|
||||
<field name="def_subject" select="1"/>
|
||||
<field name="file_name" select="1"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_email_template_tree_all">
|
||||
<field name="name">Email Templates</field>
|
||||
<field name="res_model">email.template</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" ref="email_template_tree" />
|
||||
<field name="search_view_id" ref="view_email_template_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="E-MAIL Templates" id="menu_email_template_all"
|
||||
parent="menu_email_template_configuration" action="action_email_template_tree_all" />
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="PE_ADMIN" model="res.roles">
|
||||
<field name="name">Email Administrator</field>
|
||||
</record>
|
||||
|
||||
<record id="wkf_email_template_setting" model="workflow">
|
||||
<field name="name">Email Template Workflow</field>
|
||||
<field name="osv">email_template.account</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<!--Activity -->
|
||||
|
||||
<record id="act_draft" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_email_template_setting"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">draft</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'state':'draft'})</field>
|
||||
</record>
|
||||
|
||||
<record id="act_approved" model="workflow.activity">
|
||||
<field name="name">approval</field>
|
||||
<field name="wkf_id" ref="wkf_email_template_setting"/>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">do_approval()</field>
|
||||
</record>
|
||||
|
||||
<record id="act_suspended" model="workflow.activity">
|
||||
<field name="name">suspended</field>
|
||||
<field name="wkf_id" ref="wkf_email_template_setting"/>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'state':'suspended'})</field>
|
||||
</record>
|
||||
<record id="act_dummy" model="workflow.activity">
|
||||
<field name="name">dummy</field>
|
||||
<field name="wkf_id" ref="wkf_email_template_setting"/>
|
||||
<field name="flow_stop">True</field>
|
||||
</record>
|
||||
|
||||
<!-- Transition -->
|
||||
|
||||
<record id="trans_awaiting_approved" model="workflow.transition">
|
||||
<field name="act_from" ref="act_draft"/>
|
||||
<field name="act_to" ref="act_approved"/>
|
||||
<field name="signal">button_approval</field>
|
||||
<field name="role_id" ref="PE_ADMIN"/>
|
||||
</record>
|
||||
|
||||
<record id="trans_approved_suspended" model="workflow.transition">
|
||||
<field name="act_from" ref="act_approved"/>
|
||||
<field name="act_to" ref="act_suspended"/>
|
||||
<field name="signal">button_suspended</field>
|
||||
<field name="role_id" ref="PE_ADMIN"/>
|
||||
</record>
|
||||
<record id="trans_suspended_reapproved" model="workflow.transition">
|
||||
<field name="act_from" ref="act_suspended"/>
|
||||
<field name="act_to" ref="act_draft"/>
|
||||
<field name="signal">get_reapprove</field>
|
||||
<field name="role_id" ref="PE_ADMIN"/>
|
||||
</record>
|
||||
<record id="trans_suspended_dummy" model="workflow.transition">
|
||||
<field name="act_from" ref="act_suspended"/>
|
||||
<field name="act_to" ref="act_dummy"/>
|
||||
<field name="signal">get_never</field>
|
||||
<field name="role_id" ref="PE_ADMIN"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,455 @@
|
|||
#!/usr/bin/env python
|
||||
"""html2text: Turn HTML into equivalent Markdown-structured text."""
|
||||
__version__ = "2.36"
|
||||
__author__ = "Aaron Swartz (me@aaronsw.com)"
|
||||
__copyright__ = "(C) 2004-2008 Aaron Swartz. GNU GPL 3."
|
||||
__contributors__ = ["Martin 'Joey' Schulze", "Ricardo Reyes", "Kevin Jay North"]
|
||||
|
||||
# TODO:
|
||||
# Support decoded entities with unifiable.
|
||||
|
||||
if not hasattr(__builtins__, 'True'): True, False = 1, 0
|
||||
import re, sys, urllib, htmlentitydefs, codecs, StringIO, types
|
||||
import sgmllib
|
||||
import urlparse
|
||||
sgmllib.charref = re.compile('&#([xX]?[0-9a-fA-F]+)[^0-9a-fA-F]')
|
||||
|
||||
try: from textwrap import wrap
|
||||
except: pass
|
||||
|
||||
# Use Unicode characters instead of their ascii psuedo-replacements
|
||||
UNICODE_SNOB = 0
|
||||
|
||||
# Put the links after each paragraph instead of at the end.
|
||||
LINKS_EACH_PARAGRAPH = 0
|
||||
|
||||
# Wrap long lines at position. 0 for no wrapping. (Requires Python 2.3.)
|
||||
BODY_WIDTH = 78
|
||||
|
||||
# Don't show internal links (href="#local-anchor") -- corresponding link targets
|
||||
# won't be visible in the plain text file anyway.
|
||||
SKIP_INTERNAL_LINKS = False
|
||||
|
||||
### Entity Nonsense ###
|
||||
|
||||
def name2cp(k):
|
||||
if k == 'apos': return ord("'")
|
||||
if hasattr(htmlentitydefs, "name2codepoint"): # requires Python 2.3
|
||||
return htmlentitydefs.name2codepoint[k]
|
||||
else:
|
||||
k = htmlentitydefs.entitydefs[k]
|
||||
if k.startswith("&#") and k.endswith(";"): return int(k[2:-1]) # not in latin-1
|
||||
return ord(codecs.latin_1_decode(k)[0])
|
||||
|
||||
unifiable = {'rsquo':"'", 'lsquo':"'", 'rdquo':'"', 'ldquo':'"',
|
||||
'copy':'(C)', 'mdash':'--', 'nbsp':' ', 'rarr':'->', 'larr':'<-', 'middot':'*',
|
||||
'ndash':'-', 'oelig':'oe', 'aelig':'ae',
|
||||
'agrave':'a', 'aacute':'a', 'acirc':'a', 'atilde':'a', 'auml':'a', 'aring':'a',
|
||||
'egrave':'e', 'eacute':'e', 'ecirc':'e', 'euml':'e',
|
||||
'igrave':'i', 'iacute':'i', 'icirc':'i', 'iuml':'i',
|
||||
'ograve':'o', 'oacute':'o', 'ocirc':'o', 'otilde':'o', 'ouml':'o',
|
||||
'ugrave':'u', 'uacute':'u', 'ucirc':'u', 'uuml':'u'}
|
||||
|
||||
unifiable_n = {}
|
||||
|
||||
for k in unifiable.keys():
|
||||
unifiable_n[name2cp(k)] = unifiable[k]
|
||||
|
||||
def charref(name):
|
||||
if name[0] in ['x','X']:
|
||||
c = int(name[1:], 16)
|
||||
else:
|
||||
c = int(name)
|
||||
|
||||
if not UNICODE_SNOB and c in unifiable_n.keys():
|
||||
return unifiable_n[c]
|
||||
else:
|
||||
return unichr(c)
|
||||
|
||||
def entityref(c):
|
||||
if not UNICODE_SNOB and c in unifiable.keys():
|
||||
return unifiable[c]
|
||||
else:
|
||||
try: name2cp(c)
|
||||
except KeyError: return "&" + c
|
||||
else: return unichr(name2cp(c))
|
||||
|
||||
def replaceEntities(s):
|
||||
s = s.group(1)
|
||||
if s[0] == "#":
|
||||
return charref(s[1:])
|
||||
else: return entityref(s)
|
||||
|
||||
r_unescape = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
|
||||
def unescape(s):
|
||||
return r_unescape.sub(replaceEntities, s)
|
||||
|
||||
def fixattrs(attrs):
|
||||
# Fix bug in sgmllib.py
|
||||
if not attrs: return attrs
|
||||
newattrs = []
|
||||
for attr in attrs:
|
||||
newattrs.append((attr[0], unescape(attr[1])))
|
||||
return newattrs
|
||||
|
||||
### End Entity Nonsense ###
|
||||
|
||||
def onlywhite(line):
|
||||
"""Return true if the line does only consist of whitespace characters."""
|
||||
for c in line:
|
||||
if c is not ' ' and c is not ' ':
|
||||
return c is ' '
|
||||
return line
|
||||
|
||||
def optwrap(text):
|
||||
"""Wrap all paragraphs in the provided text."""
|
||||
if not BODY_WIDTH:
|
||||
return text
|
||||
|
||||
assert wrap, "Requires Python 2.3."
|
||||
result = ''
|
||||
newlines = 0
|
||||
for para in text.split("\n"):
|
||||
if len(para) > 0:
|
||||
if para[0] is not ' ' and para[0] is not '-' and para[0] is not '*':
|
||||
for line in wrap(para, BODY_WIDTH):
|
||||
result += line + "\n"
|
||||
result += "\n"
|
||||
newlines = 2
|
||||
else:
|
||||
if not onlywhite(para):
|
||||
result += para + "\n"
|
||||
newlines = 1
|
||||
else:
|
||||
if newlines < 2:
|
||||
result += "\n"
|
||||
newlines += 1
|
||||
return result
|
||||
|
||||
def hn(tag):
|
||||
if tag[0] == 'h' and len(tag) == 2:
|
||||
try:
|
||||
n = int(tag[1])
|
||||
if n in range(1, 10): return n
|
||||
except ValueError: return 0
|
||||
|
||||
class _html2text(sgmllib.SGMLParser):
|
||||
def __init__(self, out=sys.stdout.write, baseurl=''):
|
||||
sgmllib.SGMLParser.__init__(self)
|
||||
|
||||
if out is None: self.out = self.outtextf
|
||||
else: self.out = out
|
||||
self.outtext = u''
|
||||
self.quiet = 0
|
||||
self.p_p = 0
|
||||
self.outcount = 0
|
||||
self.start = 1
|
||||
self.space = 0
|
||||
self.a = []
|
||||
self.astack = []
|
||||
self.acount = 0
|
||||
self.list = []
|
||||
self.blockquote = 0
|
||||
self.pre = 0
|
||||
self.startpre = 0
|
||||
self.lastWasNL = 0
|
||||
self.abbr_title = None # current abbreviation definition
|
||||
self.abbr_data = None # last inner HTML (for abbr being defined)
|
||||
self.abbr_list = {} # stack of abbreviations to write later
|
||||
self.baseurl = baseurl
|
||||
|
||||
def outtextf(self, s):
|
||||
self.outtext += s
|
||||
|
||||
def close(self):
|
||||
sgmllib.SGMLParser.close(self)
|
||||
|
||||
self.pbr()
|
||||
self.o('', 0, 'end')
|
||||
|
||||
return self.outtext
|
||||
|
||||
def handle_charref(self, c):
|
||||
self.o(charref(c))
|
||||
|
||||
def handle_entityref(self, c):
|
||||
self.o(entityref(c))
|
||||
|
||||
def unknown_starttag(self, tag, attrs):
|
||||
self.handle_tag(tag, attrs, 1)
|
||||
|
||||
def unknown_endtag(self, tag):
|
||||
self.handle_tag(tag, None, 0)
|
||||
|
||||
def previousIndex(self, attrs):
|
||||
""" returns the index of certain set of attributes (of a link) in the
|
||||
self.a list
|
||||
|
||||
If the set of attributes is not found, returns None
|
||||
"""
|
||||
if not attrs.has_key('href'): return None
|
||||
|
||||
i = -1
|
||||
for a in self.a:
|
||||
i += 1
|
||||
match = 0
|
||||
|
||||
if a.has_key('href') and a['href'] == attrs['href']:
|
||||
if a.has_key('title') or attrs.has_key('title'):
|
||||
if (a.has_key('title') and attrs.has_key('title') and
|
||||
a['title'] == attrs['title']):
|
||||
match = True
|
||||
else:
|
||||
match = True
|
||||
|
||||
if match: return i
|
||||
|
||||
def handle_tag(self, tag, attrs, start):
|
||||
attrs = fixattrs(attrs)
|
||||
|
||||
if hn(tag):
|
||||
self.p()
|
||||
if start: self.o(hn(tag)*"#" + ' ')
|
||||
|
||||
if tag in ['p', 'div']: self.p()
|
||||
|
||||
if tag == "br" and start: self.o(" \n")
|
||||
|
||||
if tag == "hr" and start:
|
||||
self.p()
|
||||
self.o("* * *")
|
||||
self.p()
|
||||
|
||||
if tag in ["head", "style", 'script']:
|
||||
if start: self.quiet += 1
|
||||
else: self.quiet -= 1
|
||||
|
||||
if tag in ["body"]:
|
||||
self.quiet = 0 # sites like 9rules.com never close <head>
|
||||
|
||||
if tag == "blockquote":
|
||||
if start:
|
||||
self.p(); self.o('> ', 0, 1); self.start = 1
|
||||
self.blockquote += 1
|
||||
else:
|
||||
self.blockquote -= 1
|
||||
self.p()
|
||||
|
||||
if tag in ['em', 'i', 'u']: self.o("_")
|
||||
if tag in ['strong', 'b']: self.o("**")
|
||||
if tag == "code" and not self.pre: self.o('`') #TODO: `` `this` ``
|
||||
if tag == "abbr":
|
||||
if start:
|
||||
attrsD = {}
|
||||
for (x, y) in attrs: attrsD[x] = y
|
||||
attrs = attrsD
|
||||
|
||||
self.abbr_title = None
|
||||
self.abbr_data = ''
|
||||
if attrs.has_key('title'):
|
||||
self.abbr_title = attrs['title']
|
||||
else:
|
||||
if self.abbr_title != None:
|
||||
self.abbr_list[self.abbr_data] = self.abbr_title
|
||||
self.abbr_title = None
|
||||
self.abbr_data = ''
|
||||
|
||||
if tag == "a":
|
||||
if start:
|
||||
attrsD = {}
|
||||
for (x, y) in attrs: attrsD[x] = y
|
||||
attrs = attrsD
|
||||
if attrs.has_key('href') and not (SKIP_INTERNAL_LINKS and attrs['href'].startswith('#')):
|
||||
self.astack.append(attrs)
|
||||
self.o("[")
|
||||
else:
|
||||
self.astack.append(None)
|
||||
else:
|
||||
if self.astack:
|
||||
a = self.astack.pop()
|
||||
if a:
|
||||
i = self.previousIndex(a)
|
||||
if i is not None:
|
||||
a = self.a[i]
|
||||
else:
|
||||
self.acount += 1
|
||||
a['count'] = self.acount
|
||||
a['outcount'] = self.outcount
|
||||
self.a.append(a)
|
||||
self.o("][" + `a['count']` + "]")
|
||||
|
||||
if tag == "img" and start:
|
||||
attrsD = {}
|
||||
for (x, y) in attrs: attrsD[x] = y
|
||||
attrs = attrsD
|
||||
if attrs.has_key('src'):
|
||||
attrs['href'] = attrs['src']
|
||||
alt = attrs.get('alt', '')
|
||||
i = self.previousIndex(attrs)
|
||||
if i is not None:
|
||||
attrs = self.a[i]
|
||||
else:
|
||||
self.acount += 1
|
||||
attrs['count'] = self.acount
|
||||
attrs['outcount'] = self.outcount
|
||||
self.a.append(attrs)
|
||||
self.o("![")
|
||||
self.o(alt)
|
||||
self.o("]["+`attrs['count']`+"]")
|
||||
|
||||
if tag == 'dl' and start: self.p()
|
||||
if tag == 'dt' and not start: self.pbr()
|
||||
if tag == 'dd' and start: self.o(' ')
|
||||
if tag == 'dd' and not start: self.pbr()
|
||||
|
||||
if tag in ["ol", "ul"]:
|
||||
if start:
|
||||
self.list.append({'name':tag, 'num':0})
|
||||
else:
|
||||
if self.list: self.list.pop()
|
||||
|
||||
self.p()
|
||||
|
||||
if tag == 'li':
|
||||
if start:
|
||||
self.pbr()
|
||||
if self.list: li = self.list[-1]
|
||||
else: li = {'name':'ul', 'num':0}
|
||||
self.o(" "*len(self.list)) #TODO: line up <ol><li>s > 9 correctly.
|
||||
if li['name'] == "ul": self.o("* ")
|
||||
elif li['name'] == "ol":
|
||||
li['num'] += 1
|
||||
self.o(`li['num']`+". ")
|
||||
self.start = 1
|
||||
else:
|
||||
self.pbr()
|
||||
|
||||
if tag in ["table", "tr"] and start: self.p()
|
||||
if tag == 'td': self.pbr()
|
||||
|
||||
if tag == "pre":
|
||||
if start:
|
||||
self.startpre = 1
|
||||
self.pre = 1
|
||||
else:
|
||||
self.pre = 0
|
||||
self.p()
|
||||
|
||||
def pbr(self):
|
||||
if self.p_p == 0: self.p_p = 1
|
||||
|
||||
def p(self): self.p_p = 2
|
||||
|
||||
def o(self, data, puredata=0, force=0):
|
||||
if self.abbr_data is not None: self.abbr_data += data
|
||||
|
||||
if not self.quiet:
|
||||
if puredata and not self.pre:
|
||||
data = re.sub('\s+', ' ', data)
|
||||
if data and data[0] == ' ':
|
||||
self.space = 1
|
||||
data = data[1:]
|
||||
if not data and not force: return
|
||||
|
||||
if self.startpre:
|
||||
#self.out(" :") #TODO: not output when already one there
|
||||
self.startpre = 0
|
||||
|
||||
bq = (">" * self.blockquote)
|
||||
if not (force and data and data[0] == ">") and self.blockquote: bq += " "
|
||||
|
||||
if self.pre:
|
||||
bq += " "
|
||||
data = data.replace("\n", "\n"+bq)
|
||||
|
||||
if self.start:
|
||||
self.space = 0
|
||||
self.p_p = 0
|
||||
self.start = 0
|
||||
|
||||
if force == 'end':
|
||||
# It's the end.
|
||||
self.p_p = 0
|
||||
self.out("\n")
|
||||
self.space = 0
|
||||
|
||||
|
||||
if self.p_p:
|
||||
self.out(('\n'+bq)*self.p_p)
|
||||
self.space = 0
|
||||
|
||||
if self.space:
|
||||
if not self.lastWasNL: self.out(' ')
|
||||
self.space = 0
|
||||
|
||||
if self.a and ((self.p_p == 2 and LINKS_EACH_PARAGRAPH) or force == "end"):
|
||||
if force == "end": self.out("\n")
|
||||
|
||||
newa = []
|
||||
for link in self.a:
|
||||
if self.outcount > link['outcount']:
|
||||
self.out(" ["+`link['count']`+"]: " + urlparse.urljoin(self.baseurl, link['href']))
|
||||
if link.has_key('title'): self.out(" ("+link['title']+")")
|
||||
self.out("\n")
|
||||
else:
|
||||
newa.append(link)
|
||||
|
||||
if self.a != newa: self.out("\n") # Don't need an extra line when nothing was done.
|
||||
|
||||
self.a = newa
|
||||
|
||||
if self.abbr_list and force == "end":
|
||||
for abbr, definition in self.abbr_list.items():
|
||||
self.out(" *[" + abbr + "]: " + definition + "\n")
|
||||
|
||||
self.p_p = 0
|
||||
self.out(data)
|
||||
self.lastWasNL = data and data[-1] == '\n'
|
||||
self.outcount += 1
|
||||
|
||||
def handle_data(self, data):
|
||||
if r'\/script>' in data: self.quiet -= 1
|
||||
self.o(data, 1)
|
||||
|
||||
def unknown_decl(self, data): pass
|
||||
|
||||
def wrapwrite(text): sys.stdout.write(text.encode('utf8'))
|
||||
|
||||
def html2text_file(html, out=wrapwrite, baseurl=''):
|
||||
h = _html2text(out, baseurl)
|
||||
h.feed(html)
|
||||
h.feed("")
|
||||
return h.close()
|
||||
|
||||
def html2text(html, baseurl=''):
|
||||
return optwrap(html2text_file(html, None, baseurl))
|
||||
|
||||
if __name__ == "__main__":
|
||||
baseurl = ''
|
||||
if sys.argv[1:]:
|
||||
arg = sys.argv[1]
|
||||
if arg.startswith('http://'):
|
||||
baseurl = arg
|
||||
j = urllib.urlopen(baseurl)
|
||||
try:
|
||||
from feedparser import _getCharacterEncoding as enc
|
||||
except ImportError:
|
||||
enc = lambda x, y: ('utf-8', 1)
|
||||
text = j.read()
|
||||
encoding = enc(j.headers, text)[0]
|
||||
if encoding == 'us-ascii': encoding = 'utf-8'
|
||||
data = text.decode(encoding)
|
||||
|
||||
else:
|
||||
encoding = 'utf8'
|
||||
if len(sys.argv) > 2:
|
||||
encoding = sys.argv[2]
|
||||
f = open(arg, 'r')
|
||||
try:
|
||||
data = f.read().decode(encoding)
|
||||
finally:
|
||||
f.close()
|
||||
else:
|
||||
data = sys.stdin.read().decode('utf8')
|
||||
wrapwrite(html2text(data, baseurl))
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="res_groups_email_template_manager" model="res.groups">
|
||||
<field name="name">Email Template / Settings_Manager</field>
|
||||
</record>
|
||||
|
||||
<record id="res_groups_email_template_userse" model="res.groups">
|
||||
<field name="name">Email Template / External_users</field>
|
||||
</record>
|
||||
|
||||
<record id="res_groups_email_template_usersi" model="res.groups">
|
||||
<field name="name">Email Template / Internal_users</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1 @@
|
|||
import email_template_send_wizard
|
|
@ -0,0 +1,235 @@
|
|||
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
|
||||
|
||||
class email_template_send_wizard(osv.osv_memory):
|
||||
_name = 'email_template.send.wizard'
|
||||
_description = 'This is the wizard for sending mail'
|
||||
_rec_name = "subject"
|
||||
|
||||
def _get_accounts(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
template = self._get_template(cr, uid, context)
|
||||
if not template:
|
||||
return []
|
||||
|
||||
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))]
|
||||
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."))
|
||||
|
||||
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)"""
|
||||
if not message:
|
||||
return ''
|
||||
if not id:
|
||||
id = context['src_rec_ids'][0]
|
||||
return get_value(cursor, user, id, message, template, context)
|
||||
|
||||
def _get_template(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not 'template' in context and not 'template_id' in context:
|
||||
return None
|
||||
if 'template_id' in context.keys():
|
||||
template_ids = self.pool.get('email.template').search(cr, uid, [('id','=',context['template_id'])], context=context)
|
||||
elif 'template' in context.keys():
|
||||
# Old versions of email_template used the name of the template. This caused
|
||||
# problems when the user changed the name of the template, but we keep the code
|
||||
# for compatibility with those versions.
|
||||
template_ids = self.pool.get('email.template').search(cr, uid, [('name','=',context['template'])], context=context)
|
||||
if not template_ids:
|
||||
return None
|
||||
|
||||
template = self.pool.get('email.template').browse(cr, uid, template_ids[0], context)
|
||||
|
||||
lang = self.get_value(cr, uid, template, template.lang, context)
|
||||
if lang:
|
||||
# Use translated template if necessary
|
||||
ctx = context.copy()
|
||||
ctx['lang'] = lang
|
||||
template = self.pool.get('email.template').browse(cr, uid, template.id, ctx)
|
||||
return template
|
||||
|
||||
def _get_template_value(self, cr, uid, field, context=None):
|
||||
template = self._get_template(cr, uid, context)
|
||||
if not template:
|
||||
return False
|
||||
if len(context['src_rec_ids']) > 1: # Multiple Mail: Gets original template values for multiple email change
|
||||
return getattr(template, field)
|
||||
else: # Simple Mail: Gets computed template values
|
||||
return self.get_value(cr, uid, template, getattr(template, field), context)
|
||||
|
||||
_columns = {
|
||||
'state':fields.selection([
|
||||
('single','Simple Mail Wizard Step 1'),
|
||||
('multi','Multiple Mail Wizard Step 1'),
|
||||
('done','Wizard Complete')
|
||||
],'Status',readonly=True),
|
||||
'ref_template':fields.many2one('email.template','Template',readonly=True),
|
||||
'rel_model':fields.many2one('ir.model','Model',readonly=True),
|
||||
'rel_model_ref':fields.integer('Referred Document',readonly=True),
|
||||
'from':fields.selection(_get_accounts,'From Account',select=True),
|
||||
'to':fields.char('To',size=250,required=True),
|
||||
'cc':fields.char('CC',size=250,),
|
||||
'bcc':fields.char('BCC',size=250,),
|
||||
'subject':fields.char('Subject',size=200),
|
||||
'body_text':fields.text('Body',),
|
||||
'body_html':fields.text('Body',),
|
||||
'report':fields.char('Report File Name',size=100,),
|
||||
'signature':fields.boolean('Attach my signature to mail'),
|
||||
#'filename':fields.text('File Name'),
|
||||
'requested':fields.integer('No of requested Mails',readonly=True),
|
||||
'generated':fields.integer('No of generated Mails',readonly=True),
|
||||
'full_success':fields.boolean('Complete Success',readonly=True),
|
||||
'attachment_ids': fields.many2many('ir.attachment','send_wizard_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
|
||||
}
|
||||
|
||||
_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],
|
||||
'rel_model_ref': lambda self,cr,uid,ctx: ctx['active_id'],
|
||||
'to': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'def_to', ctx),
|
||||
'cc': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'def_cc', ctx),
|
||||
'bcc': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'def_bcc', ctx),
|
||||
'subject':lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'def_subject', ctx),
|
||||
'body_text':lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'def_body_text', ctx),
|
||||
'body_html':lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'def_body_html', ctx),
|
||||
'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,
|
||||
'requested':lambda self,cr,uid,ctx: len(ctx['src_rec_ids']),
|
||||
'full_success': lambda *a: False
|
||||
}
|
||||
|
||||
def fields_get(self, cr, uid, fields=None, context=None, read_access=True):
|
||||
result = super(email_template_send_wizard, self).fields_get(cr, uid, fields, context, read_access)
|
||||
if 'attachment_ids' in result and 'src_model' in context:
|
||||
result['attachment_ids']['domain'] = [('res_model','=',context['src_model']),('res_id','=',context['active_id'])]
|
||||
return result
|
||||
|
||||
def sav_to_drafts(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
mailid = self.save_to_mailbox(cr, uid, ids, context)
|
||||
if self.pool.get('email_template.mailbox').write(cr, uid, mailid, {'folder':'drafts'}, context):
|
||||
return {'type':'ir.actions.act_window_close' }
|
||||
|
||||
def send_mail(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
mailid = self.save_to_mailbox(cr, uid, ids, context)
|
||||
if self.pool.get('email_template.mailbox').write(cr, uid, mailid, {'folder':'outbox'}, context):
|
||||
return {'type':'ir.actions.act_window_close' }
|
||||
|
||||
def get_generated(self, cr, uid, ids=None, context=None):
|
||||
if ids is None:
|
||||
ids = []
|
||||
if context is None:
|
||||
context = {}
|
||||
logger = netsvc.Logger()
|
||||
if context['src_rec_ids'] and len(context['src_rec_ids'])>1:
|
||||
#Means there are multiple items selected for email.
|
||||
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."))
|
||||
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."))
|
||||
return True
|
||||
|
||||
def save_to_mailbox(self, cr, uid, ids, context=None):
|
||||
def get_end_value(id, value):
|
||||
if len(context['src_rec_ids']) > 1: # Multiple Mail: Gets value from the template
|
||||
return self.get_value(cr, uid, template, value, context, id)
|
||||
else:
|
||||
return value
|
||||
|
||||
mail_ids = []
|
||||
template = self._get_template(cr, uid, context)
|
||||
for id in context['src_rec_ids']:
|
||||
print "@22222222222222222222222",ids
|
||||
screen_vals = self.read(cr, uid, ids[0], [],context)
|
||||
account = self.pool.get('email_template.account').read(cr, uid, screen_vals['from'], context=context)
|
||||
vals = {
|
||||
'email_from': tools.ustr(account['name']) + "<" + tools.ustr(account['email_id']) + ">",
|
||||
'email_to': get_end_value(id, screen_vals['to']),
|
||||
'email_cc': get_end_value(id, screen_vals['cc']),
|
||||
'email_bcc': get_end_value(id, screen_vals['bcc']),
|
||||
'subject': get_end_value(id, screen_vals['subject']),
|
||||
'body_text': get_end_value(id, screen_vals['body_text']),
|
||||
'body_html': get_end_value(id, screen_vals['body_html']),
|
||||
'account_id': screen_vals['from'],
|
||||
'state':'na',
|
||||
'mail_type':'multipart/alternative' #Options:'multipart/mixed','multipart/alternative','text/plain','text/html'
|
||||
}
|
||||
if screen_vals['signature']:
|
||||
signature = self.pool.get('res.users').read(cr, uid, uid, ['signature'], context)['signature']
|
||||
if signature:
|
||||
vals['body_text'] = tools.ustr(vals['body_text'] or '') + signature
|
||||
vals['body_html'] = tools.ustr(vals['body_html'] or '') + signature
|
||||
|
||||
attachment_ids = []
|
||||
|
||||
#Create partly the mail and later update attachments
|
||||
mail_id = self.pool.get('email_template.mailbox').create(cr, uid, vals, context)
|
||||
mail_ids.append(mail_id)
|
||||
if template.report_template:
|
||||
reportname = 'report.' + self.pool.get('ir.actions.report.xml').read(cr, uid, template.report_template.id, ['report_name'], context)['report_name']
|
||||
data = {}
|
||||
data['model'] = self.pool.get('ir.model').browse(cr, uid, screen_vals['rel_model'], context).model
|
||||
|
||||
# Ensure report is rendered using template's language
|
||||
ctx = context.copy()
|
||||
if template.lang:
|
||||
ctx['lang'] = self.get_value(cr, uid, template, template.lang, context, id)
|
||||
service = netsvc.LocalService(reportname)
|
||||
(result, format) = service.create(cr, uid, [id], data, ctx)
|
||||
attachment_id = self.pool.get('ir.attachment').create(cr, uid, {
|
||||
'name': _('%s (Email Attachment)') % tools.ustr(vals['subject']),
|
||||
'datas': base64.b64encode(result),
|
||||
'datas_fname': tools.ustr(get_end_value(id, screen_vals['report']) or _('Report')) + "." + format,
|
||||
'description': vals['body_text'] or _("No Description"),
|
||||
'res_model': 'email_template.mailbox',
|
||||
'res_id': mail_id
|
||||
}, context)
|
||||
attachment_ids.append( attachment_id )
|
||||
|
||||
# Add document attachments
|
||||
for attachment_id in screen_vals.get('attachment_ids',[]):
|
||||
new_id = self.pool.get('ir.attachment').copy(cr, uid, attachment_id, {
|
||||
'res_model': 'email_template.mailbox',
|
||||
'res_id': mail_id,
|
||||
}, context)
|
||||
attachment_ids.append( new_id )
|
||||
|
||||
if attachment_ids:
|
||||
self.pool.get('email_template.mailbox').write(cr, uid, mail_id, {
|
||||
'attachments_ids': [[6, 0, attachment_ids]],
|
||||
'mail_type': 'multipart/mixed'
|
||||
}, context)
|
||||
|
||||
return mail_ids
|
||||
email_template_send_wizard()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="email_template_send_wizard_form">
|
||||
<field name="name">email_template.send.wizard.form</field>
|
||||
<field name="model">email_template.send.wizard</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Send mail Wizard">
|
||||
<group col="4" colspan="4">
|
||||
<field name="rel_model" colspan="2" />
|
||||
<field name="from" required="1" colspan="2" />
|
||||
</group>
|
||||
<group col="4" colspan="4">
|
||||
<group col="6" colspan="4">
|
||||
<field name="to" select="1" colspan="4" />
|
||||
<newline />
|
||||
<field name="cc" select="2" colspan="4" />
|
||||
<newline />
|
||||
<field name="bcc" select="2" colspan="4" />
|
||||
<newline />
|
||||
<field name="subject" select="2" colspan="4" attrs="{'required':[('state','=','single')]}" />
|
||||
<newline />
|
||||
<field name="report" colspan="4" />
|
||||
</group>
|
||||
<separator string="" colspan="4" />
|
||||
<notebook colspan="4">
|
||||
<page string="Body (Plain Text)">
|
||||
<field name="body_text" select="2" colspan="4" nolabel="1" />
|
||||
</page>
|
||||
<page string="Body (HTML)">
|
||||
<field name="body_html" select="2" colspan="4" nolabel="1" />
|
||||
<!--<label string="Note: HTML body can't be edited with GTK desktop client." colspan="4"/>
|
||||
--></page>
|
||||
<page string="Attachments">
|
||||
<label string="Add here all attachments of the current document you want to include in the e-mail." colspan="4"/>
|
||||
<field name="attachment_ids" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<field name="signature" colspan="4" />
|
||||
</group>
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('state','!=','single')]}">
|
||||
<button icon="gtk-apply" name="sav_to_drafts" string="Save in Drafts" type="object" />
|
||||
<button icon="gtk-ok" name="send_mail" string="Send now" type="object" />
|
||||
<button icon="gtk-cancel" special="cancel" string="Discard Mail" />
|
||||
</group>
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('state','=','single')]}">
|
||||
<label string="Tip: Multiple emails are sent in the same language (the first one is proposed). We suggest you send emails in groups according to language." colspan="4"/>
|
||||
<field name="requested" />
|
||||
<field name="generated" />
|
||||
<button icon="gtk-ok" name="get_generated" string="Send all mails" type="object" states="multi" colspan="2" />
|
||||
<button icon="gtk-cancel" special="cancel" string="Discard Mail" colspan="2" states="multi" />
|
||||
</group>
|
||||
<button icon="gtk-ok" special="cancel" string="Close" colspan="4" states="done" />
|
||||
<field name="state" />
|
||||
<newline />
|
||||
<label string="After clicking send all mails, mails will be sent to outbox and cleared in next Send/Recieve" colspan="4"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
Loading…
Reference in New Issue