diff --git a/addons/email_template/__init__.py b/addons/email_template/__init__.py
new file mode 100755
index 00000000000..c959f7204da
--- /dev/null
+++ b/addons/email_template/__init__.py
@@ -0,0 +1,5 @@
+import email_template_account
+import email_template
+import email_template_mailbox
+import wizard
+
diff --git a/addons/email_template/__openerp__.py b/addons/email_template/__openerp__.py
new file mode 100755
index 00000000000..9c5f7bb5c70
--- /dev/null
+++ b/addons/email_template/__openerp__.py
@@ -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:
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
new file mode 100755
index 00000000000..6c46da458c6
--- /dev/null
+++ b/addons/email_template/email_template.py
@@ -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:
diff --git a/addons/email_template/email_template_account.py b/addons/email_template/email_template_account.py
new file mode 100755
index 00000000000..2503d19cd10
--- /dev/null
+++ b/addons/email_template/email_template_account.py
@@ -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'
No Mail Message
').replace('\n', '
').replace('\r', '
')
+ 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?= ,
+ =?Windows-1252?Q?David_G=F3mez?=
+ Sometimes they include extra " character at the beginning/
+ end of the contact name, like:
+ "=?iso-8859-1?Q?Enric_Mart=ED?="
+ 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:
diff --git a/addons/email_template/email_template_account_view.xml b/addons/email_template/email_template_account_view.xml
new file mode 100755
index 00000000000..5f11fa033f4
--- /dev/null
+++ b/addons/email_template/email_template_account_view.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+ email_template.account.form
+ email_template.account
+ form
+
+
+
+
+
+
+ email_template.account.tree
+ email_template.account
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ email_template.account.search
+ email_template.account
+ search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Accounts
+ email_template.account
+ form
+ form,tree
+
+ {'group_by': [], 'search_default_draft': 1, 'search_default_my': 1}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/email_template/email_template_engines.py b/addons/email_template/email_template_engines.py
new file mode 100755
index 00000000000..2fc58310e9b
--- /dev/null
+++ b/addons/email_template/email_template_engines.py
@@ -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] == "":
+ 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()
diff --git a/addons/email_template/email_template_mailbox.py b/addons/email_template/email_template_mailbox.py
new file mode 100755
index 00000000000..1934f4e0227
--- /dev/null
+++ b/addons/email_template/email_template_mailbox.py
@@ -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:
diff --git a/addons/email_template/email_template_mailbox_view.xml b/addons/email_template/email_template_mailbox_view.xml
new file mode 100755
index 00000000000..e6ff86d1087
--- /dev/null
+++ b/addons/email_template/email_template_mailbox_view.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+ email_template.mailbox.form
+ email_template.mailbox
+ form
+
+
+
+
+
+
+
+
+ email_template.mailbox.draftstree
+ email_template.mailbox
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+ email_template.mailbox.outboxtree
+ email_template.mailbox
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+ email_template.mailbox.sentboxtree
+ email_template.mailbox
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+ email_template.mailbox.trashboxtree
+ email_template.mailbox
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+ email_template.mailbox.search
+ email_template.mailbox
+ search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mailbox
+ email_template.mailbox
+ form
+ form,tree
+
+ {'group_by': [], 'search_default_draft': 1, 'search_default_outbox': 1}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/email_template/email_template_scheduler_data.xml b/addons/email_template/email_template_scheduler_data.xml
new file mode 100755
index 00000000000..30d69f99398
--- /dev/null
+++ b/addons/email_template/email_template_scheduler_data.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Email Template scheduler
+
+ 1
+ hours
+ 12
+
+
+
+
+
+
+
diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml
new file mode 100755
index 00000000000..8e14a861edc
--- /dev/null
+++ b/addons/email_template/email_template_view.xml
@@ -0,0 +1,188 @@
+
+
+
+
+
+ email_template.preview.form
+ email_template.preview
+ form
+
+
+
+
+
+
+ Template Preview
+ email_template.preview
+ email_template.preview
+ ir.actions.act_window
+ form
+ form
+
+ new
+ {'ids':active_id}
+
+
+
+
+ email.template.form
+ email.template
+ form
+
+
+
+
+
+
+ email.template.tree
+ email.template
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ email.template.search
+ email.template
+ search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Email Templates
+ email.template
+ form
+ form,tree
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/email_template/email_template_workflow.xml b/addons/email_template/email_template_workflow.xml
new file mode 100755
index 00000000000..4d3176f0ae0
--- /dev/null
+++ b/addons/email_template/email_template_workflow.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+ Email Administrator
+
+
+
+ Email Template Workflow
+ email_template.account
+ True
+
+
+
+
+
+
+ True
+ draft
+ function
+ write({'state':'draft'})
+
+
+
+ approval
+
+ function
+ do_approval()
+
+
+
+ suspended
+
+ function
+ write({'state':'suspended'})
+
+
+ dummy
+
+ True
+
+
+
+
+
+
+
+ button_approval
+
+
+
+
+
+
+ button_suspended
+
+
+
+
+
+ get_reapprove
+
+
+
+
+
+ get_never
+
+
+
+
diff --git a/addons/email_template/html2text.py b/addons/email_template/html2text.py
new file mode 100755
index 00000000000..f8be00adf0f
--- /dev/null
+++ b/addons/email_template/html2text.py
@@ -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
+
+ 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 - 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))
+
diff --git a/addons/email_template/security/email_template_security.xml b/addons/email_template/security/email_template_security.xml
new file mode 100755
index 00000000000..90067252eca
--- /dev/null
+++ b/addons/email_template/security/email_template_security.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Email Template / Settings_Manager
+
+
+
+ Email Template / External_users
+
+
+
+ Email Template / Internal_users
+
+
+
+
+
diff --git a/addons/email_template/wizard/__init__.py b/addons/email_template/wizard/__init__.py
new file mode 100755
index 00000000000..ea6fcfbcd86
--- /dev/null
+++ b/addons/email_template/wizard/__init__.py
@@ -0,0 +1 @@
+import email_template_send_wizard
diff --git a/addons/email_template/wizard/email_template_send_wizard.py b/addons/email_template/wizard/email_template_send_wizard.py
new file mode 100755
index 00000000000..7d27eab9adf
--- /dev/null
+++ b/addons/email_template/wizard/email_template_send_wizard.py
@@ -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:
diff --git a/addons/email_template/wizard/email_template_send_wizard_view.xml b/addons/email_template/wizard/email_template_send_wizard_view.xml
new file mode 100755
index 00000000000..5f2f1641e31
--- /dev/null
+++ b/addons/email_template/wizard/email_template_send_wizard_view.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+ email_template.send.wizard.form
+ email_template.send.wizard
+ form
+
+
+
+
+
+
+