+
+ name="%(mail.action_email_compose_message_wizard)d"
+ icon="terp-mail-message-new" type="action"/>
@@ -552,58 +553,56 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py
index 4e329ddc6d4..c3381601cbf 100644
--- a/addons/crm/crm_meeting.py
+++ b/addons/crm/crm_meeting.py
@@ -45,12 +45,12 @@ class crm_meeting(crm_base, osv.osv):
_inherit = "calendar.event"
_columns = {
# From crm.case
- 'name': fields.char('Summary', size=124, required=True, states={'done': [('readonly', True)]}),
- 'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)]}),
+ 'name': fields.char('Summary', size=124, required=True, states={'done': [('readonly', True)]}),
+ 'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)]}),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
- domain="[('partner_id','=',partner_id)]", states={'done': [('readonly', True)]}),
+ domain="[('partner_id','=',partner_id)]", states={'done': [('readonly', True)]}),
'section_id': fields.many2one('crm.case.section', 'Sales Team', states={'done': [('readonly', True)]}, \
- select=True, help='Sales team to which Case belongs to.'),
+ select=True, help='Sales team to which Case belongs to.'),
'email_from': fields.char('Email', size=128, states={'done': [('readonly', True)]}, help="These people will receive email."),
'id': fields.integer('ID'),
'create_date': fields.datetime('Creation Date' , readonly=True),
@@ -67,7 +67,7 @@ class crm_meeting(crm_base, osv.osv):
'event_id', 'attendee_id', 'Attendees', states={'done': [('readonly', True)]}),
'date_closed': fields.datetime('Closed', readonly=True),
'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
'state': fields.selection([('open', 'Confirmed'),
('draft', 'Unconfirmed'),
('cancel', 'Cancelled'),
@@ -75,7 +75,7 @@ class crm_meeting(crm_base, osv.osv):
size=16, readonly=True),
}
_defaults = {
- 'state': 'draft',
+ 'state': 'draft',
'active': 1,
'user_id': lambda self, cr, uid, ctx: uid,
}
@@ -138,7 +138,7 @@ class res_users(osv.osv):
def create(self, cr, uid, data, context=None):
user_id = super(res_users, self).create(cr, uid, data, context=context)
-
+
# add shortcut unless 'noshortcut' is True in context
if not(context and context.get('noshortcut', False)):
data_obj = self.pool.get('ir.model.data')
@@ -150,7 +150,6 @@ class res_users(osv.osv):
except:
# Tolerate a missing shortcut. See product/product.py for similar code.
logging.getLogger('orm').debug('Skipped meetings shortcut for user "%s"', data.get('name','
-
+
diff --git a/addons/crm/report/crm_lead_report.py b/addons/crm/report/crm_lead_report.py
index bd61e1ee80d..18615bdf344 100644
--- a/addons/crm/report/crm_lead_report.py
+++ b/addons/crm/report/crm_lead_report.py
@@ -121,7 +121,7 @@ class crm_lead_report(osv.osv):
c.planned_revenue,
c.planned_revenue*(c.probability/100) as probable_revenue,
1 as nbr,
- (SELECT count(id) FROM mailgate_message WHERE model='crm.lead' AND res_id=c.id AND history=True) AS email,
+ (SELECT count(id) FROM mail_message WHERE model='crm.lead' AND res_id=c.id AND email_from is not null) AS email,
date_trunc('day',c.create_date) as create_date,
extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as delay_close,
abs(extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24)) as delay_expected,
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 6f7b3066d7a..6b0a3ac15a9 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -29,15 +29,15 @@
"access_res_partner_manager","res.partner.crm.manager","base.model_res_partner","base.group_sale_manager",1,0,0,0
"access_res_partner_address_manager","res.partner.address.crm.user.manager","base.model_res_partner_address","base.group_sale_manager",1,0,0,0
"access_res_partner_category_manager","res.partner.category.crm.manager","base.model_res_partner_category","base.group_sale_manager",1,0,0,0
-"mail_gateway_mailgate_message_manager","mail_gateway.mailgate.message.manager","mail_gateway.model_mailgate_message","base.group_sale_manager",1,0,0,0
-"mail_gateway_mailgate_thread_manager","mail_gateway.mailgate.thread.manager","mail_gateway.model_mailgate_thread","base.group_sale_manager",1,1,1,1
+"mail_mail_message_manager","mail.message.manager","mail.model_mail_message","base.group_sale_manager",1,0,0,0
+"mail_thread_manager","mail.thread.manager","mail.model_mail_thread","base.group_sale_manager",1,1,1,1
"access_calendar_attendee_crm_user","calendar.attendee.crm.user","model_calendar_attendee","base.group_sale_salesman",1,1,1,0
"access_calendar_attendee_crm_manager","calendar.attendee.crm.manager","model_calendar_attendee","base.group_sale_manager",1,1,1,1
"access_res_partner","res.partner.crm.user","base.model_res_partner","base.group_sale_salesman",1,1,1,0
"access_res_partner_address","res.partner.address.crm.user","base.model_res_partner_address","base.group_sale_salesman",1,1,1,0
"access_res_partner_category","res.partner.category.crm.user","base.model_res_partner_category","base.group_sale_salesman",1,1,1,0
-"mail_gateway_mailgate_thread","mail_gateway.mailgate.thread","mail_gateway.model_mailgate_thread","base.group_sale_salesman",1,1,1,1
-"mail_gateway_mailgate_message_user","mail_gateway.mailgate.message.user","mail_gateway.model_mailgate_message","base.group_sale_salesman",1,1,1,1
+"mail_mailgate_thread","mail.thread","mail.model_mail_thread","base.group_sale_salesman",1,1,1,1
+"mail_gateway_mail_message_user","mail.message.user","mail.model_mail_message","base.group_sale_salesman",1,1,1,1
"access_crm_case_categ_manager","crm.case.categ manager","model_crm_case_categ","base.group_sale_manager",1,1,1,1
"access_base_action_rule_manager","base.action.rule manager","model_base_action_rule","base.group_sale_manager",1,1,1,1
"access_crm_lead_report_user","crm.lead.report user","model_crm_lead_report","base.group_sale_salesman",1,1,1,1
diff --git a/addons/crm/wizard/__init__.py b/addons/crm/wizard/__init__.py
index 9730b8d1d55..3ebdceda437 100644
--- a/addons/crm/wizard/__init__.py
+++ b/addons/crm/wizard/__init__.py
@@ -19,7 +19,7 @@
#
##############################################################################
-import crm_send_email
+import mail_compose_message
import crm_add_note
import crm_lead_to_partner
diff --git a/addons/crm/wizard/crm_add_note.py b/addons/crm/wizard/crm_add_note.py
index 3419c89d33a..478f051fe58 100644
--- a/addons/crm/wizard/crm_add_note.py
+++ b/addons/crm/wizard/crm_add_note.py
@@ -1,11 +1,10 @@
from crm import crm
from osv import fields, osv
from tools.translate import _
-import base64
+from mail.mail_message import truncate_text
AVAILABLE_STATES = crm.AVAILABLE_STATES + [('unchanged', 'Unchanged')]
-
class crm_add_note(osv.osv_memory):
"""Adds a new note to the case."""
_name = 'crm.add.note'
@@ -14,8 +13,11 @@ class crm_add_note(osv.osv_memory):
_columns = {
'body': fields.text('Note Body', required=True),
'state': fields.selection(AVAILABLE_STATES, string='Set New State To',
- required=True),
- 'attachment_ids' : fields.one2many('crm.send.mail.attachment', 'wizard_id'),
+ required=True),
+ }
+
+ _defaults = {
+ 'state': 'unchanged'
}
def action_add(self, cr, uid, ids, context=None):
@@ -32,14 +34,8 @@ class crm_add_note(osv.osv_memory):
case_list = case_pool.browse(cr, uid, context['active_ids'],
context=context)
case = case_list[0]
- user_obj = self.pool.get('res.users')
- user_name = user_obj.browse(cr, uid, [uid], context=context)[0].name
- attach = [
- (x.name, base64.decodestring(x.binary)) for x in obj.attachment_ids
- ]
- case_pool.history(cr, uid, [case], self.pool.get('mailgate.message').truncate_data(cr, uid, obj.body, context=context), history=False,
- details=obj.body, email_from=user_name, attach=attach)
-
+ case_pool.message_append(cr, uid, [case], truncate_text(obj.body),
+ body_text=obj.body)
if obj.state == 'unchanged':
pass
elif obj.state == 'done':
@@ -52,11 +48,4 @@ class crm_add_note(osv.osv_memory):
return {'type': 'ir.actions.act_window_close'}
- def default_get(self, cr, uid, fields, context=None):
- """
- This function gets default values
- """
- return {'state': u'unchanged'}
-
-
crm_add_note()
diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py
index 16223f8c28f..8200dbd1675 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity.py
+++ b/addons/crm/wizard/crm_lead_to_opportunity.py
@@ -132,26 +132,25 @@ class crm_lead2opportunity_partner(osv.osv_memory):
vals['partner_address_id'] = False
lead.write(vals, context=context)
- leads.history(cr, uid, [lead], _('Converted to opportunity'), details='Converted to Opportunity', context=context)
+ text = _('Converted to opportunity')
+ leads.message_append(cr, uid, [lead], text, body_text=text, context=context)
if lead.partner_id:
msg_ids = [ x.id for x in lead.message_ids]
- self.pool.get('mailgate.message').write(cr, uid, msg_ids, {
+ self.pool.get('mail.message').write(cr, uid, msg_ids, {
'partner_id': lead.partner_id.id
}, context=context)
leads.log(cr, uid, lead.id, _("Lead '%s' has been converted to an opportunity.") % lead.name)
- def send_mail_to_salesman(self, lead):
+ def send_mail_to_salesman(self, cr, uid, lead):
email_to = lead.user_id and lead.user_id.user_email
if not email_to:
- return
+ return False
+ message_pool = self.pool.get('mail.message')
email_from = lead.section_id and lead.section_id.user_id and lead.section_id.user_id.user_email or email_to
- partner = lead.partner_id and lead.partner_id.name or lead.partner_name
+ partner = lead.partner_id and lead.partner_id.name or lead.partner_name
subject = "lead %s converted into opportunity" % lead.name
- body = "Info \n Id : %s \n Subject: %s \n Partner: %s \n Description : %s " % (lead.id, lead.name, lead.partner_id.name, lead.description)
- try :
- tools.email_send(email_from, [email_to], subject, body)
- except:
- pass
+ body = "Info \n Id : %s \n Subject: %s \n Partner: %s \n Description : %s " % (lead.id, lead.name, lead.partner_id.name, lead.description)
+ return message_pool.schedule_with_attach(cr, uid, email_from, [email_to], subject, body)
def action_apply(self, cr, uid, ids, context=None):
"""
@@ -205,7 +204,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
partner_id = False
self._convert(cr, uid, ids, lead, partner_id, stage_ids, context=context)
- self.send_mail_to_salesman(lead)
+ self.send_mail_to_salesman(cr, uid, lead)
#If we convert in mass, don't merge if there is no other opportunity but no warning
if data.name == 'merge' and (len(data.opportunity_ids) > 1 or not context.get('mass_convert') ):
merge_obj = self.pool.get('crm.merge.opportunity')
diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py
index 57620f8c016..66e8c2adbfe 100644
--- a/addons/crm/wizard/crm_merge_opportunities.py
+++ b/addons/crm/wizard/crm_merge_opportunities.py
@@ -70,9 +70,9 @@ class crm_merge_opportunity(osv.osv_memory):
return False
opps = lead_obj.browse(cr, uid, [op_id[0]], context=context)
return opps[0]
-
+
def _update_data(self, op_ids, oldest_opp):
- data = {
+ data = {
'partner_id': self._get_first_not_null_id('partner_id', op_ids, oldest_opp), # !!
'title': self._get_first_not_null_id('title', op_ids, oldest_opp),
'name' : self._get_first_not_null('name', op_ids, oldest_opp), #not lost
@@ -105,16 +105,15 @@ class crm_merge_opportunity(osv.osv_memory):
'email_from' : self._get_first_not_null('email_from', op_ids, oldest_opp),
'email_cc' : self._get_first_not_null('email_cc', op_ids, oldest_opp),
'partner_name' : self._get_first_not_null('partner_name', op_ids, oldest_opp),
-
- }
- return data
+ }
+ return data
def merge(self, cr, uid, op_ids, context=None):
"""
- @param opp_ids : list of opportunities ids to merge
+ :param opp_ids: list of opportunities ids to merge
"""
opp_obj = self.pool.get('crm.lead')
- message_obj = self.pool.get('mailgate.message')
+ message_obj = self.pool.get('mail.message')
lead_ids = context and context.get('lead_ids', []) or []
@@ -130,17 +129,15 @@ class crm_merge_opportunity(osv.osv_memory):
else:
first_opportunity = opportunities_list[0]
tail_opportunities = opportunities_list[1:]
-
-
data = self._update_data(op_ids, oldest_opp)
#copy message into the first opportunity + merge attachement
-
+
for opp in tail_opportunities + [first_opportunity]:
attach_ids = self.get_attachments(cr, uid, opp, context=context)
self.set_attachements_res_id(cr, uid, first_opportunity.id, attach_ids)
- for history in opp.message_ids:
- message_obj.write(cr, uid, history.id, {'res_id': first_opportunity.id, 'name' : _("From %s : %s") % (opp.name, history.name) }, context=context)
+ for mail_msg in opp.message_ids:
+ message_obj.write(cr, uid, mail_msg.id, {'res_id': first_opportunity.id, 'subject' : _("From %s : %s") % (opp.name, mail_msg.subject) }, context=context)
#Notification about loss of information
details = []
@@ -172,7 +169,7 @@ class crm_merge_opportunity(osv.osv_memory):
subject = subject[0] + ", ".join(subject[1:])
details = "\n\n".join(details)
- opp_obj._history(cr, uid, [first_opportunity], subject, details=details)
+ opp_obj.message_append(cr, uid, [first_opportunity], subject, body_text=details)
#data.update({'message_ids' : [(6, 0 ,self._concat_o2m('message_ids', op_ids))]})
opp_obj.write(cr, uid, [first_opportunity.id], data)
unlink_ids = map(lambda x: x.id, tail_opportunities)
@@ -180,8 +177,6 @@ class crm_merge_opportunity(osv.osv_memory):
models_data = self.pool.get('ir.model.data')
-
-
# Get Opportunity views
opportunity_view_form = models_data._get_id(
cr, uid, 'crm', 'crm_case_form_view_oppor')
@@ -216,7 +211,6 @@ class crm_merge_opportunity(osv.osv_memory):
context['lead_ids'] = [op_ids[0].id]
return self.merge(cr, uid, op_ids, context)
-
_columns = {
'opportunity_ids' : fields.many2many('crm.lead', 'merge_opportunity_rel', 'merge_id', 'opportunity_id', 'Opportunities', domain=[('type', '=', 'opportunity')]),
}
diff --git a/addons/crm/wizard/crm_send_email.py b/addons/crm/wizard/crm_send_email.py
deleted file mode 100644
index d4ba8b476a1..00000000000
--- a/addons/crm/wizard/crm_send_email.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (). All Rights Reserved
-# $Id$
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from crm import crm
-from osv import osv, fields
-from tools.translate import _
-import base64
-import itertools
-import tools
-import re
-
-
-AVAILABLE_STATES = crm.AVAILABLE_STATES + [('unchanged', 'Unchanged')]
-
-
-class crm_send_new_email_attachment(osv.osv_memory):
- _name = 'crm.send.mail.attachment'
-
- _columns = {
- 'binary' : fields.binary('Attachment', required=True),
- 'name' : fields.char('Name', size=128, required=True),
- 'wizard_id' : fields.many2one('crm.send.mail', 'Wizard', required=True),
- }
-
-crm_send_new_email_attachment()
-
-class crm_send_new_email(osv.osv_memory):
- """ Sends new email for the case"""
- _name = "crm.send.mail"
- _description = "Send new email"
-
- _columns = {
- 'email_to' : fields.char('To', size=512, required=True),
- 'email_from' : fields.char('From', size=128, required=True),
- 'reply_to' : fields.char('Reply To', size=128, required=True, help="Reply-to of the Sales team defined on this case"),
- 'email_cc' : fields.char('CC', size=512, help="These addresses will receive a copy of this email. To modify the permanent CC list, edit the global CC field of this case"),
- 'subject': fields.char('Subject', size=512, required=True),
- 'body': fields.text('Message Body', required=True),
- 'state': fields.selection(AVAILABLE_STATES, string='Set New State To', required=True),
- 'attachment_ids' : fields.one2many('crm.send.mail.attachment', 'wizard_id', 'Attachment'),
- 'html': fields.boolean('HTML formatting?', help="Select this if you want to send email with HTML formatting."),
- }
-
- def action_mass_send(self, cr, uid, ids, context=None):
-
- if not context:
- context = {}
-
- context.update({'mail' : 'new'})
- actives_ids = context.get('active_ids')
- model = context.get('active_model')
- case_pool = self.pool.get(model)
- for id in actives_ids:
- context.update({'active_id' : id})
- self.action_send(cr, uid, ids, context=context)
-
- return {'type': 'ir.actions.act_window_close'}
-
- def action_send(self, cr, uid, ids, context=None):
- """ This sends an email to ALL the addresses of the selected partners.
- """
- hist_obj = self.pool.get('mailgate.message')
-
- if context is None:
- context = {}
-
- if not context.get('active_model'):
- raise osv.except_osv(_('Error'), _('Can not send mail!'))
-
- model = context.get('active_model')
- case_pool = self.pool.get(model)
- res_id = context and context.get('active_id', False) or False
-
-
-
- for obj in self.browse(cr, uid, ids, context=context):
- attach = [
- (x.name, base64.decodestring(x.binary)) for x in obj.attachment_ids
- ]
-
- subtype = 'plain'
- message_id = None
- ref_id = None
-
- case = case_pool.browse(cr, uid, res_id, context=context)
- if context.get('mail', 'new') == 'new':
- if case.message_ids:
- message_id = case.message_ids[0].message_id
- elif context.get('mail') == 'forward':
- # extract attachements from case and emails according to mode
- attachments = []
- attach_pool = self.pool.get('ir.attachment')
- direct_attachments = attach_pool.search(cr, uid, [('res_model', '=', 'crm.lead'), ('res_id', '=', res_id)], context=context)
- attachments += attach_pool.browse(cr, uid, direct_attachments, context=context)
- if obj.history in ['latest', 'whole'] and case.message_ids:
- msgs = case.message_ids
- if obj.history == 'latest':
- msgs = msgs[:1]
- attachments.extend(itertools.chain(*[m.attachment_ids for m in msgs]))
- attach_all = [(a.datas_fname or a.name, base64.decodestring(a.datas)) for a in attachments if a.datas]
- attach += attach_all
-
- else:
- hist = hist_obj.browse(cr, uid, res_id, context=context)
- message_id = hist.message_id
- model = hist.model
- case_pool = self.pool.get(model)
- res_id = hist.res_id
- ref_id = hist.ref_id
- case = case_pool.browse(cr, uid, res_id, context=context)
-
- if context.get('mass_mail'):
- email_temp = case.email_from and tools.ustr(case.email_from) or ''
- emails = re.findall(r'([^ ,<@]+@[^> ,]+)', email_temp)
- else:
- emails = re.findall(r'([^ ,<@]+@[^> ,]+)', obj.email_to or '')
-
- email_cc = re.findall(r'([^ ,<@]+@[^> ,]+)', obj.email_cc or '')
-
- emails = filter(None, emails)
- body = obj.body
-
- body = body and tools.ustr(body) or ''
- email_from = getattr(obj, 'email_from', False)
- x_headers = {}
- if message_id:
- x_headers['References'] = "%s" % (message_id)
-
- if obj.html:
- subtype = 'html'
-
- flag = tools.email_send(
- email_from,
- emails,
- obj.subject,
- body,
- email_cc=email_cc,
- attach=attach,
- subtype=subtype,
- reply_to=obj.reply_to,
- openobject_id=str(case.id),
- x_headers=x_headers
- )
-
- if not flag:
- raise osv.except_osv(_('Error!'), _('Unable to send mail. Please check SMTP is configured properly.'))
-
- msg_dict = {'new': 'Send', 'reply': 'Reply', 'forward': 'Forward'}
- case_pool.history(cr, uid, [case], _(msg_dict[context.get('mail', 'new')]), history=True, \
- email=obj.email_to, details=body, \
- subject=obj.subject, email_from=email_from, \
- email_cc=', '.join(email_cc), message_id=message_id, \
- references=ref_id or message_id, attach=attach)
- if obj.state == 'unchanged':
- pass
- elif obj.state == 'done':
- case_pool.case_close(cr, uid, [case.id])
- elif obj.state == 'draft':
- case_pool.case_reset(cr, uid, [case.id])
- elif obj.state in ['cancel', 'open', 'pending']:
- act = 'case_' + obj.state
- getattr(case_pool, act)(cr, uid, [case.id])
-
- return {'type': 'ir.actions.act_window_close'}
-
- def default_get(self, cr, uid, fields, context=None):
- """
- This function gets default values
- """
- if context is None:
- context = {}
-
- if not context.get('active_model'):
- raise osv.except_osv(_('Error'), _('Can not send mail!'))
-
- res = super(crm_send_new_email, self).default_get(cr, uid, fields, context=context)
-
- if context.get('mail') == 'reply':
- res.update(self.get_reply_defaults(cr, uid, fields, context=context))
- return res
-
- model = context.get('active_model')
- mod_obj = self.pool.get(model)
- res_id = context and context.get('active_ids', []) or []
-
- user_obj = self.pool.get('res.users')
- user_mail_from = user_obj._get_email_from(cr, uid, [uid], context=context)[uid]
- for case in mod_obj.browse(cr, uid, res_id, context=context):
- if 'email_to' in fields:
- res.update({'email_to': case.email_from and tools.ustr(case.email_from) or ''})
- if context.get('mass_mail'):
- res.update({'email_to': ''})
- if 'email_from' in fields:
- res.update({'email_from': user_mail_from and tools.ustr(user_mail_from) or ''})
- if 'reply_to' in fields:
- if hasattr(case, 'section_id'):
- res.update({'reply_to': case.section_id and case.section_id.reply_to or False})
- if 'subject' in fields:
- res.update({'subject': tools.ustr(context.get('subject', case.name) or '')})
- if context.get('mass_mail'):
- res.update({'subject': ''})
- if 'email_cc' in fields:
- res.update({'email_cc': tools.ustr(case.email_cc or '')})
- if context.get('mass_mail'):
- res.update({'email_cc': ''})
- if 'body' in fields:
- res.update({'body': u'\n'+(tools.ustr(case.user_id.signature or ''))})
- if 'state' in fields:
- res.update({'state': u'pending'})
-
- return res
-
- def get_reply_defaults(self, cr, uid, fields, context=None):
- """
- This function gets default values for reply mail
- """
- hist_obj = self.pool.get('mailgate.message')
- res_ids = context and context.get('active_ids', []) or []
-
- user_obj = self.pool.get('res.users')
- user_mail_from = user_obj._get_email_from(cr, uid, [uid], context=context)[uid]
-
- include_original = context and context.get('include_original', False) or False
- res = {}
- for hist in hist_obj.browse(cr, uid, res_ids, context=context):
- model = hist.model
-
- # In the case where the crm.case does not exist in the database
- if not model:
- return {'type': 'ir.actions.act_window_close'}
-
- model_pool = self.pool.get(model)
- res_id = hist.res_id
- case = model_pool.browse(cr, uid, res_id)
- if 'email_to' in fields:
- res.update({'email_to': case.email_from and tools.ustr(case.email_from) or False})
- if 'email_from' in fields:
- res.update({'email_from': user_mail_from and tools.ustr(user_mail_from) or False})
-
- signature = u'\n' + (tools.ustr(case.user_id.signature or '')) + u'\n'
- original = [signature]
-
- if include_original == True and 'body' in fields:
- header = u'-------- Original Message --------'
- sender = u'From: %s' %(tools.ustr(hist.email_from or ''))
- to = u'To: %s' % (tools.ustr(hist.email_to or ''))
- sentdate = u'Date: %s' % (tools.ustr(hist.date))
- desc = u'\n%s'%(tools.ustr(hist.description))
-
- original = [signature, header, sender, to, sentdate, desc]
-
- res['body']= u'\n' + u'\n'.join(original)
-
- if 'subject' in fields:
- res.update({u'subject': u'Re: %s' %(tools.ustr(hist.name or ''))})
- if 'email_cc' in fields:
- email_cc = (case.email_cc and tools.ustr(case.email_cc) + ', ' or '') + (hist.email_cc or '')
- res.update({'email_cc': email_cc})
- if 'reply_to' in fields:
- if hasattr(case, 'section_id'):
- res.update({'reply_to': case.section_id.reply_to or ''})
- if 'state' in fields:
- res['state'] = u'pending'
- return res
-
- def view_init(self, cr, uid, fields_list, context=None):
- """
- This function checks for precondition before wizard executes
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param fields: List of fields for default value
- @param context: A standard dictionary for contextual values
-
- """
- if context is None:
- context = {}
-
- if not context.get('active_model'):
- raise osv.except_osv(_('Error'), _('Can not send mail!'))
- return True
-
-crm_send_new_email()
-
diff --git a/addons/crm/wizard/crm_send_email_view.xml b/addons/crm/wizard/crm_send_email_view.xml
deleted file mode 100644
index be1644b6bbb..00000000000
--- a/addons/crm/wizard/crm_send_email_view.xml
+++ /dev/null
@@ -1,154 +0,0 @@
-
-
-
-
-
-
-
- crm.new.send.mail.form
- crm.send.mail
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- crm.new.send.mass.mail.form
- crm.send.mail
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send Mail
- crm.send.mail
- form
- form
-
- new
-
-
-
-
-
-
-
-
- crm.mail.reply.form
- crm.send.mail
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Reply to last Mail
- crm.send.mail
- form
- form
-
- new
-
-
-
-
diff --git a/addons/crm/wizard/mail_compose_message.py b/addons/crm/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..512d9ece77a
--- /dev/null
+++ b/addons/crm/wizard/mail_compose_message.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+import tools
+
+SUPPORTED_MODELS = ['crm.lead',]
+
+class mail_compose_message(osv.osv_memory):
+ _inherit = 'mail.compose.message'
+
+ def get_value(self, cr, uid, model, res_id, context=None):
+ """Returns a defaults-like dict with initial values for the composition
+ wizard when sending an email related to the document record identified
+ by ``model`` and ``res_id``.
+
+ Overrides the default implementation to provide more default field values
+ related to the corresponding CRM case.
+
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ """
+ result = super(mail_compose_message, self).get_value(cr, uid, model, res_id, context=context)
+ if model in SUPPORTED_MODELS and res_id:
+ model_obj = self.pool.get(model)
+ data = model_obj.browse(cr, uid , res_id, context)
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ result.update({
+ 'subject' : data.name or False,
+ 'email_to' : data.email_from or False,
+ 'email_from' : user.user_email or tools.config.get('email_from', False),
+ 'body_text' : '\n' + tools.ustr(user.signature),
+ 'email_cc' : tools.ustr(data.email_cc or ''),
+ 'model': model,
+ 'res_id': res_id,
+ 'subtype': 'plain',
+ })
+ if hasattr(data, 'section_id'):
+ result.update({'reply_to' : data.section_id and data.section_id.reply_to or False})
+ return result
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/wizard_history_event.py b/addons/crm/wizard/wizard_history_event.py
index caaf37a88d8..bb501e0b750 100644
--- a/addons/crm/wizard/wizard_history_event.py
+++ b/addons/crm/wizard/wizard_history_event.py
@@ -32,7 +32,7 @@ def _open_history_event(self, cr, uid, data, context=None):
if id2:
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
res = ''
- if data.get('model',False) and data.get('ids',False):
+ if data.get('model') and data.get('ids'):
model_obj = pooler.get_pool(cr.dbname).get(data['model'])
res = model_obj.browse(cr, uid, data['ids'], context=context)
if len(res):
diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py
index 401c43b0455..9700a5a24b2 100644
--- a/addons/crm_claim/crm_claim.py
+++ b/addons/crm_claim/crm_claim.py
@@ -22,10 +22,12 @@
from osv import fields, osv
from crm import crm
import time
+from crm import wizard
import binascii
import tools
from tools.translate import _
+wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.claim')
CRM_CLAIM_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
crm.AVAILABLE_STATES[3][0], # Done
@@ -40,69 +42,69 @@ class crm_claim(crm.crm_case, osv.osv):
_name = "crm.claim"
_description = "Claim"
_order = "priority,date desc"
- _inherit = ['mailgate.thread']
+ _inherit = ['mail.thread']
_columns = {
- 'id': fields.integer('ID', readonly=True),
- 'name': fields.char('Claim Subject', size=128, required=True),
+ 'id': fields.integer('ID', readonly=True),
+ 'name': fields.char('Claim Subject', size=128, required=True),
'action_next': fields.char('Next Action', size=200),
'date_action_next': fields.datetime('Next Action Date'),
- 'description': fields.text('Description'),
- 'resolution': fields.text('Resolution'),
- 'create_date': fields.datetime('Creation Date' , readonly=True),
- 'write_date': fields.datetime('Update Date' , readonly=True),
- 'date_deadline': fields.date('Deadline'),
- 'date_closed': fields.datetime('Closed', readonly=True),
- 'date': fields.datetime('Claim Date'),
- 'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
+ 'description': fields.text('Description'),
+ 'resolution': fields.text('Resolution'),
+ 'create_date': fields.datetime('Creation Date' , readonly=True),
+ 'write_date': fields.datetime('Update Date' , readonly=True),
+ 'date_deadline': fields.date('Deadline'),
+ 'date_closed': fields.datetime('Closed', readonly=True),
+ 'date': fields.datetime('Claim Date'),
+ 'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
- ('object_id.model', '=', 'crm.claim')]"),
- 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
+ ('object_id.model', '=', 'crm.claim')]"),
+ 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'type_action': fields.selection([('correction','Corrective Action'),('prevention','Preventive Action')], 'Action Type'),
- 'user_id': fields.many2one('res.users', 'Responsible'),
- 'user_fault': fields.char('Trouble Responsible', size=64),
+ 'user_id': fields.many2one('res.users', 'Responsible'),
+ 'user_fault': fields.char('Trouble Responsible', size=64),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
select=True, help="Sales team to which Case belongs to."\
"Define Responsible user and Email account for"\
- " mail gateway."),
- 'company_id': fields.many2one('res.company', 'Company'),
- 'partner_id': fields.many2one('res.partner', 'Partner'),
+ " mail gateway."),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ 'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
# domain="[('partner_id','=',partner_id)]"
- ),
- 'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'email_from': fields.char('Email', size=128, help="These people will receive email."),
- 'partner_phone': fields.char('Phone', size=32),
+ ),
+ 'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
+ 'email_from': fields.char('Email', size=128, help="These people will receive email."),
+ 'partner_phone': fields.char('Phone', size=32),
'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('section_ids','=',section_id)]"),
- 'cause': fields.text('Root Cause'),
- 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
+ 'cause': fields.text('Root Cause'),
+ 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
- \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
}
_defaults = {
- 'user_id': crm.crm_case._get_default_user,
- 'partner_id': crm.crm_case._get_default_partner,
- 'partner_address_id': crm.crm_case._get_default_partner_address,
- 'email_from':crm.crm_case. _get_default_email,
- 'state': lambda *a: 'draft',
- 'section_id': crm.crm_case._get_section,
+ 'user_id': crm.crm_case._get_default_user,
+ 'partner_id': crm.crm_case._get_default_partner,
+ 'partner_address_id': crm.crm_case._get_default_partner_address,
+ 'email_from':crm.crm_case. _get_default_email,
+ 'state': lambda *a: 'draft',
+ 'section_id':crm.crm_case. _get_section,
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
+ 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
-
+
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
- @param part: Partner's id
- @email: Partner's email ID
+ :param part: Partner's id
+ :param email: ignored
"""
if not part:
return {'value': {'partner_address_id': False,
- 'email_from': False,
+ 'email_from': False,
'partner_phone': False,
'partner_mobile': False
}}
@@ -113,8 +115,8 @@ class crm_claim(crm.crm_case, osv.osv):
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
- @param add: Id of Partner's address
- @email: Partner's email ID
+ :param part: Partner's id
+ :param email: ignored
"""
if not add:
return {'value': {'email_from': False}}
@@ -122,6 +124,7 @@ class crm_claim(crm.crm_case, osv.osv):
return {'value': {'email_from': address.email, 'partner_phone': address.phone, 'partner_mobile': address.mobile}}
def case_open(self, cr, uid, ids, *args):
+ """Opens Claim"""
for l in self.browse(cr, uid, ids):
# When coming from draft override date and stage otherwise just set state
if l.state == 'draft':
@@ -135,16 +138,13 @@ class crm_claim(crm.crm_case, osv.osv):
res = super(crm_claim, self).case_open(cr, uid, ids, *args)
return res
- def message_new(self, cr, uid, msg, context=None):
- """ Automatically calls when new email message arrives
- """
- mailgate_pool = self.pool.get('email.server.tools')
-
- subject = msg.get('subject') or _("No Subject")
- body = msg.get('body')
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ res_id = super(crm_claim,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
+ subject = msg.get('subject')
+ body = msg.get('body_text')
msg_from = msg.get('from')
priority = msg.get('priority')
-
vals = {
'name': subject,
'email_from': msg_from,
@@ -152,35 +152,18 @@ class crm_claim(crm.crm_case, osv.osv):
'description': body,
'user_id': False,
}
- if msg.get('priority', False):
+ if priority:
vals['priority'] = priority
+ vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False)))
+ self.write(cr, uid, [res_id], vals, context=context)
+ return res_id
- res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
- if res:
- vals.update(res)
-
- res = self.create(cr, uid, vals, context)
- attachents = msg.get('attachments', [])
- for attactment in attachents or []:
- data_attach = {
- 'name': attactment,
- 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
- 'datas_fname': attactment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- self.pool.get('ir.attachment').create(cr, uid, data_attach)
-
- return res
-
- def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
- """
- @param ids: List of update mail’s IDs
- """
+ def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None):
if isinstance(ids, (str, int, long)):
ids = [ids]
+ res_id = super(crm_claim,self).message_update(cr, uid, ids, msg, context=context)
+
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
vals['priority'] = msg.get('priority')
@@ -190,7 +173,7 @@ class crm_claim(crm.crm_case, osv.osv):
'probability':'probability'
}
vls = {}
- for line in msg['body'].split('\n'):
+ for line in msg['body_text'].split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower()):
@@ -208,19 +191,10 @@ class crm_claim(crm.crm_case, osv.osv):
res = self.write(cr, uid, [case.id], values, context=context)
return res
- def msg_send(self, cr, uid, id, *args, **argv):
- """ Send The Message
- @param ids: List of email’s IDs
- """
- return True
-
-crm_claim()
-
class res_partner(osv.osv):
_inherit = 'res.partner'
_columns = {
'claims_ids': fields.one2many('crm.claim', 'partner_id', 'Claims'),
}
-res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml
index 72dcabf7c9a..21ec4cf028e 100644
--- a/addons/crm_claim/crm_claim_view.xml
+++ b/addons/crm_claim/crm_claim_view.xml
@@ -70,15 +70,10 @@
-
-
-
-
-
@@ -159,12 +154,12 @@
-
-
+
+
@@ -172,20 +167,18 @@
-
-
+
-
-
-
+
+
+
-
+
@@ -199,10 +192,9 @@
name="%(crm.action_crm_add_note)d"
context="{'model': 'crm.lead' }"
icon="terp-document-new" type="action" />
-
+
diff --git a/addons/crm_claim/report/crm_claim_report.py b/addons/crm_claim/report/crm_claim_report.py
index 87eb8a529bc..bb9642af1e1 100644
--- a/addons/crm_claim/report/crm_claim_report.py
+++ b/addons/crm_claim/report/crm_claim_report.py
@@ -60,7 +60,7 @@ class crm_claim_report(osv.osv):
('11', 'November'), ('12', 'December')], 'Month', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'create_date': fields.datetime('Create Date', readonly=True, select=True),
- 'day': fields.char('Day', size=128, readonly=True),
+ 'day': fields.char('Day', size=128, readonly=True),
'delay_close': fields.float('Delay to close', digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
'stage_id': fields.many2one ('crm.case.stage', 'Stage', readonly=True,domain="[('section_ids','=',section_id)]"),
'categ_id': fields.many2one('crm.case.categ', 'Category',\
@@ -71,8 +71,8 @@ class crm_claim_report(osv.osv):
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'priority': fields.selection(AVAILABLE_PRIORITIES, 'Priority'),
'type_action': fields.selection([('correction','Corrective Action'),('prevention','Preventive Action')], 'Action Type'),
- 'date_closed': fields.date('Close Date', readonly=True, select=True),
- 'date_deadline': fields.date('Deadline', readonly=True, select=True),
+ 'date_closed': fields.date('Close Date', readonly=True, select=True),
+ 'date_deadline': fields.date('Deadline', readonly=True, select=True),
'delay_expected': fields.float('Overpassed Deadline',digits=(16,2),readonly=True, group_operator="avg"),
'email': fields.integer('# Emails', size=128, readonly=True),
'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg")
@@ -106,7 +106,7 @@ class crm_claim_report(osv.osv):
c.type_action as type_action,
date_trunc('day',c.create_date) as create_date,
avg(extract('epoch' from (c.date_closed-c.create_date)))/(3600*24) as delay_close,
- (SELECT count(id) FROM mailgate_message WHERE model='crm.claim' AND res_id=c.id AND history=True) AS email,
+ (SELECT count(id) FROM mail_message WHERE model='crm.claim' AND res_id=c.id AND email_from IS NOT NULL) AS email,
(SELECT avg(probability) FROM crm_case_stage WHERE id=c.stage_id) AS probability,
extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24) as delay_expected
from
diff --git a/addons/crm_fundraising/crm_fundraising.py b/addons/crm_fundraising/crm_fundraising.py
index 01234dbf436..a935162e98e 100644
--- a/addons/crm_fundraising/crm_fundraising.py
+++ b/addons/crm_fundraising/crm_fundraising.py
@@ -21,6 +21,9 @@
from osv import fields, osv
from crm import crm
+from crm import wizard
+
+wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.fundraising')
class crm_fundraising(crm.crm_case, osv.osv):
""" Fund Raising Cases """
@@ -28,67 +31,84 @@ class crm_fundraising(crm.crm_case, osv.osv):
_name = "crm.fundraising"
_description = "Fund Raising"
_order = "id desc"
- _inherit = ['mailgate.thread']
+ _inherit = ['mail.thread']
_columns = {
- 'id': fields.integer('ID'),
+ 'id': fields.integer('ID'),
'name': fields.char('Name', size=128, required=True),
- 'active': fields.boolean('Active', required=False),
+ 'active': fields.boolean('Active', required=False),
'date_action_last': fields.datetime('Last Action', readonly=1),
- 'date_action_next': fields.datetime('Next Action', readonly=1),
- 'description': fields.text('Description'),
- 'create_date': fields.datetime('Creation Date' , readonly=True),
- 'write_date': fields.datetime('Update Date' , readonly=True),
- 'date_deadline': fields.date('Deadline'),
- 'user_id': fields.many2one('res.users', 'Responsible'),
+ 'date_action_next': fields.datetime('Next Action', readonly=1),
+ 'description': fields.text('Description'),
+ 'create_date': fields.datetime('Creation Date' , readonly=True),
+ 'write_date': fields.datetime('Update Date' , readonly=True),
+ 'date_deadline': fields.date('Deadline'),
+ 'user_id': fields.many2one('res.users', 'Responsible'),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
- select=True, help='Sales team to which Case belongs to. Define Responsible user and Email account for mail gateway.'),
- 'company_id': fields.many2one('res.company', 'Company'),
- 'partner_id': fields.many2one('res.partner', 'Partner'),
+ select=True, help='Sales team to which Case belongs to. Define Responsible user and Email account for mail gateway.'),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ 'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
- domain="[('partner_id','=',partner_id)]"),
- 'email_cc': fields.text('Watchers Emails', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'email_from': fields.char('Email', size=128, help="These people will receive email."),
- 'date_closed': fields.datetime('Closed', readonly=True),
- 'date': fields.datetime('Date'),
- 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
+ domain="[('partner_id','=',partner_id)]"),
+ 'email_cc': fields.text('Watchers Emails', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
+ 'email_from': fields.char('Email', size=128, help="These people will receive email."),
+ 'date_closed': fields.datetime('Closed', readonly=True),
+ 'date': fields.datetime('Date'),
+ 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
- ('object_id.model', '=', 'crm.fundraising')]"),
- 'planned_revenue': fields.float('Planned Revenue'),
- 'planned_cost': fields.float('Planned Costs'),
- 'probability': fields.float('Probability (%)'),
- 'partner_name': fields.char("Employee's Name", size=64),
- 'partner_name2': fields.char('Employee Email', size=64),
- 'partner_phone': fields.char('Phone', size=32),
- 'partner_mobile': fields.char('Mobile', size=32),
+ ('object_id.model', '=', 'crm.fundraising')]"),
+ 'planned_revenue': fields.float('Planned Revenue'),
+ 'planned_cost': fields.float('Planned Costs'),
+ 'probability': fields.float('Probability (%)'),
+ 'partner_name': fields.char("Employee's Name", size=64),
+ 'partner_name2': fields.char('Employee Email', size=64),
+ 'partner_phone': fields.char('Phone', size=32),
+ 'partner_mobile': fields.char('Mobile', size=32),
'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('section_ids', '=', section_id)]"),
'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
- domain="[('section_id','=',section_id)]"),
- 'duration': fields.float('Duration'),
- 'ref': fields.reference('Reference', selection=crm._links_get, size=128),
- 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
- 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
+ domain="[('section_id','=',section_id)]"),
+ 'duration': fields.float('Duration'),
+ 'ref': fields.reference('Reference', selection=crm._links_get, size=128),
+ 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
+ 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
- \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
}
+
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ res_id = super(crm_fundraising,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
+ vals = {
+ 'name': msg.get('subject'),
+ 'email_from': msg.get('from'),
+ 'email_cc': msg.get('cc'),
+ 'description': msg.get('body_text'),
+ }
+ priority = msg.get('priority')
+ if priority:
+ vals['priority'] = priority
+ vals.update(self.message_partner_by_email(cr, uid, msg.get('from')))
+ self.write(cr, uid, [res_id], vals, context=context)
+ return res_id
+
+
_defaults = {
- 'active': lambda *a: 1,
- 'user_id': crm.crm_case._get_default_user,
- 'partner_id': crm.crm_case._get_default_partner,
- 'partner_address_id': crm.crm_case._get_default_partner_address,
- 'email_from': crm.crm_case. _get_default_email,
- 'state': lambda *a: 'draft',
- 'section_id': crm.crm_case. _get_section,
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
- 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
- 'probability': lambda *a:0.0,
- 'planned_cost': lambda *a:0.0,
- 'planned_revenue': lambda *a:0.0,
- }
+ 'active': 1,
+ 'user_id': crm.crm_case._get_default_user,
+ 'partner_id': crm.crm_case._get_default_partner,
+ 'partner_address_id': crm.crm_case._get_default_partner_address,
+ 'email_from': crm.crm_case. _get_default_email,
+ 'state': 'draft',
+ 'section_id': crm.crm_case. _get_section,
+ 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
+ 'priority': crm.AVAILABLE_PRIORITIES[2][0],
+ 'probability': 0.0,
+ 'planned_cost': 0.0,
+ 'planned_revenue': 0.0,
+ }
-crm_fundraising()
diff --git a/addons/crm_fundraising/crm_fundraising_view.xml b/addons/crm_fundraising/crm_fundraising_view.xml
index c98a06eda46..a527eddd1fd 100644
--- a/addons/crm_fundraising/crm_fundraising_view.xml
+++ b/addons/crm_fundraising/crm_fundraising_view.xml
@@ -136,15 +136,15 @@
-
+
-
-
+
+
@@ -152,20 +152,18 @@
-
-
+
-
-
-
+
+
+
-
+
@@ -179,10 +177,9 @@
name="%(crm.action_crm_add_note)d"
context="{'model': 'crm.lead' }"
icon="terp-document-new" type="action" />
-
+
diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py
index b27fa04a2af..48bc2a1bb2e 100644
--- a/addons/crm_helpdesk/crm_helpdesk.py
+++ b/addons/crm_helpdesk/crm_helpdesk.py
@@ -22,7 +22,7 @@
from crm import crm
from osv import fields, osv
import time
-import binascii
+from crm import wizard
import tools
from tools.translate import _
@@ -32,121 +32,92 @@ CRM_HELPDESK_STATES = (
crm.AVAILABLE_STATES[4][0], # Pending
)
+wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.helpdesk')
+
class crm_helpdesk(crm.crm_case, osv.osv):
""" Helpdesk Cases """
_name = "crm.helpdesk"
_description = "Helpdesk"
_order = "id desc"
- _inherit = ['mailgate.thread']
+ _inherit = ['mail.thread']
_columns = {
- 'id': fields.integer('ID', readonly=True),
- 'name': fields.char('Name', size=128, required=True),
- 'active': fields.boolean('Active', required=False),
- 'date_action_last': fields.datetime('Last Action', readonly=1),
- 'date_action_next': fields.datetime('Next Action', readonly=1),
- 'description': fields.text('Description'),
- 'create_date': fields.datetime('Creation Date' , readonly=True),
- 'write_date': fields.datetime('Update Date' , readonly=True),
- 'date_deadline': fields.date('Deadline'),
- 'user_id': fields.many2one('res.users', 'Responsible'),
+ 'id': fields.integer('ID', readonly=True),
+ 'name': fields.char('Name', size=128, required=True),
+ 'active': fields.boolean('Active', required=False),
+ 'date_action_last': fields.datetime('Last Action', readonly=1),
+ 'date_action_next': fields.datetime('Next Action', readonly=1),
+ 'description': fields.text('Description'),
+ 'create_date': fields.datetime('Creation Date' , readonly=True),
+ 'write_date': fields.datetime('Update Date' , readonly=True),
+ 'date_deadline': fields.date('Deadline'),
+ 'user_id': fields.many2one('res.users', 'Responsible'),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
select=True, help='Sales team to which Case belongs to.\
- Define Responsible user and Email account for mail gateway.'),
- 'company_id': fields.many2one('res.company', 'Company'),
- 'date_closed': fields.datetime('Closed', readonly=True),
- 'partner_id': fields.many2one('res.partner', 'Partner'),
+ Define Responsible user and Email account for mail gateway.'),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ 'date_closed': fields.datetime('Closed', readonly=True),
+ 'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
- domain="[('partner_id','=',partner_id)]"),
- 'email_cc': fields.text('Watchers Emails', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'email_from': fields.char('Email', size=128, help="These people will receive email."),
- 'date': fields.datetime('Date'),
- 'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
- 'ref2' : fields.reference('Reference 2', selection=crm._links_get, size=128),
+ domain="[('partner_id','=',partner_id)]"),
+ 'email_cc': fields.text('Watchers Emails', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
+ 'email_from': fields.char('Email', size=128, help="These people will receive email."),
+ 'date': fields.datetime('Date'),
+ 'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
+ 'ref2' : fields.reference('Reference 2', selection=crm._links_get, size=128),
'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."),
- 'planned_revenue': fields.float('Planned Revenue'),
- 'planned_cost': fields.float('Planned Costs'),
- 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
- 'probability': fields.float('Probability (%)'),
+ 'planned_revenue': fields.float('Planned Revenue'),
+ 'planned_cost': fields.float('Planned Costs'),
+ 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
+ 'probability': fields.float('Probability (%)'),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
- ('object_id.model', '=', 'crm.helpdesk')]"),
- 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
- 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
+ ('object_id.model', '=', 'crm.helpdesk')]"),
+ 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
+ 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
\nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
}
_defaults = {
- 'active': lambda *a: 1,
- 'user_id': crm.crm_case._get_default_user,
- 'partner_id': crm.crm_case._get_default_partner,
- 'partner_address_id': crm.crm_case._get_default_partner_address,
- 'email_from': crm.crm_case. _get_default_email,
- 'state': lambda *a: 'draft',
+ 'active': lambda *a: 1,
+ 'user_id': crm.crm_case._get_default_user,
+ 'partner_id': crm.crm_case._get_default_partner,
+ 'partner_address_id': crm.crm_case._get_default_partner_address,
+ 'email_from': crm.crm_case. _get_default_email,
+ 'state': lambda *a: 'draft',
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
- 'section_id': crm.crm_case. _get_section,
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
- 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
+ 'section_id': crm.crm_case. _get_section,
+ 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
+ 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
- def message_new(self, cr, uid, msg, context=None):
- """
- Automatically calls when new email message arrives
-
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks
- """
- mailgate_pool = self.pool.get('email.server.tools')
-
- subject = msg.get('subject') or _("No Subject")
- body = msg.get('body')
- msg_from = msg.get('from')
- priority = msg.get('priority')
-
+ def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ res_id = super(crm_helpdesk,self).message_new(cr, uid, msg_dict, custom_values=custom_values, context=context)
+ subject = msg_dict.get('subject') or _("No Subject")
+ body = msg_dict.get('body_text')
+ msg_from = msg_dict.get('from')
vals = {
'name': subject,
'email_from': msg_from,
- 'email_cc': msg.get('cc'),
+ 'email_cc': msg_dict.get('cc'),
'description': body,
'user_id': False,
}
- if msg.get('priority', False):
- vals['priority'] = priority
+ vals.update(self.message_partner_by_email(cr, uid, msg_from))
+ self.write(cr, uid, [res_id], vals, context)
+ return res_id
- res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
- if res:
- vals.update(res)
-
- res = self.create(cr, uid, vals, context)
- attachents = msg.get('attachments', [])
- for attactment in attachents or []:
- data_attach = {
- 'name': attactment,
- 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
- 'datas_fname': attactment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- self.pool.get('ir.attachment').create(cr, uid, data_attach)
-
- return res
-
- def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
- """
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of update mail’s IDs
- """
+ def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None):
if isinstance(ids, (str, int, long)):
ids = [ids]
+ super(crm_helpdesk,self).message_update(cr, uid, ids, msg, context=context)
+
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
vals['priority'] = msg.get('priority')
@@ -156,7 +127,7 @@ class crm_helpdesk(crm.crm_case, osv.osv):
'probability':'probability'
}
vls = {}
- for line in msg['body'].split('\n'):
+ for line in msg['body_text'].split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower()):
@@ -174,19 +145,5 @@ class crm_helpdesk(crm.crm_case, osv.osv):
res = self.write(cr, uid, [case.id], values, context=context)
return res
- def msg_send(self, cr, uid, id, *args, **argv):
-
- """ Send The Message
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of email’s IDs
- @param *args: Return Tuple Value
- @param **args: Return Dictionary of Keyword Value
- """
- return True
-
-crm_helpdesk()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml
index af893a3f40f..0bdaf256a27 100644
--- a/addons/crm_helpdesk/crm_helpdesk_view.xml
+++ b/addons/crm_helpdesk/crm_helpdesk_view.xml
@@ -99,12 +99,12 @@
-
-
+
+
@@ -112,20 +112,18 @@
-
-
+
-
-
-
+
+
+
-
+
@@ -139,10 +137,9 @@
name="%(crm.action_crm_add_note)d"
context="{'model': 'crm.lead' }"
icon="terp-document-new" type="action" />
-
+
diff --git a/addons/crm_helpdesk/report/crm_helpdesk_report.py b/addons/crm_helpdesk/report/crm_helpdesk_report.py
index 7b70b77d5d1..80fc07db38d 100644
--- a/addons/crm_helpdesk/report/crm_helpdesk_report.py
+++ b/addons/crm_helpdesk/report/crm_helpdesk_report.py
@@ -56,7 +56,7 @@ class crm_helpdesk_report(osv.osv):
'date_deadline': fields.date('Deadline', select=True),
'priority': fields.selection([('5', 'Lowest'), ('4', 'Low'), \
('3', 'Normal'), ('2', 'High'), ('1', 'Highest')], 'Priority'),
- 'channel_id': fields.many2one('crm.case.channel', 'Channel'),
+ 'channel_id': fields.many2one('crm.case.channel', 'Channel'),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.helpdesk')]"),
@@ -97,7 +97,7 @@ class crm_helpdesk_report(osv.osv):
c.planned_cost,
count(*) as nbr,
extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as delay_close,
- (SELECT count(id) FROM mailgate_message WHERE model='crm.helpdesk' AND res_id=c.id AND history=True) AS email,
+ (SELECT count(id) FROM mail_message WHERE model='crm.helpdesk' AND res_id=c.id AND email_from IS NOT NULL) AS email,
abs(avg(extract('epoch' from (c.date_deadline - c.date_closed)))/(3600*24)) as delay_expected
from
crm_helpdesk c
diff --git a/addons/crm_partner_assign/test/test_crm_partner_assign.yml b/addons/crm_partner_assign/test/test_crm_partner_assign.yml
index 1253d55391c..4d982daab20 100644
--- a/addons/crm_partner_assign/test/test_crm_partner_assign.yml
+++ b/addons/crm_partner_assign/test/test_crm_partner_assign.yml
@@ -1,43 +1,43 @@
-
In order to test Forward Partner functionality, I create an opportunity and forward it to partner.
--
+-
I assign an email address to Administrator.
--
+-
!record {model: res.users, id: base.user_root}:
user_email: admin@openerp.com
--
+-
I create some partner grades.
-
I create a grade 'First'.
--
+-
!record {model: res.partner.grade, id: res_partner_grade_first0}:
name: First
sequence: 1
--
+-
I create another grade 'Second'.
--
+-
!record {model: res.partner.grade, id: res_partner_grade_second0}:
name: Second
sequence: 2
--
+-
I create one more grade 'Third'.
--
+-
!record {model: res.partner.grade, id: res_partner_grade_third0}:
name: Third
sequence: 3
--
+-
I assign grade 'First' to the partner 'Axelor'.
--
+-
!record {model: res.partner, id: base.res_partner_desertic_hispafuentes}:
grade_id: res_partner_grade_first0
-
I assgin a reply-to email address to Sales Team.
--
+-
!record {model: crm.case.section, id: crm.section_sales_department}:
reply_to: sales_openerp@openerp.com
--
+-
I create an opportunity 'Questionnaire on OpenERP'.
--
+-
!record {model: crm.lead, id: crm_lead_questionnaireonopenerp0}:
categ_id: crm.categ_oppor7
section_id: crm.section_sales_department
@@ -62,7 +62,7 @@
!python {model: crm.lead.forward.to.partner}: |
from tools import config
vals = {
- 'name': 'email',
+ 'subject': 'email',
'email_to': 'info@axelor.com',
'email_from': 'Administrator ',
'reply_to': 'sales_openerp@openerp.com'
diff --git a/addons/crm_partner_assign/wizard/crm_forward_to_partner.py b/addons/crm_partner_assign/wizard/crm_forward_to_partner.py
index 544dbd1ee7b..cdaf8f19b12 100644
--- a/addons/crm_partner_assign/wizard/crm_forward_to_partner.py
+++ b/addons/crm_partner_assign/wizard/crm_forward_to_partner.py
@@ -28,10 +28,10 @@ from tools.translate import _
class crm_lead_forward_to_partner(osv.osv_memory):
"""Forwards lead history"""
_name = 'crm.lead.forward.to.partner'
- _inherit = "crm.send.mail"
+ _inherit = "mail.compose.message"
_columns = {
- 'name': fields.selection([('user', 'User'), ('partner', 'Partner'), \
+ 'send_to': fields.selection([('user', 'User'), ('partner', 'Partner'), \
('email', 'Email Address')], 'Send to', required=True),
'user_id': fields.many2one('res.users', "User"),
'partner_id' : fields.many2one('res.partner', 'Partner'),
@@ -40,7 +40,7 @@ class crm_lead_forward_to_partner(osv.osv_memory):
}
_defaults = {
- 'name' : 'email',
+ 'send_to' : 'email',
'history': 'latest',
'email_from': lambda self, cr, uid, *a: self.pool.get('res.users')._get_email_from(cr, uid, uid)[uid],
}
@@ -68,13 +68,13 @@ class crm_lead_forward_to_partner(osv.osv_memory):
@param hist_id: Id of latest history
@param context: A standard dictionary for contextual values
"""
- log_pool = self.pool.get('mailgate.message')
+ log_pool = self.pool.get('mail.message')
hist = log_pool.browse(cr, uid, hist_id, context=context)
header = '-------- Original Message --------'
sender = 'From: %s' %(hist.email_from or '')
to = 'To: %s' % (hist.email_to or '')
sentdate = 'Date: %s' % (hist.date or '')
- desc = '\n%s'%(hist.description)
+ desc = '\n%s'%(hist.body_text)
original = [header, sender, to, sentdate, desc]
original = '\n'.join(original)
return original
@@ -109,7 +109,7 @@ class crm_lead_forward_to_partner(osv.osv_memory):
res_id = context.get('active_id')
msg_val = self._get_case_history(cr, uid, history_type, res_id, context=context)
if msg_val:
- res = {'value': {'body' : '\n\n' + msg_val}}
+ res = {'value': {'body_text' : '\n\n' + msg_val}}
return res
def _get_case_history(self, cr, uid, history_type, res_id, context=None):
@@ -125,12 +125,12 @@ class crm_lead_forward_to_partner(osv.osv_memory):
elif history_type == 'whole':
log_ids = model_pool.browse(cr, uid, res_id, context=context).message_ids
- log_ids = map(lambda x: x.id, filter(lambda x: x.history, log_ids))
+ log_ids = map(lambda x: x.id, filter(lambda x: x.email_from, log_ids))
msg_val = case_info + '\n\n' + self.get_whole_history(cr, uid, log_ids, context=context)
elif history_type == 'latest':
log_ids = model_pool.browse(cr, uid, res_id, context=context).message_ids
- log_ids = filter(lambda x: x.history and x.id, log_ids)
+ log_ids = filter(lambda x: x.email_from and x.id, log_ids)
if not log_ids:
msg_val = case_info
else:
@@ -170,6 +170,13 @@ class crm_lead_forward_to_partner(osv.osv_memory):
email = self.pool.get('res.partner.address').browse(cr, uid, address_id).email
return {'value': {'email_to' : email}}
+ def send_mail(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ super(crm_lead_forward_to_partner, self).send_mail(cr, uid, ids, context=context)
+ self.action_forward(cr, uid, ids, context)
+ return {'type': 'ir.actions.act_window_close'}
+
def action_forward(self, cr, uid, ids, context=None):
"""
Forward the lead to a partner
@@ -180,15 +187,13 @@ class crm_lead_forward_to_partner(osv.osv_memory):
case_pool = self.pool.get(context.get('active_model'))
res_id = context and context.get('active_id', False) or False
case = case_pool.browse(cr, uid, res_id, context=context)
-
context.update({'mail': 'forward'})
- super(crm_lead_forward_to_partner, self).action_send(cr, uid, ids, context=context)
to_write = {'date_assign': time.strftime('%Y-%m-%d')}
- if (this.name == 'partner' and this.partner_id):
+ if (this.send_to == 'partner' and this.partner_id):
to_write['partner_assigned_id'] = this.partner_id.id
- if this.name == 'user':
+ if this.send_to == 'user':
to_write.update({'user_id' : this.user_id.id})
email_re = r'([^ ,<@]+@[^> ,]+)'
email_cc = re.findall(email_re, case.email_cc or '')
@@ -202,7 +207,6 @@ class crm_lead_forward_to_partner(osv.osv_memory):
new_cc.append(to)
to_write.update({'email_cc' : ', '.join(new_cc) })
case_pool.write(cr, uid, case.id, to_write, context=context)
-
return {'type': 'ir.actions.act_window_close'}
def get_lead_details(self, cr, uid, lead_id, context=None):
@@ -285,14 +289,12 @@ class crm_lead_forward_to_partner(osv.osv_memory):
body = self._get_case_history(cr, uid, defaults.get('history', 'latest'), lead.id, context=context)
defaults.update({
'subject' : '%s: %s - %s' % (_('Fwd'), 'Openerp lead forward', lead.name),
- 'body' : body,
+ 'body_text' : body,
'email_cc' : email_cc,
'email_to' : email or 'dummy@dummy.ly'
})
return defaults
-crm_lead_forward_to_partner()
-
class crm_lead_mass_forward_to_partner(osv.osv_memory):
_name = 'crm.lead.mass.forward.to.partner'
_inherit = 'crm.lead.forward.to.partner'
@@ -312,13 +314,11 @@ class crm_lead_mass_forward_to_partner(osv.osv_memory):
continue
context.update({'active_id' : case.id})
- value = self.default_get(cr, uid, ['body', 'email_to', 'email_cc', 'subject', 'history'], context=context)
+ value = self.default_get(cr, uid, ['body_text', 'email_to', 'email_cc', 'subject', 'history'], context=context)
self.write(cr, uid, ids, value, context=context)
self.action_forward(cr,uid, ids, context=context)
return {'type': 'ir.actions.act_window_close'}
-crm_lead_mass_forward_to_partner()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml b/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
index 9826bf9d45c..d3ecddb1aa3 100644
--- a/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
+++ b/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
@@ -1,41 +1,54 @@
+
+
crm_lead_forward_to_partner
crm.lead.forward.to.partner
form
-
-
+
-
-
+
+
-
-
+
+
-
-
-
-
-
- crm_lead_forward_to_partner1
- crm.lead.forward.to.partner
- form
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/email_template/__init__.py b/addons/email_template/__init__.py
index dd87232679d..97931ea8bd5 100644
--- a/addons/email_template/__init__.py
+++ b/addons/email_template/__init__.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,9 +19,7 @@
# along with this program. If not, see
#
##############################################################################
-
-import email_template_account
import email_template
-import email_template_mailbox
import wizard
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/__openerp__.py b/addons/email_template/__openerp__.py
index f56062f37ed..e5f83f9ac0c 100644
--- a/addons/email_template/__openerp__.py
+++ b/addons/email_template/__openerp__.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,32 +21,47 @@
##############################################################################
{
- "name" : "Email Template for OpenERP",
- "version" : "0.7 RC",
+ "name" : "Email Templates",
+ "version" : "1.1",
"author" : "Openlabs",
"website" : "http://openerp.com",
"category" : "Tools",
'complexity': "expert",
- "depends" : ['marketing', 'base_tools'],
+ "depends" : ['mail'],
"description": """
-Email Template is extraction of Power Email basically just to send emails.
-==========================================================================
+Email Templating (simplified version of the original Power Email by Openlabs)
+=============================================================================
-You can define email accounts(server, port, mail format - HTML/Text/Both)
-and email templates (resource, recipient, subject, body, attachments).
+Lets you design complete email templates related to any OpenERP document (Sale
+Orders, Invoices and so on), including sender, recipient, subject, body (HTML and
+Text). You may also automatically attach files to your templates, or print and
+attach a report.
+
+For advanced use, the templates may include dynamic attributes of the document
+they are related to. For example, you may use the name of a Partner's country
+when writing to them, also providing a safe default in case the attribute is
+not defined. Each template contains a built-in assistant to help with the
+inclusion of these dynamic values.
+
+If you enable the option, a composition assistant will also appear in the sidebar
+of the OpenERP documents to which the template applies (e.g. Invoices).
+This serves as a quick way to send a new email based on the template, after
+reviewing and adapting the contents, if needed.
+This composition assistant will also turn into a mass mailing system when called
+for multiple documents at once.
+
+These email templates are also at the heart of the marketing campaign system
+(see the ``marketing_campaign`` application), if you need to automate larger
+campaigns on any OpenERP document.
+
+Technical note: only the templating system of the original Power Email by
+Openlabs was kept
-For each email template, you can have OpenERP generate a Wizard Action / Button
-that will be related to the object. So if you choose to do marketing campaigns
-for leads, the action will be added to the right side panel of the Lead form.
""",
- "init_xml": ['email_template_scheduler_data.xml'],
- "update_xml": [
- 'security/email_template_security.xml',
- 'email_template_workflow.xml',
- 'email_template_account_view.xml',
+ "data": [
+ 'wizard/email_template_preview_view.xml',
'email_template_view.xml',
- 'email_template_mailbox_view.xml',
- 'wizard/email_template_send_wizard_view.xml',
+ 'wizard/email_compose_message_view.xml',
'security/ir.model.access.csv'
],
"installable": True,
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
index aabc211df11..3917b3f2ddb 100644
--- a/addons/email_template/email_template.py
+++ b/addons/email_template/email_template.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,277 +21,186 @@
##############################################################################
import base64
-import random
-import netsvc
import logging
-import re
-TEMPLATE_ENGINES = []
-
-from osv import osv, fields
+import netsvc
+from osv import osv
+from osv import fields
+import tools
from tools.translate import _
try:
from mako.template import Template as MakoTemplate
- TEMPLATE_ENGINES.append(('mako', 'Mako Templates'))
except ImportError:
- logging.getLogger('init').warning("module email_template: 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 ImportError:
- logging.getLogger('init').warning("module email_template: Django templates not installed")
-
-import tools
-import pooler
-import logging
-
-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: OpenERP 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=context)
- env = {
- 'user':pool.get('res.users').browse(cursor, user, user, context=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:
- logging.exception("can't render %r", message)
- return u""
- else:
- return message
+ logging.getLogger('init').warning("email_template: mako templates not available, templating features will not work!")
class email_template(osv.osv):
- "Templates for sending Email"
-
+ "Templates for sending email"
+ _inherit = 'mail.message'
_name = "email.template"
- _description = 'Email Templates for Models'
+ _description = 'Email Templates'
+ _rec_name = 'name' # override mail.message's behavior
- 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']
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ """Render the given template text, replace mako expressions ``${expr}``
+ with the result of evaluating these expressions with
+ an evaluation context containing:
+
+ * ``user``: browse_record of the current user
+ * ``object``: browse_record of the document record this mail is
+ related to
+ * ``context``: the context passed to the mail composition wizard
+
+ :param str template: the template text to render
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ """
+ if not template: return u""
+ try:
+ template = tools.ustr(template)
+ record = None
+ if res_id:
+ record = self.pool.get(model).browse(cr, uid, res_id, context=context)
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ result = MakoTemplate(template).render_unicode(object=record,
+ user=user,
+ # context kw would clash with mako internals
+ ctx=context,
+ format_exceptions=True)
+ if result == u'False':
+ result = u''
+ return result
+ except Exception:
+ logging.exception("failed to render mako template value %r", template)
+ return u""
+
+ def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
+ if context is None:
+ context = {}
+ if not template_id:
+ return False
+ template = self.browse(cr, uid, template_id, context)
+ lang = self.render_template(cr, uid, template.lang, template.model, record_id, context)
+ if lang:
+ # Use translated template if necessary
+ ctx = context.copy()
+ ctx['lang'] = lang
+ template = self.browse(cr, uid, template.id, ctx)
else:
- mod_name = False
- return {
- 'value':{'model_int_name':mod_name}
- }
+ template = self.browse(cr, uid, int(template_id), context)
+ return template
+
+ def onchange_model_id(self, cr, uid, ids, model_id, context=None):
+ mod_name = False
+ if model_id:
+ mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
+ return {'value':{'model': mod_name}}
_columns = {
- 'name' : fields.char('Name', size=100, required=True),
- 'object_name':fields.many2one('ir.model', 'Resource'),
- 'model_int_name':fields.char('Model Internal Name', size=200,),
- 'from_account':fields.many2one(
- 'email_template.account',
- string="Email Account",
- help="Emails will be sent from this approved account."),
- 'def_to':fields.char(
- 'Recipient (To)',
- size=250,
- help="The Recipient of email. "
- "Placeholders can be used here. "
- "e.g. ${object.email_to}"),
- 'def_cc':fields.char(
- 'CC',
- size=250,
- help="Carbon Copy address(es), comma-separated."
- " Placeholders can be used here. "
- "e.g. ${object.email_cc}"),
- 'def_bcc':fields.char(
- 'BCC',
- size=250,
- help="Blind Carbon Copy address(es), comma-separated."
- " Placeholders can be used here. "
- "e.g. ${object.email_bcc}"),
- 'reply_to':fields.char('Reply-To',
- size=250,
- help="The address recipients should reply to,"
- " if different from the From address."
- " Placeholders can be used here. "
- "e.g. ${object.email_reply_to}"),
- 'message_id':fields.char('Message-ID',
- size=250,
- help="Specify the Message-ID SMTP header to use in outgoing emails. Please note that this overrides the Resource tracking option! Placeholders can be used here."),
- 'track_campaign_item':fields.boolean('Resource Tracking',
- help="Enable this is you wish to include a special \
-tracking marker in outgoing emails so you can identify replies and link \
-them back to the corresponding resource record. \
-This is useful for CRM leads for example"),
- '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(
- 'Subject',
- size=200,
- help="The 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(
- 'Signature',
- help="the signature from the User details"
- " will be appended to the mail"),
- 'file_name':fields.char(
- 'Report Filename',
- size=200,
- help="Name of the generated report file. Placeholders can be used in the filename. eg: 2009_SO003.pdf",
- translate=True),
- 'report_template':fields.many2one(
- 'ir.actions.report.xml',
- 'Report to send'),
- 'attachment_ids': fields.many2many(
- 'ir.attachment',
- 'email_template_attachment_rel',
- 'email_template_id',
- 'attachment_id',
- 'Attached Files',
- help="You may attach existing files to this template, "
- "so they will be added in all emails created from this template"),
- 'ref_ir_act_window':fields.many2one(
- 'ir.actions.act_window',
- 'Window Action',
- help="Action that will open this email template on Resource records",
- readonly=True),
- 'ref_ir_value':fields.many2one(
- 'ir.values',
- 'Wizard Button',
- help="Button in the side bar of the form view of this Resource that will invoke the Window Action",
- 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"),
- '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
- )
+ 'name': fields.char('Name', size=250),
+ 'model_id': fields.many2one('ir.model', 'Related document model'),
+ 'lang': fields.char('Language Selection', size=250,
+ help="Optional translation language (ISO code) to select when sending out an email. "
+ "If not set, the english version will be used. "
+ "This should usually be a placeholder expression "
+ "that provides the appropriate language code, e.g. "
+ "${object.partner_id.lang.code}."),
+ 'user_signature': fields.boolean('Add Signature',
+ help="If checked, the user's signature will be appended to the text version "
+ "of the message"),
+ 'report_name': fields.char('Report Filename', size=200, translate=True,
+ help="Name to use for the generated report file (may contain placeholders)\n"
+ "The extension can be omitted and will then come from the report type."),
+ 'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
+ 'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
+ help="Sidebar action to make this template available on records "
+ "of the related document model"),
+ 'ref_ir_value':fields.many2one('ir.values', 'Sidebar button', readonly=True,
+ help="Sidebar button to open the sidebar action"),
+ 'track_campaign_item': fields.boolean('Resource Tracking',
+ help="Enable this is you wish to include a special tracking marker "
+ "in outgoing emails so you can identify replies and link "
+ "them back to the corresponding resource record. "
+ "This is useful for CRM leads for example"),
+
+ # Overridden mail.message.common fields for technical reasons:
+ 'model': fields.related('model_id','model', type='char', string='Related Document model',
+ size=128, select=True, store=True, readonly=True),
+ # we need a separate m2m table to avoid ID collisions with the original mail.message entries
+ 'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
+ 'attachment_id', 'Files to attach',
+ help="You may attach files to this template, to be added to all "
+ "emails created from this template"),
+
+ # Overridden mail.message.common fields to make tooltips more appropriate:
+ 'subject':fields.char('Subject', size=512, translate=True, help="Subject (placeholders may be used here)",),
+ 'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"),
+ 'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"),
+ 'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"),
+ 'email_bcc': fields.char('Bcc', size=256, help="Blind carbon copy recipients (placeholders may be used here)"),
+ 'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"),
+ 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
+ help="Optional preferred server for outgoing mails. If not set, the highest "
+ "priority one will be used."),
+ 'body_text': fields.text('Text contents', translate=True, help="Plaintext version of the message (placeholders may be used here)"),
+ 'body_html': fields.text('Rich-text contents', help="Rich-text/HTML version of the message (placeholders may be used here)"),
+ 'message_id': fields.char('Message-Id', size=256, help="Message-ID SMTP header to use in outgoing messages based on this template. "
+ "Please note that this overrides the 'Resource Tracking' option, "
+ "so if you simply need to track replies to outgoing emails, enable "
+ "that option instead.\n"
+ "Placeholders must be used here, as this value always needs to be unique!"),
+
+ # Fake fields used to implement the placeholder assistant
+ 'model_object_field': fields.many2one('ir.model.fields', string="Field",
+ help="Select target field from the related document model.\n"
+ "If it is a relationship field you will be able to select "
+ "a target field at the destination of the relationship."),
+ 'sub_object': fields.many2one('ir.model', 'Sub-model', readonly=True,
+ help="When a relationship field is selected as first field, "
+ "this field shows the document model the relationship goes to."),
+ 'sub_model_object_field': fields.many2one('ir.model.fields', 'Sub-field',
+ help="When a relationship field is selected as first field, "
+ "this field lets you select the target field within the "
+ "destination document model (sub-model)."),
+ 'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128),
+ 'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
_defaults = {
- 'template_language' : lambda *a:'mako',
-
+ 'track_campaign_item': True
}
- _sql_constraints = [
- ('name', 'unique (name)','The template name must be unique !')
- ]
-
def create_action(self, cr, uid, ids, context=None):
vals = {}
- if context is None:
- context = {}
- template_obj = self.browse(cr, uid, ids, context=context)[0]
- src_obj = template_obj.object_name.model
- vals['ref_ir_act_window'] = self.pool.get('ir.actions.act_window').create(cr, uid, {
- 'name': template_obj.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, template_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)
- ir_values_obj = self.pool.get('ir.values')
- vals['ref_ir_value'] = ir_values_obj.create(cr, uid, {
- 'name': _('Send Mail (%s)') % template_obj.name,
- 'model': src_obj,
- 'key2': 'client_action_multi',
- 'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
- 'object': True,
- }, context)
+ action_obj = self.pool.get('ir.actions.act_window')
+ data_obj = self.pool.get('ir.model.data')
+ for template in self.browse(cr, uid, ids, context=context):
+ src_obj = template.model_id.model
+ model_data_id = data_obj._get_id(cr, uid, 'mail', 'email_compose_message_wizard_form')
+ res_id = data_obj.browse(cr, uid, model_data_id, context=context).res_id
+ vals['ref_ir_act_window'] = action_obj.create(cr, uid, {
+ 'name': template.name,
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'mail.compose.message',
+ 'src_model': src_obj,
+ 'view_type': 'form',
+ 'context': "{'mail.compose.message.mode':'mass_mail'}",
+ 'view_mode':'form,tree',
+ 'view_id': res_id,
+ 'target': 'new',
+ 'auto_refresh':1
+ }, context)
+ vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, {
+ 'name': _('Send Mail (%s)') % template.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, ids, {
- 'ref_ir_act_window': vals['ref_ir_act_window'],
- 'ref_ir_value': vals['ref_ir_value'],
- }, context)
+ 'ref_ir_act_window': vals.get('ref_ir_act_window',False),
+ 'ref_ir_value': vals.get('ref_ir_value',False),
+ }, context)
return True
def unlink_action(self, cr, uid, ids, context=None):
@@ -304,9 +213,6 @@ This is useful for CRM leads for example"),
ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context)
except:
raise osv.except_osv(_("Warning"), _("Deletion of Record failed"))
-
- def delete_action(self, cr, uid, ids, context=None):
- self.unlink_action(cr, uid, ids, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
@@ -314,511 +220,167 @@ This is useful for CRM leads for example"),
return super(email_template, self).unlink(cr, uid, ids, context=context)
def copy(self, cr, uid, id, default=None, context=None):
+ template = self.browse(cr, uid, id, context=context)
if default is None:
default = {}
default = default.copy()
- old = self.read(cr, uid, id, ['name'], context=context)
- new_name = _("Copy of template %s") % 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})
+ default['name'] = template.name + _('(copy)')
return super(email_template, self).copy(cr, uid, id, default, context)
- def build_expression(self, field_name, sub_field_name, null_value, template_language='mako'):
- """
- Returns a template expression based on data provided
- @param field_name: field name
- @param sub_field_name: sub field name (M2O)
- @param null_value: default value if the target value is empty
- @param template_language: name of template engine
- @return: computed expression
- """
+ def build_expression(self, field_name, sub_field_name, null_value):
+ """Returns a placeholder expression for use in a template field,
+ based on the values provided in the placeholder assistant.
+ :param field_name: main field name
+ :param sub_field_name: sub field name (M2O)
+ :param null_value: default value if the target value is empty
+ :return: final placeholder expression
+ """
expression = ''
- if template_language == 'mako':
- if field_name:
- expression = "${object." + field_name
- if sub_field_name:
- expression += "." + sub_field_name
- if null_value:
- expression += " or '''%s'''" % null_value
- expression += "}"
- elif template_language == 'django':
- if field_name:
- expression = "{{object." + field_name
- if sub_field_name:
- expression += "." + sub_field_name
- if null_value:
- expression += "|default: '''%s'''" % null_value
- expression += "}}"
+ if field_name:
+ expression = "${object." + field_name
+ if sub_field_name:
+ expression += "." + sub_field_name
+ if null_value:
+ expression += " or '''%s'''" % null_value
+ expression += "}"
return expression
- 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.build_expression(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.build_expression(field_obj.name,
- False,
- False,
- template_language
- )
- result['sub_model_object_field'] = False
- result['null_value'] = False
+ def onchange_sub_model_object_value_field(self, cr, uid, ids, model_object_field, sub_model_object_field=False, null_value=None, context=None):
+ result = {
+ 'sub_object': False,
+ 'copyvalue': False,
+ 'sub_model_object_field': False,
+ 'null_value': False
+ }
+ if model_object_field:
+ fields_obj = self.pool.get('ir.model.fields')
+ field_value = fields_obj.browse(cr, uid, model_object_field, context)
+ if field_value.ttype in ['many2one', 'one2many', 'many2many']:
+ res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_value.relation)], context=context)
+ sub_field_value = False
+ if sub_model_object_field:
+ sub_field_value = fields_obj.browse(cr, uid, sub_model_object_field, context)
+ if res_ids:
+ result.update({
+ 'sub_object': res_ids[0],
+ 'copyvalue': self.build_expression(field_value.name, sub_field_value and sub_field_value.name or False, null_value or False),
+ 'sub_model_object_field': sub_model_object_field or False,
+ 'null_value': null_value or False
+ })
+ else:
+ result.update({
+ 'copyvalue': self.build_expression(field_value.name, False, null_value or False),
+ 'null_value': null_value or 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.build_expression(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.build_expression(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.build_expression(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.build_expression(field_obj.name,
- False,
- null_value,
- template_language
- )
- result['sub_model_object_field'] = False
- result['null_value'] = null_value
- return {'value':result}
+ def generate_email(self, cr, uid, template_id, res_id, context=None):
+ """Generates an email from the template for given (model, res_id) pair.
- def _add_attachment(self, cursor, user, mailbox_id, name, data, filename, context=None):
+ :param template_id: id of the template to render.
+ :param res_id: id of the record to use for rendering the template (model
+ is taken from template definition)
+ :returns: a dict containing all relevant fields for creating a new
+ mail.message entry, with the addition one additional
+ special key ``attachments`` containing a list of
"""
- Add an attachment to a given mailbox entry.
-
- :param data: base64 encoded attachment data to store
- """
- attachment_obj = self.pool.get('ir.attachment')
- attachment_data = {
- 'name': (name or '') + _(' (Email Attachment)'),
- 'datas': data,
- 'datas_fname': filename,
- 'description': name or _('No Description'),
- 'res_model':'email_template.mailbox',
- 'res_id': mailbox_id,
+ if context is None:
+ context = {}
+ values = {
+ 'subject': False,
+ 'body_text': False,
+ 'body_html': False,
+ 'email_from': False,
+ 'email_to': False,
+ 'email_cc': False,
+ 'email_bcc': False,
+ 'reply_to': False,
+ 'auto_delete': False,
+ 'model': False,
+ 'res_id': False,
+ 'mail_server_id': False,
+ 'attachments': False,
+ 'attachment_ids': False,
+ 'message_id': False,
+ 'state': 'outgoing',
}
- attachment_id = attachment_obj.create(cursor,
- user,
- attachment_data,
- context)
- if attachment_id:
- self.pool.get('email_template.mailbox').write(
- cursor,
- user,
- mailbox_id,
- {
- 'attachments_ids':[(4, attachment_id)],
- 'mail_type':'multipart/mixed'
- },
- context)
+ if not template_id:
+ return values
- def generate_attach_reports(self,
- cursor,
- user,
- template,
- record_id,
- mail,
- context=None):
- """
- Generate report to be attached and attach it
- to the email, and add any directly attached files as well.
+ report_xml_pool = self.pool.get('ir.actions.report.xml')
+ template = self.get_email_template(cr, uid, template_id, res_id, context)
- @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
- """
+ for field in ['subject', 'body_text', 'body_html', 'email_from',
+ 'email_to', 'email_cc', 'email_bcc', 'reply_to',
+ 'message_id']:
+ values[field] = self.render_template(cr, uid, getattr(template, field),
+ template.model, res_id, context=context) \
+ or False
+
+ if template.user_signature:
+ signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
+ values['body_text'] += '\n\n' + signature
+
+ values.update(mail_server_id = template.mail_server_id.id or False,
+ auto_delete = template.auto_delete,
+ model=template.model,
+ res_id=res_id or False)
+
+ attachments = {}
+ # Add report as a Document
if template.report_template:
- reportname = 'report.' + \
- self.pool.get('ir.actions.report.xml').read(
- cursor,
- user,
- template.report_template.id,
- ['report_name'],
- context)['report_name']
- service = netsvc.LocalService(reportname)
- data = {}
- data['model'] = template.model_int_name
- (result, format) = service.create(cursor,
- user,
- [record_id],
- data,
- context)
- fname = tools.ustr(get_value(cursor, user, record_id,
- template.file_name, template, context)
- or 'Report')
- ext = '.' + format
- if not fname.endswith(ext):
- fname += ext
- self._add_attachment(cursor, user, mail.id, mail.subject, base64.b64encode(result), fname, context)
-
- if template.attachment_ids:
- for attachment in template.attachment_ids:
- self._add_attachment(cursor, user, mail.id, attachment.name, attachment.datas, attachment.datas_fname, context)
-
- return True
-
- def _generate_mailbox_item_from_template(self,
- 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.from_account.id,
- 'name':template.from_account.name,
- 'email_id':template.from_account.email_id
- }
- lang = get_value(cursor,
- user,
- record_id,
- template.lang,
- template,
- context)
- if lang:
+ report_name = template.report_name
+ report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
+ # Ensure report is rendered using template's language
ctx = context.copy()
- ctx.update({'lang':lang})
- template = self.browse(cursor, user, template.id, context=ctx)
+ if template.lang:
+ ctx['lang'] = self.render_template(cr, uid, template.lang, template.model, res_id, context)
+ service = netsvc.LocalService(report_service)
+ (result, format) = service.create(cr, uid, [res_id], {'model': template.model}, ctx)
+ result = base64.b64encode(result)
+ if not report_name:
+ report_name = report_service
+ ext = "." + format
+ if not report_name.endswith(ext):
+ report_name += ext
+ attachments[report_name] = result
- # determine name of sender, either it is specified in email_id or we
- # use the account name
- email_id = from_account['email_id'].strip()
- email_from = re.findall(r'([^ ,<@]+@[^> ,]+)', email_id)[0]
- if email_from != email_id:
- # we should keep it all, name is probably specified in the address
- email_from = from_account['email_id']
- else:
- email_from = tools.ustr(from_account['name']) + "<" + tools.ustr(email_id) + ">"
+ # Add document attachments
+ for attach in template.attachment_ids:
+ # keep the bytes as fetched from the db, base64 encoded
+ attachments[attach.datas_fname] = attach.datas
- # FIXME: should do this in a loop and rename template fields to the corresponding
- # mailbox fields. (makes no sense to have different names I think.
- mailbox_values = {
- 'email_from': email_from,
- '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),
- 'reply_to':get_value(cursor,
- user,
- record_id,
- template.reply_to,
- 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',
- }
+ values['attachments'] = attachments
+ return values
- if template['message_id']:
- # use provided message_id with placeholders
- mailbox_values.update({'message_id': get_value(cursor, user, record_id, template['message_id'], template, context)})
+ def send_mail(self, cr, uid, template_id, res_id, context=None):
+ """Generates a new mail message for the given template and record,
+ and schedule it for delivery through the ``mail`` module's scheduler.
- elif template['track_campaign_item']:
- # get appropriate message-id
- mailbox_values.update({'message_id': tools.misc.generate_tracking_message_id(record_id)})
-
- if not mailbox_values['account_id']:
- raise Exception("Unable to send the mail. No account linked to the template.")
- #Use signatures if allowed
- 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")
- result = True
- mailbox_obj = self.pool.get('email_template.mailbox')
- for record_id in record_ids:
- mailbox_id = self._generate_mailbox_item_from_template(
- cursor,
- user,
- template,
- record_id,
- context)
- mail = mailbox_obj.browse(
- cursor,
- user,
- mailbox_id,
- context=context
- )
- if template.report_template or template.attachment_ids:
- 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
- )
- # TODO : manage return value of all the records
- result = self.pool.get('email_template.mailbox').send_this_mail(cursor, user, [mailbox_id], context)
- return result
-
-email_template()
-
-
-## FIXME: this class duplicates a lot of features of the email template send wizard,
-## one of the 2 should inherit from the other!
-
-class email_template_preview(osv.osv_memory):
- _name = "email_template.preview"
- _description = "Email Template Preview"
-
- 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 'template_id' in context:
- ref_obj_id = self.pool.get('email.template').read(cr, uid, context['template_id'], ['object_name'], context)
- ref_obj_name = self.pool.get('ir.model').read(cr, uid, ref_obj_id['object_name'][0], ['model'], context)['model']
- model_obj = self.pool.get(ref_obj_name)
- ref_obj_ids = model_obj.search(cr, uid, [], 0, 20, 'id', context=context)
- if not ref_obj_ids:
- ref_obj_ids = []
-
- # also add the default one if requested, otherwise it won't be available for selection:
- default_id = context.get('default_rel_model_ref')
- if default_id and default_id not in ref_obj_ids:
- ref_obj_ids.insert(0, default_id)
- return model_obj.name_get(cr, uid, ref_obj_ids, context)
- return []
-
- def default_get(self, cr, uid, fields, context=None):
- if context is None:
- context = {}
- result = super(email_template_preview, self).default_get(cr, uid, fields, context=context)
- if (not fields or 'rel_model_ref' in fields) and 'template_id' in context \
- and not result.get('rel_model_ref'):
- selectables = self._get_model_recs(cr, uid, context=context)
- result['rel_model_ref'] = selectables and selectables[0][0] or False
- return result
-
- def _default_model(self, cursor, user, context=None):
+ :param int template_id: id of the template to render
+ :param int res_id: id of the record to render the template with
+ (model is taken from the template)
"""
- Returns the default value for model field
- @param cursor: Database Cursor
- @param user: ID of current user
- @param context: OpenERP Context
- """
- return self.pool.get('email.template').read(
- cursor,
- user,
- context['template_id'],
- ['object_name'],
- context).get('object_name', False)
-
- _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),
- 'reply_to':fields.char('Reply-To',
- size=250,
- help="The address recipients should reply to,"
- " if different from the From address."
- " Placeholders can be used here."),
- 'message_id':fields.char('Message-ID',
- size=250,
- help="The Message-ID header value, if you need to"
- "specify it, for example to automatically recognize the replies later."
- " Placeholders can be used here."),
- 'subject':fields.char('Subject', size=200, readonly=True),
- 'body_text':fields.text('Body', readonly=True),
- 'body_html':fields.text('Body', readonly=True),
- 'report':fields.char('Report Name', size=100, readonly=True),
- }
- _defaults = {
- 'ref_template': lambda self, cr, uid, ctx:ctx['template_id'] or False,
- '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['template_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['template_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['reply_to'] = get_value(cr, uid, rel_model_ref, template.reply_to, template, context)
- if template.message_id:
- vals['message_id'] = get_value(cr, uid, rel_model_ref, template.message_id, template, context)
- elif template.track_campaign_item:
- vals['message_id'] = tools.misc.generate_tracking_message_id(rel_model_ref)
- vals['subject'] = get_value(cr, uid, rel_model_ref, template.def_subject, template, context)
- vals['body_text'] = get_value(cr, uid, rel_model_ref, template.def_body_text, template, context)
- vals['body_html'] = get_value(cr, uid, rel_model_ref, template.def_body_html, template, context)
- vals['report'] = get_value(cr, uid, rel_model_ref, template.file_name, template, context)
- return {'value':vals}
-
-email_template_preview()
+ mail_message = self.pool.get('mail.message')
+ ir_attachment = self.pool.get('ir.attachment')
+ template = self.browse(cr, uid, template_id, context)
+ values = self.generate_email(cr, uid, template_id, res_id, context=context)
+ attachments = values.pop('attachments') or {}
+ message_id = mail_message.create(cr, uid, values, context=context)
+ # link attachments
+ attachment_ids = []
+ for fname, fcontent in attachments.iteritems():
+ attachment_data = {
+ 'name': fname,
+ 'datas_fname': fname,
+ 'datas': fcontent,
+ 'res_model': mail_message._name,
+ 'res_id': message_id,
+ }
+ if context.has_key('default_type'):
+ del context['default_type']
+ attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context))
# 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
deleted file mode 100644
index a5f51400c8a..00000000000
--- a/addons/email_template/email_template_account.py
+++ /dev/null
@@ -1,470 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2004-2010 OpenERP SA ()
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
-#
-##############################################################################
-
-from osv import osv, fields
-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 netsvc
-import datetime
-from tools.translate import _
-import tools
-import logging
-
-EMAIL_PATTERN = re.compile(r'([^()\[\] ,<:\\>@";]+@[^()\[\] ,<:\\>@";]+)') # See RFC822
-def extract_emails(emails_str):
- """
- Returns a list of email addresses recognized in a string, ignoring the rest of the string.
- extract_emails('a@b.com,c@bcom, "John Doe" , e@b.com') -> ['a@b.com','c@bcom', 'd@b.com', 'e@b.com']"
- """
- return EMAIL_PATTERN.findall(emails_str)
-
-
-def extract_emails_from_dict(addresses={}):
- """
- Extracts email addresses from a dictionary with comma-separated address string values, handling
- separately the To, CC, BCC and Reply-To addresses.
-
- :param addresses: a dictionary of addresses in the form {'To': 'a@b.com,c@bcom; d@b.com;e@b.com' , 'CC': 'e@b.com;f@b.com', ... }
- :return: a dictionary with a list of separate addresses for each header (To, CC, BCC), with an additional key 'all-recipients'
- containing all addresses for the 'To', 'CC', 'BCC' entries.
- """
- result = {'all-recipients':[]}
- keys = ['To', 'CC', 'BCC', 'Reply-To']
- for each in keys:
- emails = extract_emails(addresses.get(each, u''))
- while u'' in emails:
- emails.remove(u'')
- result[each] = emails
- if each != 'Reply-To':
- result['all-recipients'].extend(emails)
- return result
-
-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('Description',
- size=64, required=True,
- readonly=True, select=True,
- help="The description is used as the Sender name along with the provided From Email, \
-unless it is already specified in the From Email, e.g: John Doe ",
- states={'draft':[('readonly', False)]}),
- 'auto_delete': fields.boolean('Auto Delete', size=64, readonly=True,
- help="Permanently delete emails after sending",
- states={'draft':[('readonly', False)]}),
- 'user':fields.many2one('res.users',
- 'Related User', required=True,
- readonly=True, states={'draft':[('readonly', False)]}),
- 'email_id': fields.char('From Email',
- size=120, required=True,
- readonly=True, states={'draft':[('readonly', False)]} ,
- help="eg: 'john@doe.com' or 'John Doe '"),
- 'smtpserver': fields.char('Server',
- size=120, required=True,
- readonly=True, states={'draft':[('readonly', False)]},
- help="Enter name of outgoing server, eg: smtp.yourdomain.com"),
- 'smtpport': fields.integer('SMTP Port',
- size=64, required=True,
- readonly=True, states={'draft':[('readonly', False)]},
- help="Enter port number, eg: 25 or 587"),
- 'smtpuname': fields.char('User Name',
- size=120, required=False,
- readonly=True, states={'draft':[('readonly', False)]},
- help="Specify the username if your SMTP server requires authentication, "
- "otherwise leave it empty."),
- 'smtppass': fields.char('Password',
- size=120, invisible=True,
- required=False, readonly=True,
- states={'draft':[('readonly', False)]}),
- 'smtptls':fields.boolean('TLS',
- states={'draft':[('readonly', False)]}, readonly=True),
-
- 'smtpssl':fields.boolean('SSL/TLS (only in python 2.6)',
- states={'draft':[('readonly', False)]}, readonly=True),
- 'send_pref':fields.selection([
- ('html', 'HTML, otherwise Text'),
- ('text', 'Text, otherwise HTML'),
- ('alternative', 'Both HTML & Text (Alternative)'),
- ('mixed', 'Both HTML & Text (Mixed)')
- ], 'Mail Format', required=True),
- 'company':fields.selection([
- ('yes', 'Yes'),
- ('no', 'No')
- ], 'Corporate',
- readonly=True,
- help="Select if this mail account does not belong " \
- "to specific user but to the organization as a whole. " \
- "eg: info@companydomain.com",
- required=True, states={
- 'draft':[('readonly', False)]
- }),
-
- 'state':fields.selection([
- ('draft', 'Initiated'),
- ('suspended', 'Suspended'),
- ('approved', 'Approved')
- ],
- 'State', required=True, readonly=True),
- }
-
- _defaults = {
- 'name':lambda self, cursor, user, context:self.pool.get(
- 'res.users'
- ).read(
- cursor,
- user,
- user,
- ['name'],
- context
- )['name'],
- 'state':lambda * a:'draft',
- 'smtpport':lambda *a:25,
- 'smtpserver':lambda *a:'localhost',
- 'company':lambda *a:'yes',
- '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 name_get(self, cr, uid, ids, context=None):
- return [(a["id"], "%s (%s)" % (a['email_id'], a['name'])) for a in self.read(cr, uid, ids, ['name', 'email_id'], context=context)]
-
- def _constraint_unique(self, cursor, user, ids, context=None):
- """
- 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 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=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(str(this_object.smtpuname), str(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") % tools.ustr(error)
- )
-
- def do_approval(self, cr, uid, ids, context=None):
- #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=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, tools.ustr(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 send_mail(self, cr, uid, ids, addresses, subject='', body=None, payload=None, message_id=None, context=None):
- #TODO: Replace all this with a single email object
- if body is None:
- body = {}
- 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:
- # Prepare multipart containers depending on data
- text_subtype = (core_obj.send_pref == 'alternative') and 'alternative' or 'mixed'
- # Need a multipart/mixed wrapper for attachments if content is alternative
- if payload and text_subtype == 'alternative':
- payload_part = MIMEMultipart(_subtype='mixed')
- text_part = MIMEMultipart(_subtype=text_subtype)
- payload_part.attach(text_part)
- else:
- # otherwise a single multipart/mixed will do the whole job
- payload_part = text_part = MIMEMultipart(_subtype=text_subtype)
-
- if subject:
- payload_part['Subject'] = subject
- from_email = core_obj.email_id
- if '<' in from_email:
- # We have a structured email address, keep it untouched
- payload_part['From'] = Header(core_obj.email_id, 'utf-8').encode()
- else:
- # Plain email address, construct a structured one based on the name:
- sender_name = Header(core_obj.name, 'utf-8').encode()
- payload_part['From'] = sender_name + " <" + core_obj.email_id + ">"
- payload_part['Organization'] = tools.ustr(core_obj.user.company_id.name)
- payload_part['Date'] = formatdate()
- addresses_l = extract_emails_from_dict(addresses)
- if addresses_l['To']:
- payload_part['To'] = u','.join(addresses_l['To'])
- if addresses_l['CC']:
- payload_part['CC'] = u','.join(addresses_l['CC'])
- if addresses_l['Reply-To']:
- payload_part['Reply-To'] = addresses_l['Reply-To'][0]
- if message_id:
- payload_part['Message-ID'] = message_id
- if body.get('text', False):
- temp_body_text = body.get('text', '')
- l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', ''))
- 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 in ('text', 'mixed', 'alternative'):
- body_text = body.get('text', u'')
- body_text = tools.ustr(body_text)
- text_part.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8'))
- if core_obj.send_pref in ('html', 'mixed', 'alternative'):
- html_body = body.get('html', u'')
- if len(html_body) == 0 or html_body == u'':
- html_body = body.get('text', u'<Empty Message>
').replace('\n', ' ').replace('\r', ' ')
- html_body = tools.ustr(html_body)
- text_part.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8'))
-
- #Now add attachments if any, wrapping into a container multipart/mixed if needed
- if payload:
- for file in payload:
- part = MIMEBase('application', "octet-stream")
- part.set_payload(base64.decodestring(payload[file]))
- part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
- Encoders.encode_base64(part)
- payload_part.attach(part)
- except Exception, error:
- logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:MIME Error\nDescription: %s") % (id, tools.ustr(error)))
- return {'error_msg': _("Server Send Error\nDescription: %s")%error}
- try:
- serv.sendmail(payload_part['From'], addresses_l['all-recipients'], payload_part.as_string())
- except Exception, error:
- logging.getLogger('email_template').error(_("Mail from Account %s failed. Probable Reason: Server Send Error\n Description: %s"), id, tools.ustr(error), exc_info=True)
- return {'error_msg': _("Server Send Error\nDescription: %s") % tools.ustr(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)
- return {'nodestroy':True,'error_msg': _("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
- 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')
- except Exception, e:
- logger.notifyChannel(
- _("Email Template"),
- netsvc.LOG_WARNING,
- _(
- "Datetime Extraction failed.Date:%s \
- \tError:%s") % (
- time_as_string,
- tools.ustr(e))
- )
- return date_as_date
-
- def send_receive(self, cr, uid, ids, context=None):
- 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
deleted file mode 100644
index 09f9062d187..00000000000
--- a/addons/email_template/email_template_account_view.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-
-
-
-
-
-
-
-
-
- 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
-
- {'search_default_draft': 1, 'search_default_my': 1}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/email_template/email_template_mailbox.py b/addons/email_template/email_template_mailbox.py
deleted file mode 100644
index 4486cb5451e..00000000000
--- a/addons/email_template/email_template_mailbox.py
+++ /dev/null
@@ -1,214 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2004-2010 OpenERP SA ()
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
-#
-##############################################################################
-
-from osv import osv, fields
-import time
-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 OpenERP Scheduler
- to periodically send emails
- """
- try:
- self.send_all_mail(cursor, user, context=context)
- except Exception, e:
- LOGGER.notifyChannel(
- "Email Template",
- netsvc.LOG_ERROR,
- _("Error sending mail: %s") % 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):
- #previous method to send email (link with email account can be found at the revision 4172 and below
- result = True
- attachment_pool = self.pool.get('ir.attachment')
- for id in (ids or []):
- 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 = attachment_pool.browse(cr, uid, attid, context)#,['datas_fname','datas'])
- payload[attachment.datas_fname] = attachment.datas
- result = account_obj.send_mail(cr, uid,
- [values['account_id'][0]],
- {'To':values.get('email_to') or u'',
- 'CC':values.get('email_cc') or u'',
- 'BCC':values.get('email_bcc') or u'',
- 'Reply-To':values.get('reply_to') or u''},
- values['subject'] or u'',
- {'text':values.get('body_text') or u'', 'html':values.get('body_html') or u''},
- payload=payload,
- message_id=values['message_id'],
- context=context)
- if result == True:
- account = account_obj.browse(cr, uid, values['account_id'][0], context=context)
- if account.auto_delete:
- self.write(cr, uid, id, {'folder': 'trash'}, context=context)
- self.unlink(cr, uid, [id], context=context)
- # Remove attachments for this mail
- attachment_pool.unlink(cr, uid, values['attachments_ids'], context=context)
- return result
- else:
- 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:
- error = result['error_msg']
- self.historise(cr, uid, [id], error, context)
-
- except Exception, error:
- logger = netsvc.Logger()
- logger.notifyChannel("email-template", netsvc.LOG_ERROR, _("Sending of Mail %s failed. Probable Reason:Could not login to server\nError: %s") % (id, error))
- self.historise(cr, uid, [id], error, context)
- self.write(cr, uid, id, {'state':'na'}, context)
- return result
-
- 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(
- 'Recipient (To)',
- size=250,),
- 'email_cc':fields.char(
- 'CC',
- size=250),
- 'email_bcc':fields.char(
- 'BCC',
- size=250),
- 'reply_to':fields.char(
- 'Reply-To',
- size=250),
- 'message_id':fields.char(
- 'Message-ID',
- size=250),
- 'subject':fields.char(
- 'Subject',
- size=200,),
- 'body_text':fields.text(
- 'Standard Body (Text)'),
- 'body_html':fields.text(
- 'Body (Rich Text Clients 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', help="Date on which Email Sent or Received"),
- 'history':fields.text(
- 'History',
- readonly=True,
- store=True)
- }
-
- _defaults = {
- 'state': lambda * a: 'na',
- 'folder': lambda * a: 'outbox',
- }
-
- def unlink(self, cr, uid, ids, context=None):
- """
- It just changes the folder of the item to "Trash", if it is no in Trash folder yet,
- or completely deletes it if it is already in Trash.
- """
- to_update = []
- to_remove = []
- for mail in self.browse(cr, uid, ids, context=context):
- if mail.folder == 'trash':
- to_remove.append(mail.id)
- else:
- to_update.append(mail.id)
- # Changes the folder to trash
- self.write(cr, uid, to_update, {'folder': 'trash'}, context=context)
- return super(email_template_mailbox, self).unlink(cr, uid, to_remove, context=context)
-
-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
deleted file mode 100644
index 0d1b3b0a2bf..00000000000
--- a/addons/email_template/email_template_mailbox_view.xml
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
-
- email_template.mailbox.form
- email_template.mailbox
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- email_template.mailbox.tree
- email_template.mailbox
- tree
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- email_template.mailbox.search
- email_template.mailbox
- search
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Emails
- email_template.mailbox
- form
- tree,form
-
- {'search_default_outbox': 1}
- An email template is an email document that will be sent as part of a marketing campaign. You can personalize it according to specific customer profile fields, so that a partner name or other partner related information may be inserted automatically.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml
index 7d2a5f7ad5c..b4f76a03c6c 100644
--- a/addons/email_template/email_template_view.xml
+++ b/addons/email_template/email_template_view.xml
@@ -1,141 +1,99 @@
-
-
-
- email_template.preview.form
- email_template.preview
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Template Preview
- email_template.preview
- email_template.preview
- ir.actions.act_window
- form
- form
-
- new
- {'template_id':active_id}
-
-
email.template.form
email.template
form
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
-
+
-
+ domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
+ on_change="onchange_sub_model_object_value_field(model_object_field)"
+ colspan="4"/>
+
+ on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
-
+ on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)" />
+
+ type="action" colspan="4" target="new" icon="gtk-zoom-fit" context="{'template_id':active_id}"/>
-
-
-
-
-
+
+
+
+
+
-
+
+
+
+
-
-
-
-
+
-
+ domain="[('model','=',model)]"/>
+
+
+
+
@@ -150,16 +108,13 @@
email.template
tree
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
@@ -173,26 +128,26 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
+
- Email Templates
+ Templates
email.template
form
form,tree
@@ -200,16 +155,9 @@
-
+
+
-
-
-
-
-
-
-
diff --git a/addons/email_template/email_template_workflow.xml b/addons/email_template/email_template_workflow.xml
deleted file mode 100644
index 84a58815b5e..00000000000
--- a/addons/email_template/email_template_workflow.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
- 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/security/email_template_security.xml b/addons/email_template/security/email_template_security.xml
deleted file mode 100644
index e3f3401217c..00000000000
--- a/addons/email_template/security/email_template_security.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/addons/email_template/security/ir.model.access.csv b/addons/email_template/security/ir.model.access.csv
index 17859d6bb9d..f5f61240a96 100644
--- a/addons/email_template/security/ir.model.access.csv
+++ b/addons/email_template/security/ir.model.access.csv
@@ -1,10 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"access_email_template_account","email_template.account","model_email_template_account","marketing.group_marketing_user",1,0,0,0
-"access_email_template","email.template","model_email_template","marketing.group_marketing_user",1,0,0,0
-"access_email_template_mailbox","email_template.mailbox","model_email_template_mailbox","marketing.group_marketing_user",1,1,1,1
-"access_email_template_account_system","email_template.account system","model_email_template_account","base.group_system",1,1,1,1
+"access_email_template","email.template","model_email_template",,1,0,0,0
"access_email_template_system","email.template system","model_email_template","base.group_system",1,1,1,1
-"access_email_template_mailbox_system","email_template.mailbox system","model_email_template_mailbox","base.group_system",1,0,0,0
-"access_email_template_account_manager","email_template.account","model_email_template_account","marketing.group_marketing_manager",1,1,1,1
-"access_email_template_manager","email.template","model_email_template","marketing.group_marketing_manager",1,1,1,1
-"access_email_template_mailbox_manager","email_template.mailbox","model_email_template_mailbox","marketing.group_marketing_manager",1,1,1,1
+"access_email_template_manager","email.template","model_email_template",,1,1,1,1
diff --git a/addons/email_template/wizard/__init__.py b/addons/email_template/wizard/__init__.py
index 1641152490e..b9ac29ec4c2 100644
--- a/addons/email_template/wizard/__init__.py
+++ b/addons/email_template/wizard/__init__.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,5 +19,6 @@
# along with this program. If not, see
#
##############################################################################
+import email_template_preview
+import mail_compose_message
-import email_template_send_wizard
diff --git a/addons/email_template/wizard/email_compose_message_view.xml b/addons/email_template/wizard/email_compose_message_view.xml
new file mode 100644
index 00000000000..cf233b20682
--- /dev/null
+++ b/addons/email_template/wizard/email_compose_message_view.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ mail.compose.message.form
+ mail.compose.message
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/email_template/wizard/email_template_preview.py b/addons/email_template/wizard/email_template_preview.py
new file mode 100644
index 00000000000..650b714866d
--- /dev/null
+++ b/addons/email_template/wizard/email_template_preview.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2009 Sharoon Thomas
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from osv import osv, fields
+
+class email_template_preview(osv.osv_memory):
+ _inherit = "email.template"
+ _name = "email_template.preview"
+ _description = "Email Template Preview"
+ _rec_name = "subject"
+
+ def _get_records(self, cr, uid, context=None):
+ """
+ Return Records of particular Email Template's Model
+ """
+ if context is None:
+ context = {}
+
+ template_id = context.get('template_id', False)
+ if not template_id:
+ return []
+ email_template = self.pool.get('email.template')
+ template = email_template.browse(cr, uid, int(template_id), context=context)
+ template_object = template.model_id
+ model = self.pool.get(template_object.model)
+ record_ids = model.search(cr, uid, [], 0, 10, 'id', context=context)
+ default_id = context.get('default_res_id')
+
+ if default_id and default_id not in record_ids:
+ record_ids.insert(0, default_id)
+
+ return model.name_get(cr, uid, record_ids, context)
+
+
+ def default_get(self, cr, uid, fields, context=None):
+ if context is None:
+ context = {}
+ result = super(email_template_preview, self).default_get(cr, uid, fields, context=context)
+
+ email_template = self.pool.get('email.template')
+ template_id = context.get('template_id')
+ if 'res_id' in fields and not result.get('res_id'):
+ records = self._get_records(cr, uid, context=context)
+ result['res_id'] = records and records[0][0] or False # select first record as a Default
+ if template_id and 'model_id' in fields and not result.get('model_id'):
+ result['model_id'] = email_template.read(cr, uid, int(template_id), ['model_id'], context).get('model_id', False)
+ return result
+
+ _columns = {
+ 'res_id':fields.selection(_get_records, 'Sample Document'),
+ }
+
+ def on_change_res_id(self, cr, uid, ids, res_id, context=None):
+ if not res_id:
+ return {}
+ vals = {}
+ email_template = self.pool.get('email.template')
+ template_id = context and context.get('template_id')
+ template = email_template.get_email_template(cr, uid, template_id=template_id, record_id=res_id, context=context)
+ model = template.model
+ vals['email_to'] = self.render_template(cr, uid, template.email_to, model, res_id, context)
+ vals['email_cc'] = self.render_template(cr, uid, template.email_cc, model, res_id, context)
+ vals['email_bcc'] = self.render_template(cr, uid, template.email_bcc, model, res_id, context)
+ vals['reply_to'] = self.render_template(cr, uid, template.reply_to, model, res_id, context)
+ vals['subject'] = self.render_template(cr, uid, template.subject, model, res_id, context)
+ description = self.render_template(cr, uid, template.body_text, model, res_id, context) or ''
+ if template.user_signature:
+ signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
+ description += '\n' + signature
+ vals['body_text'] = description
+ vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
+ return {'value': vals}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/wizard/email_template_preview_view.xml b/addons/email_template/wizard/email_template_preview_view.xml
new file mode 100644
index 00000000000..4aab57396ad
--- /dev/null
+++ b/addons/email_template/wizard/email_template_preview_view.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+ email_template.preview.form
+ email_template.preview
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template Preview
+ email_template.preview
+ email_template.preview
+ ir.actions.act_window
+ form
+ form
+
+ new
+ {'template_id':active_id}
+
+
+
+
diff --git a/addons/email_template/wizard/email_template_send_wizard.py b/addons/email_template/wizard/email_template_send_wizard.py
deleted file mode 100644
index 4f6ab7af381..00000000000
--- a/addons/email_template/wizard/email_template_send_wizard.py
+++ /dev/null
@@ -1,279 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
-#
-##############################################################################
-
-from osv import osv, fields
-from mako.template import Template
-from mako import exceptions
-import netsvc
-import base64
-from tools.translate import _
-import tools
-from email_template.email_template import get_value
-
-
-## FIXME: this wizard duplicates a lot of features of the email template preview,
-## one of the 2 should inherit from the other!
-
-class email_template_send_wizard(osv.osv_memory):
- _name = 'email_template.send.wizard'
- _description = 'This is the wizard for sending mail'
- _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.from_account:
- return [(template.from_account.id, '%s (%s)' % (template.from_account.name, template.from_account.email_id))]
- else:
- account_id = self.pool.get('email_template.account').search(cr,uid,[('company','=','no'),('user','=',uid)], context=context)
- if account_id:
- account = self.pool.get('email_template.account').browse(cr,uid,account_id, context)
- return [(r.id,r.name + " (" + r.email_id + ")") for r in account]
- else:
- logger.notifyChannel(_("email-template"), netsvc.LOG_ERROR, _("No personal email accounts are configured for you. \nEither ask admin to enforce an account for this template or get yourself a personal email account."))
- raise osv.except_osv(_("Missing mail account"),_("No personal email accounts are configured for you. \nEither ask admin to enforce an account for this template or get yourself a personal email account."))
-
- def get_value(self, cursor, user, template, message, context=None, id=None):
- """Gets the value of the message parsed with the content of object id (or the first 'src_rec_ids' if id is not given)"""
- 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):
- if context is None:
- context = {}
- 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,),
- 'reply_to':fields.char('Reply-To',
- size=250,
- help="The address recipients should reply to,"
- " if different from the From address."
- " Placeholders can be used here."),
- 'message_id':fields.char('Message-ID',
- size=250,
- help="The Message-ID header value, if you need to"
- "specify it, for example to automatically recognize the replies later."
- " Placeholders can be used here."),
- 'subject':fields.char('Subject',size=200),
- 'body_text':fields.text('Body',),
- 'body_html':fields.text('Body',),
- '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'),
- }
-
- #FIXME: probably better by overriding default_get directly
- _defaults = {
- 'state': lambda self,cr,uid,ctx: len(ctx['src_rec_ids']) > 1 and 'multi' or 'single',
- 'rel_model': lambda self,cr,uid,ctx: self.pool.get('ir.model').search(cr,uid,[('model','=',ctx['src_model'])],context=ctx)[0],
- '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,
- 'reply_to': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'reply_to', ctx),
- 'reply_to': lambda self,cr,uid,ctx: self._get_template_value(cr, uid, 'reply_to', ctx),
- 'requested':lambda self,cr,uid,ctx: len(ctx['src_rec_ids']),
- 'full_success': False,
- 'attachment_ids': [],
- }
-
- def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
- if context is None:
- context = {}
- result = super(email_template_send_wizard, self).fields_get(cr, uid, fields, context, write_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=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("email-template", netsvc.LOG_INFO, _("Emails for multiple items saved in outbox."))
- self.write(cr, uid, ids, {
- 'generated':len(mail_ids),
- 'state':'done'
- }, context)
- else:
- raise osv.except_osv(_("Email Template"),_("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
-
- if context is None:
- context = {}
- mail_ids = []
- template = self._get_template(cr, uid, context)
- for id in context['src_rec_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
deleted file mode 100644
index a5ad037aee6..00000000000
--- a/addons/email_template/wizard/email_template_send_wizard_view.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
- email_template.send.wizard.form
- email_template.send.wizard
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..60dd022c1b9
--- /dev/null
+++ b/addons/email_template/wizard/mail_compose_message.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import base64
+
+from osv import osv
+from osv import fields
+from tools.translate import _
+import tools
+
+
+def _reopen(self,res_id,model):
+ return {'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'view_type': 'form',
+ 'res_id': res_id,
+ 'res_model': self._name,
+ 'target': 'new',
+
+ # save original model in context, otherwise
+ # it will be lost on the action's context switch
+ 'mail.compose.target.model': model,
+ }
+
+class mail_compose_message(osv.osv_memory):
+ _inherit = 'mail.compose.message'
+
+ def _get_templates(self, cr, uid, context=None):
+ """
+ Return Email Template of particular Model.
+ """
+ if context is None:
+ context = {}
+ record_ids = []
+ email_template= self.pool.get('email.template')
+ model = False
+ if context.get('message_id'):
+ mail_message = self.pool.get('mail.message')
+ message_data = mail_message.browse(cr, uid, int(context.get('message_id')), context)
+ model = message_data.model
+ elif context.get('mail.compose.target.model') or context.get('active_model'):
+ model = context.get('mail.compose.target.model', context.get('active_model'))
+ if model:
+ record_ids = email_template.search(cr, uid, [('model', '=', model)])
+ return email_template.name_get(cr, uid, record_ids, context) + [(False,'')]
+ return []
+
+ _columns = {
+ 'use_template': fields.boolean('Use Template'),
+ 'template_id': fields.selection(_get_templates, 'Template'),
+ }
+
+ def on_change_template(self, cr, uid, ids, use_template, template_id, email_from=None, email_to=None, context=None):
+ if context is None:
+ context = {}
+ values = {}
+ if template_id:
+ res_id = context.get('active_id', False)
+ if context.get('mail.compose.message.mode') == 'mass_mail':
+ # use the original template values - to be rendered when actually sent
+ # by super.send_mail()
+ values = self.pool.get('email.template').read(cr, uid, template_id, self.fields_get_keys(cr, uid), context)
+ else:
+ # render the mail as one-shot
+ values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
+ # retrofit generated attachments in the expected field format
+ if values['attachments']:
+ attachment = values.pop('attachments')
+ attachment_obj = self.pool.get('ir.attachment')
+ att_ids = []
+ for fname, fcontent in attachment.iteritems():
+ data_attach = {
+ 'name': fname,
+ 'datas': base64.b64encode(fcontent),
+ 'datas_fname': fname,
+ 'description': fname,
+ 'res_model' : self._name,
+ 'res_id' : ids[0] if ids else False
+ }
+ att_ids.append(attachment_obj.create(cr, uid, data_attach))
+ values['attachment_ids'] = att_ids
+ else:
+ # restore defaults
+ values = self.default_get(cr, uid, self.fields_get_keys(cr, uid), context)
+ values.update(use_template=use_template, template_id=template_id)
+
+ return {'value': values}
+
+
+ def template_toggle(self, cr, uid, ids, context=None):
+ for record in self.browse(cr, uid, ids, context=context):
+ had_template = record.use_template
+ record.write({'use_template': not(record.use_template)})
+ if had_template:
+ # equivalent to choosing an empty template
+ onchange_defaults = self.on_change_template(cr, uid, record.id, record.use_template,
+ False, email_from=record.email_from,
+ email_to=record.email_to, context=context)
+ record.write(onchange_defaults['value'])
+ return _reopen(self, record.id, record.model)
+
+ def save_as_template(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ email_template = self.pool.get('email.template')
+ model_pool = self.pool.get('ir.model')
+ for record in self.browse(cr, uid, ids, context=context):
+ model = record.model or context.get('active_model')
+ model_ids = model_pool.search(cr, uid, [('model', '=', model)])
+ model_id = model_ids and model_ids[0] or False
+ model_name = ''
+ if model_id:
+ model_name = model_pool.browse(cr, uid, model_id, context=context).name
+ template_name = "%s: %s" % (model_name, tools.ustr(record.subject))
+ values = {
+ 'name': template_name,
+ 'email_from': record.email_from or False,
+ 'subject': record.subject or False,
+ 'body_text': record.body_text or False,
+ 'email_to': record.email_to or False,
+ 'email_cc': record.email_cc or False,
+ 'email_bcc': record.email_bcc or False,
+ 'reply_to': record.reply_to or False,
+ 'model_id': model_id or False,
+ 'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
+ }
+ template_id = email_template.create(cr, uid, values, context=context)
+ record.write({'template_id': template_id,
+ 'use_template': True})
+
+ # _reopen same wizard screen with new template preselected
+ return _reopen(self, record.id, model)
+
+ # override the basic implementation
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/event/__openerp__.py b/addons/event/__openerp__.py
index 680cf1e6572..9a9acaf0fb3 100644
--- a/addons/event/__openerp__.py
+++ b/addons/event/__openerp__.py
@@ -29,7 +29,7 @@
Organization and management of Events.
======================================
-This module allow you
+This module allows you
* to manage your events and their registrations
* to use emails to automatically confirm and send acknowledgements for any registration to an event
* ...
@@ -39,7 +39,7 @@ Note that:
Association / Configuration / Types of Events
""",
'author': 'OpenERP SA',
- 'depends': ['crm', 'base_contact', 'account', 'marketing'],
+ 'depends': ['crm', 'base_contact', 'account', 'marketing', 'mail'],
'init_xml': [],
'update_xml': [
'security/event_security.xml',
diff --git a/addons/event/event.py b/addons/event/event.py
index 0fc28c7c193..ba802ef1ffa 100644
--- a/addons/event/event.py
+++ b/addons/event/event.py
@@ -24,8 +24,11 @@ import time
from crm import crm
from osv import fields, osv
from tools.translate import _
-import tools
import decimal_precision as dp
+from crm import wizard
+
+
+wizard.mail_compose_message.SUPPORTED_MODELS.append('event.registration')
class event_type(osv.osv):
""" Event Type """
@@ -280,7 +283,7 @@ class event_registration(osv.osv):
"""Event Registration"""
_name= 'event.registration'
_description = __doc__
- _inherit = 'mailgate.thread'
+ _inherit = 'mail.thread'
def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
cur_obj = self.pool.get('res.currency')
@@ -313,8 +316,8 @@ class event_registration(osv.osv):
'create_date': fields.datetime('Creation Date', readonly=True),
'write_date': fields.datetime('Write Date', readonly=True),
'description': fields.text('Description', states={'done': [('readonly', True)]}),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
- 'log_ids': fields.one2many('mailgate.message', 'res_id', 'Logs', domain=[('history', '=', False),('model','=',_name)]),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ 'log_ids': fields.one2many('mail.message', 'res_id', 'Logs', domain=[('email_from', '=', False),('model','=',_name)]),
'date_deadline': fields.related('event_id','date_end', type='datetime', string="End Date", readonly=True),
'date': fields.related('event_id', 'date_begin', type='datetime', string="Start Date", readonly=True),
'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
@@ -354,7 +357,7 @@ class event_registration(osv.osv):
})
inv_id = inv_pool.create(cr, uid, val_invoice['value'], context=context)
inv_pool.button_compute(cr, uid, [inv_id])
- self.history(cr, uid, [reg], _('Invoiced'))
+ self.message_append(cr, uid, [reg], _('Invoiced'))
return inv_id
def copy(self, cr, uid, id, default=None, context=None):
@@ -429,7 +432,7 @@ class event_registration(osv.osv):
"""
res = self.write(cr, uid, ids, {'state': 'open'}, context=context)
self.mail_user(cr, uid, ids)
- self.history(cr, uid, ids, _('Open'))
+ self.message_append(cr, uid, ids, _('Open'))
return res
def do_close(self, cr, uid, ids, context=None):
@@ -443,7 +446,7 @@ class event_registration(osv.osv):
if invoice_id:
values['invoice_id'] = invoice_id
res = self.write(cr, uid, ids, values)
- self.history(cr, uid, ids, msg)
+ self.message_append(cr, uid, ids, msg)
return res
def check_confirm(self, cr, uid, ids, context=None):
@@ -514,42 +517,38 @@ class event_registration(osv.osv):
"""This Function Cancel Event Registration.
"""
registrations = self.browse(cr, uid, ids)
- self.history(cr, uid, registrations, _('Cancel'))
+ self.message_append(cr, uid, registrations, _('Cancel'))
return self.write(cr, uid, ids, {'state': 'cancel'})
def mail_user(self, cr, uid, ids, confirm=False, context=None):
"""
Send email to user
"""
-
- for regestration in self.browse(cr, uid, ids, context=context):
- src = regestration.event_id.reply_to or False
+ mail_message = self.pool.get('mail.message')
+ for registration in self.browse(cr, uid, ids, context=context):
+ src = registration.event_id.reply_to or False
email_to = []
email_cc = []
- if regestration.email_from:
- email_to = regestration.email_from
- if regestration.email_cc:
- email_cc += [regestration.email_cc]
+ if registration.email_from:
+ email_to = [registration.email_from]
+ if registration.email_cc:
+ email_cc += [registration.email_cc]
if not (email_to or email_cc):
continue
subject = ""
body = ""
if confirm:
- subject = _('Auto Confirmation: [%s] %s') %(regestration.id, regestration.name)
- body = regestration.event_id.mail_confirm
- elif regestration.event_id.mail_auto_confirm or regestration.event_id.mail_auto_registr:
- if regestration.event_id.state in ['draft', 'fixed', 'open', 'confirm', 'running'] and regestration.event_id.mail_auto_registr:
- subject = _('Auto Registration: [%s] %s') %(regestration.id, regestration.name)
- body = regestration.event_id.mail_registr
- if (regestration.event_id.state in ['confirm', 'running']) and regestration.event_id.mail_auto_confirm:
- subject = _('Auto Confirmation: [%s] %s') %(regestration.id, regestration.name)
- body = regestration.event_id.mail_confirm
+ subject = _('Auto Confirmation: [%s] %s') %(registration.id, registration.name)
+ body = registration.event_id.mail_confirm
+ elif registration.event_id.mail_auto_confirm or registration.event_id.mail_auto_registr:
+ if registration.event_id.state in ['draft', 'fixed', 'open', 'confirm', 'running'] and registration.event_id.mail_auto_registr:
+ subject = _('Auto Registration: [%s] %s') %(registration.id, registration.name)
+ body = registration.event_id.mail_registr
+ if (registration.event_id.state in ['confirm', 'running']) and registration.event_id.mail_auto_confirm:
+ subject = _('Auto Confirmation: [%s] %s') %(registration.id, registration.name)
+ body = registration.event_id.mail_confirm
if subject or body:
- tools.email_send(src, email_to, subject, body, email_cc=email_cc, openobject_id=regestration.id)
- self.history(cr, uid, [regestration], subject, history = True, \
- email=email_to, details=body, \
- subject=subject, email_from=src, \
- email_cc=', '.join(email_cc))
+ mail_message.schedule_with_attach(cr, uid, src, email_to, subject, body, model='event.registration', email_cc=email_cc, res_id=registration.id)
return True
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml
index 6eafd851559..41c328d8908 100644
--- a/addons/event/event_view.xml
+++ b/addons/event/event_view.xml
@@ -391,12 +391,12 @@
-
-
+
+
@@ -404,20 +404,18 @@
-
-
+
-
-
-
+
+
+
-
+
@@ -427,14 +425,13 @@
-
-
+
diff --git a/addons/fetchmail/__openerp__.py b/addons/fetchmail/__openerp__.py
index d2694fdb958..986c3bc12a1 100644
--- a/addons/fetchmail/__openerp__.py
+++ b/addons/fetchmail/__openerp__.py
@@ -21,19 +21,42 @@
##############################################################################
{
- "name" : "Fetchmail Server",
+ "name" : "Automated Email Retriever",
"version" : "1.0",
- "depends" : ["base", 'mail_gateway'],
+ "depends" : ["base", 'mail'],
"author" : "OpenERP SA",
"category": 'Tools',
"description": """
-Fetch email from POP / IMAP servers.
-====================================
+Retrieve incoming email on POP / IMAP servers
+=============================================
- * Supports SSL
- * Integrated with all modules
- * Automatically receive email
- * Email-based record operations (Add, Update)
+Enter the parameters of your POP/IMAP account(s), and any incoming
+emails on these accounts will be automatically downloaded into your OpenERP
+system. All POP3/IMAP-compatible servers are supported, included those
+that require an encrypted SSL/TLS connection.
+
+This can be used to easily create email-based workflows for many
+email-enabled OpenERP documents, such as:
+
+ * CRM Leads/Opportunities
+ * CRM Claims
+ * Project Issues
+ * Project Tasks
+ * Human Resource Recruitments (Applicants)
+ * etc.
+
+Just install the relevant application, and you can assign any of
+these document types (Leads, Project Issues, etc.) to your incoming
+email accounts. New emails will automatically spawn new documents
+of the chosen type, so it's a snap to create a mailbox-to-OpenERP
+integration. Even better: these documents directly act as mini
+conversations synchronized by email. You can reply from within
+OpenERP, and the answers will automatically be collected when
+they come back, and attached to the same *conversation* document.
+
+For more specific needs, you may also assign custom-defined actions
+(technically: Server Actions) to be triggered for each incoming
+mail.
""",
'website': 'http://www.openerp.com',
'init_xml': [],
diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py
index 9817f0d6633..8aaff197d47 100644
--- a/addons/fetchmail/fetchmail.py
+++ b/addons/fetchmail/fetchmail.py
@@ -19,8 +19,8 @@
#
##############################################################################
+import logging
import time
-
from imaplib import IMAP4
from imaplib import IMAP4_SSL
from poplib import POP3
@@ -29,222 +29,200 @@ from poplib import POP3_SSL
import netsvc
from osv import osv, fields
import tools
+from tools.translate import _
-logger = netsvc.Logger()
+logger = logging.getLogger('fetchmail')
-
-class email_server(osv.osv):
-
- _name = 'email.server'
+class fetchmail_server(osv.osv):
+ """Incoming POP/IMAP mail server account"""
+ _name = 'fetchmail.server'
_description = "POP/IMAP Server"
+ _order = 'priority'
_columns = {
'name':fields.char('Name', size=256, required=True, readonly=False),
'active':fields.boolean('Active', required=False),
'state':fields.selection([
('draft', 'Not Confirmed'),
- ('waiting', 'Waiting for Verification'),
('done', 'Confirmed'),
], 'State', select=True, readonly=True),
- 'server' : fields.char('Server', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}),
+ 'server' : fields.char('Server Name', size=256, required=True, readonly=True, help="Hostname or IP of the mail server", states={'draft':[('readonly', False)]}),
'port' : fields.integer('Port', required=True, readonly=True, states={'draft':[('readonly', False)]}),
'type':fields.selection([
('pop', 'POP Server'),
('imap', 'IMAP Server'),
- ], 'Server Type', select=True, readonly=False),
- 'is_ssl':fields.boolean('SSL ?', required=False),
- 'attach':fields.boolean('Add Attachments ?', required=False, help="Fetches mail with attachments if true."),
- 'date': fields.date('Date', readonly=True, states={'draft':[('readonly', False)]}),
- 'user' : fields.char('User Name', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}),
- 'password' : fields.char('Password', size=1024, invisible=True, required=True, readonly=True, states={'draft':[('readonly', False)]}),
+ ], 'Server Type', select=True, required=True, readonly=False),
+ 'is_ssl':fields.boolean('SSL/TLS', help="Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"),
+ 'attach':fields.boolean('Keep Attachments', help="Whether attachments should be downloaded. "
+ "If not enabled, incoming emails will be stripped of any attachments before being processed"),
+ 'original':fields.boolean('Keep Original', help="Whether a full original copy of each email should be kept for reference"
+ "and attached to each processed message. This will usually double the size of your message database."),
+ 'date': fields.datetime('Last Fetch Date', readonly=True),
+ 'user' : fields.char('Username', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}),
+ 'password' : fields.char('Password', size=1024, required=True, readonly=True, states={'draft':[('readonly', False)]}),
'note': fields.text('Description'),
- 'action_id':fields.many2one('ir.actions.server', 'Email Server Action', required=False, domain="[('state','=','email')]", help="An Email Server Action. It will be run whenever an e-mail is fetched from server."),
- 'object_id': fields.many2one('ir.model', "Object To Create", required=True, help="Whenever an email arrives, it automatically creates the object of this type with all the information attached."),
- 'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Priority between 0 to 10, select define the order of Processing"),
- 'user_id':fields.many2one('res.users', 'User', required=False, help="This is the user that runs the cron"),
- 'message_ids': fields.one2many('mailgate.message', 'server_id', 'Messages', readonly=True),
+ 'action_id':fields.many2one('ir.actions.server', 'Server Action', help="Optional custom server action to trigger for each incoming mail, "
+ "on the record that was created or updated by this mail"),
+ 'object_id': fields.many2one('ir.model', "Target document type", required=True, help="Process each incoming mail as part of a conversation "
+ "corresponding to this document type. This will create "
+ "new documents for new conversations, or attach follow-up "
+ "emails to the existing conversations (documents)."),
+ 'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, "
+ "lower values mean higher priority"),
+ 'message_ids': fields.one2many('mail.message', 'fetchmail_server_id', 'Messages', readonly=True),
}
_defaults = {
- 'state': lambda *a: "draft",
- 'active': lambda *a: True,
- 'priority': lambda *a: 5,
- 'date': lambda *a: time.strftime('%Y-%m-%d'),
- 'user_id': lambda self, cr, uid, ctx: uid,
+ 'state': "draft",
+ 'active': True,
+ 'priority': 5,
+ 'attach': True,
}
- def check_duplicate(self, cr, uid, ids, context=None):
- # RFC *-* Why this limitation? why not in SQL constraint?
- vals = self.read(cr, uid, ids, ['user', 'password'], context=context)[0]
- cr.execute("select count(id) from email_server where user=%s and password=%s", (vals['user'], vals['password']))
- res = cr.fetchone()
- if res:
- if res[0] > 1:
- return False
- return True
-
- def check_model(self, cr, uid, ids, context = None):
- if context is None:
- context = {}
- current_rec = self.read(cr, uid, ids, context)
- if current_rec:
- current_rec = current_rec[0]
- model_name = self.pool.get('ir.model').browse(cr, uid, current_rec.get('object_id')[0]).model
- model = self.pool.get(model_name)
- if hasattr(model, 'message_new'):
- return True
- return False
-
- _constraints = [
- (check_duplicate, 'Warning! Can\'t have duplicate server configuration!', ['user', 'password']),
- (check_model, 'Warning! Record for selected Model can not be created\nPlease choose valid Model', ['object_id'])
- ]
-
def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False):
port = 0
if server_type == 'pop':
port = ssl and 995 or 110
elif server_type == 'imap':
port = ssl and 993 or 143
-
return {'value':{'port':port}}
def set_draft(self, cr, uid, ids, context=None):
self.write(cr, uid, ids , {'state':'draft'})
return True
-
+
+ def connect(self, cr, uid, server_id, context=None):
+ if isinstance(server_id, (list,tuple)):
+ server_id = server_id[0]
+ server = self.browse(cr, uid, server_id, context)
+ if server.type == 'imap':
+ if server.is_ssl:
+ connection = IMAP4_SSL(server.server, int(server.port))
+ else:
+ connection = IMAP4(server.server, int(server.port))
+ connection.login(server.user, server.password)
+ elif server.type == 'pop':
+ if server.is_ssl:
+ connection = POP3_SSL(server.server, int(server.port))
+ else:
+ connection = POP3(server.server, int(server.port))
+ #TODO: use this to remove only unread messages
+ #connection.user("recent:"+server.user)
+ connection.user(server.user)
+ connection.pass_(server.password)
+ return connection
+
def button_confirm_login(self, cr, uid, ids, context=None):
if context is None:
context = {}
for server in self.browse(cr, uid, ids, context=context):
- logger.notifyChannel(server.type, netsvc.LOG_INFO, 'fetchmail start checking for new emails on %s' % (server.name))
- context.update({'server_id': server.id, 'server_type': server.type})
try:
- if server.type == 'imap':
- imap_server = None
- if server.is_ssl:
- imap_server = IMAP4_SSL(server.server, int(server.port))
- else:
- imap_server = IMAP4(server.server, int(server.port))
-
- imap_server.login(server.user, server.password)
- ret_server = imap_server
-
- elif server.type == 'pop':
- pop_server = None
- if server.is_ssl:
- pop_server = POP3_SSL(server.server, int(server.port))
- else:
- pop_server = POP3(server.server, int(server.port))
-
- #TODO: use this to remove only unread messages
- #pop_server.user("recent:"+server.user)
- pop_server.user(server.user)
- pop_server.pass_(server.password)
- ret_server = pop_server
-
- self.write(cr, uid, [server.id], {'state':'done'})
- if context.get('get_server',False):
- return ret_server
+ connection = server.connect()
+ server.write({'state':'done'})
except Exception, e:
- logger.notifyChannel(server.type, netsvc.LOG_WARNING, '%s' % (e))
- return True
-
- def button_fetch_mail(self, cr, uid, ids, context=None):
- self.fetch_mail(cr, uid, ids, context=context)
+ logger.exception("Failed to connect to %s server %s", server.type, server.name)
+ raise osv.except_osv(_("Connection test failed!"), _("Here is what we got instead:\n %s") % e)
+ finally:
+ try:
+ if connection:
+ if server.type == 'imap':
+ connection.close()
+ elif server.type == 'pop':
+ connection.quit()
+ except Exception:
+ # ignored, just a consequence of the previous exception
+ pass
return True
def _fetch_mails(self, cr, uid, ids=False, context=None):
if not ids:
- ids = self.search(cr, uid, [])
+ ids = self.search(cr, uid, [('state','=','done')])
return self.fetch_mail(cr, uid, ids, context=context)
def fetch_mail(self, cr, uid, ids, context=None):
+ """WARNING: meant for cron usage only - will commit() after each email!"""
if context is None:
context = {}
- email_tool = self.pool.get('email.server.tools')
+ mail_thread = self.pool.get('mail.thread')
action_pool = self.pool.get('ir.actions.server')
- context.update({'get_server': True})
for server in self.browse(cr, uid, ids, context=context):
+ logger.info('start checking for new emails on %s server %s', server.type, server.name)
+ context.update({'fetchmail_server_id': server.id, 'server_type': server.type})
count = 0
- user = server.user_id.id or uid
- try:
- if server.type == 'imap':
- imap_server = self.button_confirm_login(cr, uid, [server.id], context=context)
+ if server.type == 'imap':
+ try:
+ imap_server = server.connect()
imap_server.select()
result, data = imap_server.search(None, '(UNSEEN)')
for num in data[0].split():
result, data = imap_server.fetch(num, '(RFC822)')
- res_id = email_tool.process_email(cr, user, server.object_id.model, data[0][1], attach=server.attach, context=context)
+ res_id = mail_thread.message_process(cr, uid, server.object_id.model, data[0][1],
+ save_original=server.original,
+ strip_attachments=(not server.attach),
+ context=context)
if res_id and server.action_id:
- action_pool.run(cr, user, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
-
+ action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
imap_server.store(num, '+FLAGS', '\\Seen')
+ cr.commit()
count += 1
- logger.notifyChannel('imap', netsvc.LOG_INFO, 'fetchmail fetch/process %s email(s) from %s' % (count, server.name))
-
- imap_server.close()
- imap_server.logout()
- elif server.type == 'pop':
- pop_server = self.button_confirm_login(cr, uid, [server.id], context=context)
- pop_server.list()
+ logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name)
+ except Exception, e:
+ logger.exception("Failed to fetch mail from %s server %s", server.type, server.name)
+ finally:
+ if imap_server:
+ imap_server.close()
+ imap_server.logout()
+ elif server.type == 'pop':
+ try:
+ pop_server = server.connect()
(numMsgs, totalSize) = pop_server.stat()
+ pop_server.list()
for num in range(1, numMsgs + 1):
(header, msges, octets) = pop_server.retr(num)
msg = '\n'.join(msges)
- res_id = email_tool.process_email(cr, user, server.object_id.model, msg, attach=server.attach, context=context)
+ res_id = mail_thread.message_process(cr, uid, server.object_id.model,
+ msg,
+ save_original=server.original,
+ strip_attachments=(not server.attach),
+ context=context)
if res_id and server.action_id:
- action_pool.run(cr, user, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
-
+ action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
pop_server.dele(num)
-
- pop_server.quit()
-
- logger.notifyChannel(server.type, netsvc.LOG_INFO, 'fetchmail fetch %s email(s) from %s' % (numMsgs, server.name))
-
- except Exception, e:
- logger.notifyChannel(server.type, netsvc.LOG_WARNING, '%s' % (tools.ustr(e)))
-
+ cr.commit()
+ logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name)
+ except Exception, e:
+ logger.exception("Failed to fetch mail from %s server %s", server.type, server.name)
+ finally:
+ if pop_server:
+ pop_server.quit()
+ server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
return True
-email_server()
-
-class mailgate_message(osv.osv):
-
- _inherit = "mailgate.message"
-
+class mail_message(osv.osv):
+ _inherit = "mail.message"
_columns = {
- 'server_id': fields.many2one('email.server', "Mail Server", readonly=True, select=True),
- 'server_type':fields.selection([
- ('pop', 'POP Server'),
- ('imap', 'IMAP Server'),
- ], 'Server Type', select=True, readonly=True),
+ 'fetchmail_server_id': fields.many2one('fetchmail.server', "Inbound Mail Server",
+ readonly=True,
+ select=True,
+ oldname='server_id'),
}
- _order = 'id desc'
def create(self, cr, uid, values, context=None):
if context is None:
context={}
- server_id = context.get('server_id',False)
- server_type = context.get('server_type',False)
- if server_id:
- values['server_id'] = server_id
- if server_type:
- values['server_type'] = server_type
- res = super(mailgate_message,self).create(cr, uid, values, context=context)
+ fetchmail_server_id = context.get('fetchmail_server_id')
+ if fetchmail_server_id:
+ values['fetchmail_server_id'] = fetchmail_server_id
+ res = super(mail_message,self).create(cr, uid, values, context=context)
return res
def write(self, cr, uid, ids, values, context=None):
if context is None:
context={}
- server_id = context.get('server_id',False)
- server_type = context.get('server_type',False)
- if server_id:
- values['server_id'] = server_id
- if server_type:
- values['server_type'] = server_type
- res = super(mailgate_message,self).write(cr, uid, ids, values, context=context)
+ fetchmail_server_id = context.get('fetchmail_server_id')
+ if fetchmail_server_id:
+ values['fetchmail_server_id'] = server_id
+ res = super(mail_message,self).write(cr, uid, ids, values, context=context)
return res
-mailgate_message()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/fetchmail/fetchmail_data.xml b/addons/fetchmail/fetchmail_data.xml
index ef40a76489f..d77aadc12de 100644
--- a/addons/fetchmail/fetchmail_data.xml
+++ b/addons/fetchmail/fetchmail_data.xml
@@ -6,10 +6,10 @@
5
minutes
-1
-
-
-
-
+
+ fetchmail.server
+ _fetch_mails
+ ()
diff --git a/addons/fetchmail/fetchmail_installer_view.xml b/addons/fetchmail/fetchmail_installer_view.xml
index 34cfc686a8d..911ad8101da 100644
--- a/addons/fetchmail/fetchmail_installer_view.xml
+++ b/addons/fetchmail/fetchmail_installer_view.xml
@@ -1,9 +1,9 @@
- Setup Your Mail server
+ Setup Incoming Mail Server (fetchmail)
ir.actions.act_window
- email.server
+ fetchmail.server
form
tree,form
diff --git a/addons/fetchmail/fetchmail_view.xml b/addons/fetchmail/fetchmail_view.xml
index 364d9bbe47f..05f62f4b9fb 100644
--- a/addons/fetchmail/fetchmail_view.xml
+++ b/addons/fetchmail/fetchmail_view.xml
@@ -1,44 +1,42 @@
-
+
- email.server.tree
- email.server
+ fetchmail.server.list
+ fetchmail.server
tree
-
+
-
+
-
+
- email.server.form
- email.server
+ fetchmail.server.form
+ fetchmail.server
form
-
+
-
-
-
+
-
-
+
+
@@ -46,35 +44,39 @@
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
+
- email.server.search
- email.server
+ fetchmail.server.search
+ fetchmail.server
search
-
+
@@ -91,68 +93,52 @@
-
+
- Email Servers
- email.server
+ Incoming Mail Servers
+ fetchmail.server
form
tree,form
-
-
-
- mailgate.message.tree
- mailgate.message
- tree
-
-
-
-
-
-
-
-
-
-
- mailgate.message.inherit.search
- mailgate.message
- search
-
-
-
-
-
-
-
-
-
-
-
-
-
- Messages
- mailgate.message
- form
- tree,form
- {'search_default_emails': 1}
-
-
-
-
-
-
+
+ mail.message.list.fetchmail
+ mail.message
+ tree
+
+
+
+
+
+
+
+
+
+ mail.message.inherit.search
+ mail.message
+ search
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/fetchmail/security/ir.model.access.csv b/addons/fetchmail/security/ir.model.access.csv
index 7aa6c84d22b..bc5572de82d 100644
--- a/addons/fetchmail/security/ir.model.access.csv
+++ b/addons/fetchmail/security/ir.model.access.csv
@@ -1,2 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"access_email_server","email.server","model_email_server",,1,1,1,1
+"access_fetchmail_server","fetchmail.server","model_fetchmail_server",,1,0,0,0
+"access_fetchmail_server","fetchmail.server","model_fetchmail_server","base.group_system",1,1,1,1
diff --git a/addons/hr_evaluation/__openerp__.py b/addons/hr_evaluation/__openerp__.py
index d2e0702f067..adf1d9d69c4 100644
--- a/addons/hr_evaluation/__openerp__.py
+++ b/addons/hr_evaluation/__openerp__.py
@@ -43,7 +43,7 @@ in the form of pdf file. Implements a dashboard for My Current Evaluations
"data": [
"security/ir.model.access.csv",
"security/hr_evaluation_security.xml",
- "wizard/hr_evaluation_mail_view.xml",
+# "wizard/hr_evaluation_mail_view.xml",
"hr_evaluation_view.xml",
"report/hr_evaluation_report_view.xml",
"board_hr_evaluation_view.xml",
diff --git a/addons/hr_evaluation/hr_evaluation.py b/addons/hr_evaluation/hr_evaluation.py
index 0b0ebb92261..7634052704c 100644
--- a/addons/hr_evaluation/hr_evaluation.py
+++ b/addons/hr_evaluation/hr_evaluation.py
@@ -24,7 +24,6 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
from dateutil import parser
from osv import fields, osv
-import tools
from tools.translate import _
class hr_evaluation_plan(osv.osv):
@@ -193,6 +192,7 @@ class hr_evaluation(osv.osv):
return {'value': {'plan_id':evaluation_plan_id}}
def button_plan_in_progress(self, cr, uid, ids, context=None):
+ mail_message = self.pool.get('mail.message')
hr_eval_inter_obj = self.pool.get('hr.evaluation.interview')
if context is None:
context = {}
@@ -229,7 +229,7 @@ class hr_evaluation(osv.osv):
sub = phase.email_subject
dest = [child.work_email]
if dest:
- tools.email_send(evaluation.employee_id.work_email, dest, sub, body)
+ mail_message.schedule_with_attach(cr, uid, evaluation.employee_id.work_email, dest, sub, body, context=context)
self.write(cr, uid, ids, {'state':'wait'}, context=context)
return True
diff --git a/addons/hr_evaluation/hr_evaluation_view.xml b/addons/hr_evaluation/hr_evaluation_view.xml
index 84d2569320c..869d1f93994 100644
--- a/addons/hr_evaluation/hr_evaluation_view.xml
+++ b/addons/hr_evaluation/hr_evaluation_view.xml
@@ -306,6 +306,10 @@
+
@@ -329,6 +333,7 @@
+
@@ -403,8 +408,16 @@
-
+
+
+
)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+import tools
+from tools.translate import _
+
+class mail_compose_message(osv.osv_memory):
+ _inherit = 'mail.compose.message'
+
+ def get_value(self, cr, uid, model, resource_id, context=None):
+ '''
+ To get values of the resource_id for the model
+ @param model: Object
+ @param resource_id: id of a record for which values to be read
+
+ @return: Returns a dictionary
+ '''
+ if context is None:
+ context = {}
+ result = super(mail_compose_message, self).get_value(cr, uid, model, resource_id, context=context)
+ if model == 'hr.evaluation.interview' and resource_id:
+ model_pool = self.pool.get(model)
+ record_data = model_pool.browse(cr, uid, resource_id, context)
+ if record_data.state == "waiting_answer":
+ msg = _("Hello %s, \n\n Kindly post your response for '%s' survey interview. \n\n Thanks,") %(record_data.user_to_review_id.name, record_data.survey_id.title)
+ result.update({
+ 'email_from': tools.config.get('email_from',''),
+ 'email_to': record_data.user_to_review_id.work_email or False,
+ 'subject': _("Reminder to fill up Survey"),
+ 'body_text': msg,
+ 'res_id': resource_id,
+ 'model': model,
+ 'email_cc': False,
+ 'email_bcc': False,
+ 'reply_to': False,
+ })
+ return result
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py
index 4ab71801a67..68d607b0335 100644
--- a/addons/hr_expense/hr_expense.py
+++ b/addons/hr_expense/hr_expense.py
@@ -276,7 +276,7 @@ class hr_expense_line(osv.osv):
if product_id:
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
res['name'] = product.name
- amount_unit = product.price_get('standard_price', context=context)[product.id]
+ amount_unit = product.price_get('standard_price')[product.id]
res['unit_amount'] = amount_unit
if not uom_id:
res['uom_id'] = product.uom_id.id
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index 3456f9c8bbe..7fde90dc8e9 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -29,6 +29,9 @@ import collections
import binascii
import tools
from tools.translate import _
+from crm import wizard
+
+wizard.mail_compose_message.SUPPORTED_MODELS.append('hr.applicant')
AVAILABLE_STATES = [
('draft', 'New'),
@@ -90,7 +93,7 @@ class hr_applicant(crm.crm_case, osv.osv):
_name = "hr.applicant"
_description = "Applicant"
_order = "id desc"
- _inherit = ['mailgate.thread']
+ _inherit = ['mail.thread']
def _compute_day(self, cr, uid, ids, fields, args, context=None):
"""
@@ -128,7 +131,7 @@ class hr_applicant(crm.crm_case, osv.osv):
_columns = {
'name': fields.char('Name', size=128, required=True),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."),
'description': fields.text('Description'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
@@ -319,22 +322,13 @@ class hr_applicant(crm.crm_case, osv.osv):
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
return value
- def message_new(self, cr, uid, msg, context=None):
- """
- Automatically calls when new email message arrives
-
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks
- """
- mailgate_pool = self.pool.get('email.server.tools')
- attach_obj = self.pool.get('ir.attachment')
-
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ res_id = super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
subject = msg.get('subject') or _("No Subject")
- body = msg.get('body')
+ body = msg.get('body_text')
msg_from = msg.get('from')
priority = msg.get('priority')
-
vals = {
'name': subject,
'email_from': msg_from,
@@ -342,42 +336,19 @@ class hr_applicant(crm.crm_case, osv.osv):
'description': body,
'user_id': False,
}
- if msg.get('priority', False):
+ if priority:
vals['priority'] = priority
+ vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False)))
+ self.write(cr, uid, [res_id], vals, context)
+ return res_id
- res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
- if res:
- vals.update(res)
- res = self.create(cr, uid, vals, context=context)
-
- attachents = msg.get('attachments', [])
- for attactment in attachents or []:
- data_attach = {
- 'name': attactment,
- 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
- 'datas_fname': attactment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- attach_obj.create(cr, uid, data_attach, context=context)
-
- return res
-
- def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
- """
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of update mail’s IDs
- """
-
+ def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None):
if isinstance(ids, (str, int, long)):
ids = [ids]
msg_from = msg['from']
vals.update({
- 'description': msg['body']
+ 'description': msg['body_text']
})
if msg.get('priority', False):
vals['priority'] = msg.get('priority')
@@ -388,7 +359,7 @@ class hr_applicant(crm.crm_case, osv.osv):
'probability':'probability'
}
vls = { }
- for line in msg['body'].split('\n'):
+ for line in msg['body_text'].split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower(), False):
@@ -397,19 +368,9 @@ class hr_applicant(crm.crm_case, osv.osv):
vals.update(vls)
res = self.write(cr, uid, ids, vals, context=context)
+ self.message_append_dict(cr, uid, ids, msg, context=context)
return res
- def msg_send(self, cr, uid, id, *args, **argv):
- """ Send The Message
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of email’s IDs
- @param *args: Return Tuple Value
- @param **args: Return Dictionary of Keyword Value
- """
- return True
-
def case_open(self, cr, uid, ids, *args):
"""
@param self: The object pointer
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index b1d957aa6dc..629b1c68012 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -144,15 +144,15 @@
-
+
-
-
+
+
@@ -160,20 +160,18 @@
-
-
+
-
-
-
+
+
+
-
+
@@ -183,14 +181,13 @@
-
-
+
diff --git a/addons/hr_recruitment/security/ir.model.access.csv b/addons/hr_recruitment/security/ir.model.access.csv
index 2907b96fd81..96baaf9e278 100644
--- a/addons/hr_recruitment/security/ir.model.access.csv
+++ b/addons/hr_recruitment/security/ir.model.access.csv
@@ -3,7 +3,7 @@
"access_hr_recruitment_report","hr.recruitment.report","model_hr_recruitment_report","base.group_hr_manager",1,1,1,1
"access_hr_recruitment_stage_user","hr.recruitment.stage.user","model_hr_recruitment_stage","base.group_hr_user",1,1,1,1
"access_hr_recruitment_degree","hr.recruitment.degree","model_hr_recruitment_degree","base.group_hr_user",1,1,1,1
-"access_mailgate_message_user","mailgate.message.user","mail_gateway.model_mailgate_message","base.group_hr_user",1,1,1,1
+"access_mail_message_user","mail.message.user","mail.model_mail_message","base.group_hr_user",1,1,1,1
"access_res_partner_hr_user","res.partner.user","base.model_res_partner","base.group_hr_user",1,1,1,1
"access_res_partner_address_hr_user","res.partner.address.user","base.model_res_partner_address","base.group_hr_user",1,1,1,1
"access_survey_hr_user","survey.hr.user","survey.model_survey","base.group_hr_user",1,1,1,0
diff --git a/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py b/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py
index edd08b5b531..cd9cbea52f7 100644
--- a/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py
+++ b/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py
@@ -32,7 +32,7 @@ class job2phonecall(osv.osv_memory):
'deadline': fields.datetime('Planned Date'),
'note': fields.text('Goals'),
'category_id': fields.many2one('crm.case.categ', 'Category', required=True),
- }
+ }
def _date_user(self, cr, uid, context=None):
case_obj = self.pool.get('hr.applicant')
@@ -50,7 +50,7 @@ class job2phonecall(osv.osv_memory):
return categ_id and categ_id[0] or case.categ_id and case.categ_id.id or False
def _get_note(self, cr, uid, context=None):
- msg_obj = self.pool.get('mailgate.message')
+ mail_message = self.pool.get('mail.message')
if context is None:
context = {}
if context.get('active_id'):
@@ -58,9 +58,9 @@ class job2phonecall(osv.osv_memory):
if case.description:
return case.description
else:
- msg_ids = msg_obj.search(cr, uid, [('model', '=', 'hr.applicant'), ('res_id', '=', case.id), ('email_from', '!=', ''), ('email_to', '!=', '')], limit=1)
+ msg_ids = mail_message.search(cr, uid, [('model', '=', 'hr.applicant'), ('res_id', '=', case.id), ('email_from', '!=', ''), ('email_to', '!=', '')], limit=1)
if msg_ids:
- return msg_obj.browse(cr, uid, msg_ids[0], context=context).description
+ return mail_message.browse(cr, uid, msg_ids[0], context=context).body_text
return False
diff --git a/addons/mail_gateway/__init__.py b/addons/mail/__init__.py
similarity index 89%
rename from addons/mail_gateway/__init__.py
rename to addons/mail/__init__.py
index 6526541a068..4787883ef9c 100644
--- a/addons/mail_gateway/__init__.py
+++ b/addons/mail/__init__.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
+# Copyright (C) 2009-Today OpenERP SA ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,8 +19,10 @@
#
##############################################################################
-import mail_gateway
+import mail_message
+import mail_thread
import res_partner
+import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py
new file mode 100644
index 00000000000..e9716be9e8e
--- /dev/null
+++ b/addons/mail/__openerp__.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP S.A. ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{
+ 'name': 'Email Subsystem',
+ 'version': '1.0',
+ 'category': 'Tools',
+ 'complexity': "easy",
+ 'description': """
+A generic email subsystem with message storage and queuing
+==========================================================
+
+This email subsystem is not intended to be used as as standalone
+application, but to provide a unified email abstraction that all
+other applications can use.
+
+The main features are:
+
+ * Relies on the global Outgoing Mail Servers configured in the
+ Administration menu for delivering outgoing mail
+ * Provides an API for sending messages and archiving them,
+ grouped by conversation
+ * Any OpenERP document can act as a conversation topic, provided
+ it includes the necessary support for handling incoming emails
+ (see the ``mail.thread`` class for more details).
+ * Includes queuing mechanism with automated configurable
+ scheduler-based processing
+ * Includes a generic email composition assistant, that can turn
+ into a mass-mailing assistant, and is capable of interpreting
+ simple *placeholder expressions* that will be replaced with
+ dynamic data when each email is actually sent.
+ This generic assistant is easily extensible to provide advanced
+ features (see ``email_template`` for example, which adds email
+ templating features to this assistant)
+
+ """,
+ 'author': 'OpenERP SA',
+ 'website': 'http://www.openerp.com',
+ 'depends': ['base', 'base_tools'],
+ 'data': [
+ "wizard/mail_compose_message_view.xml",
+ "mail_message_view.xml",
+ "mail_thread_view.xml",
+ "res_partner_view.xml",
+ 'security/ir.model.access.csv',
+ 'mail_data.xml',
+ ],
+ 'installable': True,
+ 'active': False,
+ 'certificate': '001056784984222247309',
+ 'images': ['images/customer_history.jpeg','images/messages_form.jpeg','images/messages_list.jpeg'],
+}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail_gateway/i18n/bg.po b/addons/mail/i18n/bg.po
similarity index 100%
rename from addons/mail_gateway/i18n/bg.po
rename to addons/mail/i18n/bg.po
diff --git a/addons/mail_gateway/i18n/ca.po b/addons/mail/i18n/ca.po
similarity index 100%
rename from addons/mail_gateway/i18n/ca.po
rename to addons/mail/i18n/ca.po
diff --git a/addons/mail_gateway/i18n/de.po b/addons/mail/i18n/de.po
similarity index 100%
rename from addons/mail_gateway/i18n/de.po
rename to addons/mail/i18n/de.po
diff --git a/addons/mail_gateway/i18n/es.po b/addons/mail/i18n/es.po
similarity index 100%
rename from addons/mail_gateway/i18n/es.po
rename to addons/mail/i18n/es.po
diff --git a/addons/mail_gateway/i18n/es_PY.po b/addons/mail/i18n/es_PY.po
similarity index 100%
rename from addons/mail_gateway/i18n/es_PY.po
rename to addons/mail/i18n/es_PY.po
diff --git a/addons/mail_gateway/i18n/fi.po b/addons/mail/i18n/fi.po
similarity index 100%
rename from addons/mail_gateway/i18n/fi.po
rename to addons/mail/i18n/fi.po
diff --git a/addons/mail_gateway/i18n/fr.po b/addons/mail/i18n/fr.po
similarity index 100%
rename from addons/mail_gateway/i18n/fr.po
rename to addons/mail/i18n/fr.po
diff --git a/addons/mail_gateway/i18n/gl.po b/addons/mail/i18n/gl.po
similarity index 100%
rename from addons/mail_gateway/i18n/gl.po
rename to addons/mail/i18n/gl.po
diff --git a/addons/mail_gateway/i18n/hu.po b/addons/mail/i18n/hu.po
similarity index 100%
rename from addons/mail_gateway/i18n/hu.po
rename to addons/mail/i18n/hu.po
diff --git a/addons/mail_gateway/i18n/it.po b/addons/mail/i18n/it.po
similarity index 100%
rename from addons/mail_gateway/i18n/it.po
rename to addons/mail/i18n/it.po
diff --git a/addons/mail_gateway/i18n/lt.po b/addons/mail/i18n/lt.po
similarity index 100%
rename from addons/mail_gateway/i18n/lt.po
rename to addons/mail/i18n/lt.po
diff --git a/addons/mail_gateway/i18n/lv.po b/addons/mail/i18n/lv.po
similarity index 100%
rename from addons/mail_gateway/i18n/lv.po
rename to addons/mail/i18n/lv.po
diff --git a/addons/mail_gateway/i18n/mail_gateway.pot b/addons/mail/i18n/mail.pot
similarity index 100%
rename from addons/mail_gateway/i18n/mail_gateway.pot
rename to addons/mail/i18n/mail.pot
diff --git a/addons/mail_gateway/i18n/nl.po b/addons/mail/i18n/nl.po
similarity index 100%
rename from addons/mail_gateway/i18n/nl.po
rename to addons/mail/i18n/nl.po
diff --git a/addons/mail_gateway/i18n/pl.po b/addons/mail/i18n/pl.po
similarity index 100%
rename from addons/mail_gateway/i18n/pl.po
rename to addons/mail/i18n/pl.po
diff --git a/addons/mail_gateway/i18n/pt.po b/addons/mail/i18n/pt.po
similarity index 100%
rename from addons/mail_gateway/i18n/pt.po
rename to addons/mail/i18n/pt.po
diff --git a/addons/mail_gateway/i18n/pt_BR.po b/addons/mail/i18n/pt_BR.po
similarity index 100%
rename from addons/mail_gateway/i18n/pt_BR.po
rename to addons/mail/i18n/pt_BR.po
diff --git a/addons/mail_gateway/i18n/ro.po b/addons/mail/i18n/ro.po
similarity index 100%
rename from addons/mail_gateway/i18n/ro.po
rename to addons/mail/i18n/ro.po
diff --git a/addons/mail_gateway/i18n/ru.po b/addons/mail/i18n/ru.po
similarity index 100%
rename from addons/mail_gateway/i18n/ru.po
rename to addons/mail/i18n/ru.po
diff --git a/addons/mail_gateway/i18n/sl.po b/addons/mail/i18n/sl.po
similarity index 100%
rename from addons/mail_gateway/i18n/sl.po
rename to addons/mail/i18n/sl.po
diff --git a/addons/mail_gateway/i18n/sr@latin.po b/addons/mail/i18n/sr@latin.po
similarity index 100%
rename from addons/mail_gateway/i18n/sr@latin.po
rename to addons/mail/i18n/sr@latin.po
diff --git a/addons/mail_gateway/i18n/sv.po b/addons/mail/i18n/sv.po
similarity index 100%
rename from addons/mail_gateway/i18n/sv.po
rename to addons/mail/i18n/sv.po
diff --git a/addons/mail_gateway/i18n/zh_CN.po b/addons/mail/i18n/zh_CN.po
similarity index 100%
rename from addons/mail_gateway/i18n/zh_CN.po
rename to addons/mail/i18n/zh_CN.po
diff --git a/addons/email_template/email_template_scheduler_data.xml b/addons/mail/mail_data.xml
similarity index 59%
rename from addons/email_template/email_template_scheduler_data.xml
rename to addons/mail/mail_data.xml
index 45a9be04ac7..964552b951a 100644
--- a/addons/email_template/email_template_scheduler_data.xml
+++ b/addons/mail/mail_data.xml
@@ -1,15 +1,15 @@
-
- Email Template scheduler
+
+ Email Queue Manager
1
hours
-1
-
-
+
+
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
new file mode 100644
index 00000000000..621eef59da5
--- /dev/null
+++ b/addons/mail/mail_message.py
@@ -0,0 +1,522 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-2011 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import base64
+import dateutil.parser
+import email
+import logging
+import re
+import time
+from email.header import decode_header
+from email.message import Message
+
+import tools
+from osv import osv
+from osv import fields
+from tools.translate import _
+from tools.safe_eval import literal_eval
+
+_logger = logging.getLogger('mail')
+
+def format_date_tz(date, tz=None):
+ if not date:
+ return 'n/a'
+ format = tools.DEFAULT_SERVER_DATETIME_FORMAT
+ return tools.server_to_local_timestamp(date, format, format, tz)
+
+def truncate_text(text):
+ lines = text and text.split('\n') or []
+ if len(lines) > 3:
+ res = '\n\t'.join(lines[:3]) + '...'
+ else:
+ res = '\n\t'.join(lines)
+ return res
+
+def decode(text):
+ """Returns unicode() string conversion of the the given encoded smtp header text"""
+ if text:
+ text = decode_header(text.replace('\r', ''))
+ return ''.join([tools.ustr(x[0], x[1]) for x in text])
+
+def to_email(text):
+ """Return a list of the email addresses found in ``text``"""
+ if not text: return []
+ return re.findall(r'([^ ,<@]+@[^> ,]+)', text)
+
+class mail_message_common(osv.osv_memory):
+ """Common abstract class for holding the main attributes of a
+ message object. It could be reused as parent model for any
+ database model or wizard screen that needs to hold a kind of
+ message"""
+
+ _name = 'mail.message.common'
+ _rec_name = 'subject'
+ _columns = {
+ 'subject': fields.char('Subject', size=512, required=True),
+ 'model': fields.char('Related Document model', size=128, select=1, readonly=1),
+ 'res_id': fields.integer('Related Document ID', select=1, readonly=1),
+ 'date': fields.datetime('Date'),
+ 'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences'),
+ 'email_to': fields.char('To', size=256, help='Message recipients'),
+ 'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
+ 'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),
+ 'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
+ 'headers': fields.text('Message headers', readonly=1,
+ help="Full message headers, e.g. SMTP session headers "
+ "(usually available on inbound messages only)"),
+ 'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
+ 'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
+ 'subtype': fields.char('Message type', size=32, help="Type of message, usually 'html' or 'plain', used to "
+ "select plaintext or rich text contents accordingly", readonly=1),
+ 'body_text': fields.text('Text contents', help="Plain-text version of the message"),
+ 'body_html': fields.text('Rich-text contents', help="Rich-text/HTML version of the message"),
+ }
+
+ _defaults = {
+ 'subtype': 'plain'
+ }
+
+class mail_message(osv.osv):
+ '''Model holding RFC2822 email messages, and providing facilities
+ to parse, queue and send new messages
+
+ Messages that do not have a value for the email_from column
+ are simple log messages (e.g. document state changes), while
+ actual e-mails have the email_from value set.
+ The ``display_text`` field will have a slightly different
+ presentation for real emails and for log messages.
+ '''
+
+ _name = 'mail.message'
+ _inherit = 'mail.message.common'
+ _description = 'Email Message'
+ _order = 'date desc'
+
+ # XXX to review - how to determine action to use?
+ def open_document(self, cr, uid, ids, context=None):
+ action_data = False
+ if ids:
+ msg = self.browse(cr, uid, ids[0], context=context)
+ model = msg.model
+ res_id = msg.res_id
+
+ ir_act_window = self.pool.get('ir.actions.act_window')
+ action_ids = ir_act_window.search(cr, uid, [('res_model', '=', model)])
+ if action_ids:
+ action_data = ir_act_window.read(cr, uid, action_ids[0], context=context)
+ action_data.update({
+ 'domain' : "[('id','=',%d)]"%(res_id),
+ 'nodestroy': True,
+ 'context': {}
+ })
+ return action_data
+
+ # XXX to review - how to determine action to use?
+ def open_attachment(self, cr, uid, ids, context=None):
+ action_data = False
+ action_pool = self.pool.get('ir.actions.act_window')
+ message = self.browse(cr, uid, ids, context=context)[0]
+ att_ids = [x.id for x in message.attachment_ids]
+ action_ids = action_pool.search(cr, uid, [('res_model', '=', 'ir.attachment')])
+ if action_ids:
+ action_data = action_pool.read(cr, uid, action_ids[0], context=context)
+ action_data.update({
+ 'domain': [('id','in',att_ids)],
+ 'nodestroy': True
+ })
+ return action_data
+
+ def _get_display_text(self, cr, uid, ids, name, arg, context=None):
+ if context is None:
+ context = {}
+ tz = context.get('tz')
+ result = {}
+ for message in self.browse(cr, uid, ids, context=context):
+ msg_txt = ''
+ if message.email_from:
+ msg_txt += _('%s wrote on %s: \n Subject: %s \n\t') % (message.email_from or '/', format_date_tz(message.date, tz), message.subject)
+ if message.body_text:
+ msg_txt += truncate_text(message.body_text)
+ else:
+ msg_txt = (message.user_id.name or '/') + _(' on ') + format_date_tz(message.date, tz) + ':\n\t'
+ msg_txt += message.subject
+ result[message.id] = msg_txt
+ return result
+
+ _columns = {
+ 'partner_id': fields.many2one('res.partner', 'Related partner'),
+ 'user_id': fields.many2one('res.users', 'Related user', readonly=1),
+ 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'),
+ 'display_text': fields.function(_get_display_text, method=True, type='text', size="512", string='Display Text'),
+ 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
+ 'state': fields.selection([
+ ('outgoing', 'Outgoing'),
+ ('sent', 'Sent'),
+ ('received', 'Received'),
+ ('exception', 'Delivery Failed'),
+ ('cancel', 'Cancelled'),
+ ], 'State', readonly=True),
+ 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
+ 'original': fields.binary('Original', help="Original version of the message, as it was sent on the network", readonly=1),
+ }
+
+ _defaults = {
+ 'state': 'received',
+ }
+
+ def init(self, cr):
+ cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'mail_message_model_res_id_idx'""")
+ if not cr.fetchone():
+ cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
+
+ def copy(self, cr, uid, id, default=None, context=None):
+ """Overridden to avoid duplicating fields that are unique to each email"""
+ if default is None:
+ default = {}
+ default.update(message_id=False,original=False,headers=False)
+ return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
+
+ def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, email_cc=None,
+ email_bcc=None, reply_to=False, attachments=None, message_id=False, references=False,
+ res_id=False, subtype='plain', headers=None, mail_server_id=False, auto_delete=False,
+ context=None):
+ """Schedule sending a new email message, to be sent the next time the mail scheduler runs, or
+ the next time :meth:`process_email_queue` is called explicitly.
+
+ :param string email_from: sender email address
+ :param list email_to: list of recipient addresses (to be joined with commas)
+ :param string subject: email subject (no pre-encoding/quoting necessary)
+ :param string body: email body, according to the ``subtype`` (by default, plaintext).
+ If html subtype is used, the message will be automatically converted
+ to plaintext and wrapped in multipart/alternative.
+ :param list email_cc: optional list of string values for CC header (to be joined with commas)
+ :param list email_bcc: optional list of string values for BCC header (to be joined with commas)
+ :param string model: optional model name of the document this mail is related to (this will also
+ be used to generate a tracking id, used to match any response related to the
+ same document)
+ :param int res_id: optional resource identifier this mail is related to (this will also
+ be used to generate a tracking id, used to match any response related to the
+ same document)
+ :param string reply_to: optional value of Reply-To header
+ :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
+ must match the format of the ``body`` parameter. Default is 'plain',
+ making the content part of the mail "text/plain".
+ :param dict attachments: map of filename to filecontents, where filecontents is a string
+ containing the bytes of the attachment
+ :param dict headers: optional map of headers to set on the outgoing mail (may override the
+ other headers, including Subject, Reply-To, Message-Id, etc.)
+ :param int mail_server_id: optional id of the preferred outgoing mail server for this mail
+ :param bool auto_delete: optional flag to turn on auto-deletion of the message after it has been
+ successfully sent (default to False)
+
+ """
+ if context is None:
+ context = {}
+ if attachments is None:
+ attachments = {}
+ attachment_obj = self.pool.get('ir.attachment')
+ for param in (email_to, email_cc, email_bcc):
+ if param and not isinstance(param, list):
+ param = [param]
+ msg_vals = {
+ 'subject': subject,
+ 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'user_id': uid,
+ 'model': model,
+ 'res_id': res_id,
+ 'body_text': body if subtype != 'html' else False,
+ 'body_html': body if subtype == 'html' else False,
+ 'email_from': email_from,
+ 'email_to': email_to and ','.join(email_to) or '',
+ 'email_cc': email_cc and ','.join(email_cc) or '',
+ 'email_bcc': email_bcc and ','.join(email_bcc) or '',
+ 'reply_to': reply_to,
+ 'message_id': message_id,
+ 'references': references,
+ 'subtype': subtype,
+ 'headers': headers, # serialize the dict on the fly
+ 'mail_server_id': mail_server_id,
+ 'state': 'outgoing',
+ 'auto_delete': auto_delete
+ }
+ email_msg_id = self.create(cr, uid, msg_vals, context)
+ attachment_ids = []
+ for fname, fcontent in attachments.iteritems():
+ attachment_data = {
+ 'name': fname,
+ 'datas_fname': fname,
+ 'datas': fcontent,
+ 'res_model': self._name,
+ 'res_id': email_msg_id,
+ }
+ if context.has_key('default_type'):
+ del context['default_type']
+ attachment_ids.append(attachment_obj.create(cr, uid, attachment_data, context))
+ if attachment_ids:
+ self.write(cr, uid, email_msg_id, { 'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
+ return email_msg_id
+
+ def mark_outgoing(self, cr, uid, ids, context=None):
+ return self.write(cr, uid, ids, {'state':'outgoing'}, context)
+
+ def process_email_queue(self, cr, uid, ids=None, context=None):
+ """Send immediately queued messages, committing after each
+ message is sent - this is not transactional and should
+ not be called during another transaction!
+
+ :param list ids: optional list of emails ids to send. If passed
+ no search is performed, and these ids are used
+ instead.
+ :param dict context: if a 'filters' key is present in context,
+ this value will be used as an additional
+ filter to further restrict the outgoing
+ messages to send (by default all 'outgoing'
+ messages are sent).
+ """
+ if context is None:
+ context = {}
+ if not ids:
+ filters = [('state', '=', 'outgoing')]
+ if 'filters' in context:
+ filters.extend(context['filters'])
+ ids = self.search(cr, uid, filters, context=context)
+ res = None
+ try:
+ # Force auto-commit - this is meant to be called by
+ # the scheduler, and we can't allow rolling back the status
+ # of previously sent emails!
+ res = self.send(cr, uid, ids, auto_commit=True, context=context)
+ except Exception:
+ _logger.exception("Failed processing mail queue")
+ return res
+
+ def parse_message(self, message, save_original=False):
+ """Parses a string or email.message.Message representing an
+ RFC-2822 email, and returns a generic dict holding the
+ message details.
+
+ :param message: the message to parse
+ :type message: email.message.Message | string | unicode
+ :param bool save_original: whether the returned dict
+ should include an ``original`` entry with the base64
+ encoded source of the message.
+ :rtype: dict
+ :return: A dict with the following structure, where each
+ field may not be present if missing in original
+ message::
+
+ { 'message-id': msg_id,
+ 'subject': subject,
+ 'from': from,
+ 'to': to,
+ 'cc': cc,
+ 'headers' : { 'X-Mailer': mailer,
+ #.. all X- headers...
+ },
+ 'subtype': msg_mime_subtype,
+ 'body_text': plaintext_body
+ 'body_html': html_body,
+ 'attachments': { 'file1': 'bytes',
+ 'file2': 'bytes' }
+ # ...
+ 'original': source_of_email,
+ }
+ """
+ msg_txt = message
+ if isinstance(message, str):
+ msg_txt = email.message_from_string(message)
+
+ # Warning: message_from_string doesn't always work correctly on unicode,
+ # we must use utf-8 strings here :-(
+ if isinstance(message, unicode):
+ message = message.encode('utf-8')
+ msg_txt = email.message_from_string(message)
+
+ message_id = msg_txt.get('message-id', False)
+ msg = {}
+
+ if save_original:
+ # save original, we need to be able to read the original email sometimes
+ msg['original'] = message.as_string() if isinstance(message, Message) \
+ else message
+ msg['original'] = base64.b64encode(msg['original']) # binary fields are b64
+
+ if not message_id:
+ # Very unusual situation, be we should be fault-tolerant here
+ message_id = time.time()
+ msg_txt['message-id'] = message_id
+ _logger.info('Parsing Message without message-id, generating a random one: %s', message_id)
+
+ fields = msg_txt.keys()
+ msg['id'] = message_id
+ msg['message-id'] = message_id
+
+ if 'Subject' in fields:
+ msg['subject'] = decode(msg_txt.get('Subject'))
+
+ if 'Content-Type' in fields:
+ msg['content-type'] = msg_txt.get('Content-Type')
+
+ if 'From' in fields:
+ msg['from'] = decode(msg_txt.get('From') or msg_txt.get_unixfrom())
+
+ if 'To' in fields:
+ msg['to'] = decode(msg_txt.get('To'))
+ if 'Delivered-To' in fields:
+ msg['to'] = decode(msg_txt.get('Delivered-To'))
+
+ if 'CC' in fields:
+ msg['cc'] = decode(msg_txt.get('CC'))
+
+ if 'Reply-To' in fields:
+ msg['reply'] = decode(msg_txt.get('Reply-To'))
+
+ if 'Date' in fields:
+ date_hdr = decode(msg_txt.get('Date'))
+ msg['date'] = dateutil.parser.parse(date_hdr).strftime("%Y-%m-%d %H:%M:%S")
+
+ if 'Content-Transfer-Encoding' in fields:
+ msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
+
+ if 'References' in fields:
+ msg['references'] = msg_txt.get('References')
+
+ if 'In-Reply-To' in fields:
+ msg['in-reply-to'] = msg_txt.get('In-Reply-To')
+
+ msg['headers'] = {}
+ msg['subtype'] = 'plain'
+ for item in msg_txt.items():
+ if item[0].startswith('X-'):
+ msg['headers'].update({item[0]: item[1]})
+ if not msg_txt.is_multipart() or 'text/plain' in msg.get('content-type', ''):
+ encoding = msg_txt.get_content_charset()
+ body = msg_txt.get_payload(decode=True)
+ if 'text/html' in msg.get('content-type', ''):
+ msg['body_html'] = body
+ msg['subtype'] = 'html'
+ body = tools.html2plaintext(body)
+ msg['body_text'] = tools.ustr(body, encoding)
+
+ attachments = {}
+ if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
+ body = ""
+ if 'multipart/alternative' in msg.get('content-type', ''):
+ msg['subtype'] = 'alternative'
+ else:
+ msg['subtype'] = 'mixed'
+ for part in msg_txt.walk():
+ if part.get_content_maintype() == 'multipart':
+ continue
+
+ encoding = part.get_content_charset()
+ filename = part.get_filename()
+ if part.get_content_maintype()=='text':
+ content = part.get_payload(decode=True)
+ if filename:
+ attachments[filename] = content
+ content = tools.ustr(content, encoding)
+ if part.get_content_subtype() == 'html':
+ msg['body_html'] = content
+ msg['subtype'] = 'html' # html version prevails
+ body = tools.ustr(tools.html2plaintext(content))
+ elif part.get_content_subtype() == 'plain':
+ body = content
+ elif part.get_content_maintype() in ('application', 'image'):
+ if filename :
+ attachments[filename] = part.get_payload(decode=True)
+ else:
+ res = part.get_payload(decode=True)
+ body += tools.ustr(res, encoding)
+
+ msg['body_text'] = body
+ msg['attachments'] = attachments
+
+ # for backwards compatibility:
+ msg['body'] = msg['body_text']
+ msg['sub_type'] = msg['subtype'] or 'plain'
+ return msg
+
+ def send(self, cr, uid, ids, auto_commit=False, context=None):
+ """Sends the selected emails immediately, ignoring their current
+ state (mails that have already been sent should not be passed
+ unless they should actually be re-sent).
+ Emails successfully delivered are marked as 'sent', and those
+ that fail to be deliver are marked as 'exception', and the
+ corresponding error message is output in the server logs.
+
+ :param bool auto_commit: whether to force a commit of the message
+ status after sending each message (meant
+ only for processing by the scheduler),
+ should never be True during normal
+ transactions (default: False)
+ :return: True
+ """
+ if context is None:
+ context = {}
+ ir_mail_server = self.pool.get('ir.mail_server')
+ self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
+ for message in self.browse(cr, uid, ids, context=context):
+ try:
+ attachments = []
+ for attach in message.attachment_ids:
+ attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
+ msg = ir_mail_server.build_email(
+ email_from=message.email_from,
+ email_to=to_email(message.email_to),
+ subject=message.subject,
+ body=message.body_html if message.subtype == 'html' else message.body_text,
+ email_cc=to_email(message.email_cc),
+ email_bcc=to_email(message.email_bcc),
+ reply_to=message.reply_to,
+ attachments=attachments, message_id=message.message_id,
+ references = message.references,
+ object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
+ subtype=message.subtype,
+ headers=message.headers and literal_eval(message.headers))
+ res = ir_mail_server.send_email(cr, uid, msg,
+ mail_server_id=message.mail_server_id.id,
+ context=context)
+ if res:
+ message.write({'state':'sent', 'message_id': res})
+ else:
+ message.write({'state':'exception'})
+
+ # if auto_delete=True then delete that sent messages as well as attachments
+ message.refresh()
+ if message.state == 'sent' and message.auto_delete:
+ self.pool.get('ir.attachment').unlink(cr, uid,
+ [x.id for x in message.attachment_ids],
+ context=context)
+ message.unlink()
+ except Exception:
+ _logger.exception('failed sending mail.message %s', message.id)
+ message.write({'state':'exception'})
+
+ if auto_commit == True:
+ cr.commit()
+ return True
+
+ def cancel(self, cr, uid, ids, context=None):
+ self.write(cr, uid, ids, {'state':'cancel'}, context=context)
+ return True
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
new file mode 100644
index 00000000000..83e7474fe1a
--- /dev/null
+++ b/addons/mail/mail_message_view.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+ mail.message.form
+ mail.message
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.message.tree
+ mail.message
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.message.search
+ mail.message
+ search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Messages
+ mail.message
+ form
+ tree,form
+ [('email_from', '!=', False)]
+ {'search_default_outgoing':1}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
new file mode 100644
index 00000000000..fa785c3517c
--- /dev/null
+++ b/addons/mail/mail_thread.py
@@ -0,0 +1,463 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2009-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import time
+import tools
+import base64
+import email
+from email.utils import parsedate
+
+import logging
+import xmlrpclib
+from osv import osv, fields
+from tools.translate import _
+from mail_message import decode, to_email
+
+_logger = logging.getLogger('mail')
+
+class mail_thread(osv.osv):
+ '''Mixin model, meant to be inherited by any model that needs to
+ act as a discussion topic on which messages can be attached.
+ Public methods are prefixed with ``message_`` in order to avoid
+ name collisions with methods of the models that will inherit
+ from this mixin.
+
+ ``mail.thread`` adds a one2many of mail.messages, acting as the
+ thread's history, and a few methods that may be overridden to
+ implement model-specific behavior upon arrival of new messages.
+
+ Inheriting classes are not required to implement any method, as the
+ default implementation will work for any model. However it is common
+ to override at least the ``message_new`` and ``message_update``
+ methods (calling ``super``) to add model-specific behavior at
+ creation and update of a thread.
+
+ '''
+ _name = 'mail.thread'
+ _description = 'Email Thread'
+
+ _columns = {
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', readonly=True),
+ }
+
+ def message_thread_followers(self, cr, uid, ids, context=None):
+ """Returns a list of email addresses of the people following
+ this thread, including the sender of each mail, and the
+ people who were in CC of the messages, if any.
+ """
+ res = {}
+ if isinstance(ids, (str, int, long)):
+ ids = [long(ids)]
+ for thread in self.browse(cr, uid, ids, context=context):
+ l = set()
+ for message in thread.message_ids:
+ l.add((message.user_id and message.user_id.user_email) or '')
+ l.add(message.email_from or '')
+ l.add(message.email_cc or '')
+ res[thread.id] = filter(None, l)
+ return res
+
+ def copy(self, cr, uid, id, default=None, context=None):
+ """Overrides default copy method to empty the thread of
+ messages attached to this record, as the copied object
+ will have its own thread and does not have to share it.
+ """
+ if default is None:
+ default = {}
+ default.update({
+ 'message_ids': [],
+ })
+ return super(mail_thread, self).copy(cr, uid, id, default, context=context)
+
+ def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
+ """Called by ``message_process`` when a new message is received
+ for a given thread model, if the message did not belong to
+ an existing thread.
+ The default behavior is to create a new record of the corresponding
+ model (based on some very basic info extracted from the message),
+ then attach the message to the newly created record
+ (by calling ``message_append_dict``).
+ Additional behavior may be implemented by overriding this method.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process`` and
+ ``mail.message.parse`` for details.
+ :param dict custom_values: optional dictionary of additional
+ field values to pass to create()
+ when creating the new thread record.
+ Be careful, these values may override
+ any other values coming from the message.
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the record
+ to create (instead of the current model).
+ :rtype: int
+ :return: the id of the newly created thread object
+ """
+ if context is None:
+ context = {}
+ model = context.get('thread_model') or self._name
+ model_pool = self.pool.get(model)
+ fields = model_pool.fields_get(cr, uid, context=context)
+ data = model_pool.default_get(cr, uid, fields, context=context)
+ if 'name' in fields and not data.get('name'):
+ data['name'] = msg_dict.get('from','')
+ if custom_values and isinstance(custom_values, dict):
+ data.update(custom_values)
+ res_id = model_pool.create(cr, uid, data, context=context)
+ self.message_append_dict(cr, uid, [res_id], msg_dict, context=context)
+ return res_id
+
+ def message_update(self, cr, uid, ids, msg_dict, vals={}, default_act=None, context=None):
+ """Called by ``message_process`` when a new message is received
+ for an existing thread. The default behavior is to create a
+ new mail.message in the given thread (by calling
+ ``message_append_dict``)
+ Additional behavior may be implemented by overriding this
+ method.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process`` and
+ ``mail.message.parse()`` for details.
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the thread to
+ update (instead of the current model).
+ """
+ return self.message_append_dict(cr, uid, ids, msg_dict, context=context)
+
+ def message_append(self, cr, uid, threads, subject, body_text=None, email_to=False,
+ email_from=False, email_cc=None, email_bcc=None, reply_to=None,
+ email_date=None, message_id=False, references=None,
+ attachments=None, body_html=None, subtype=None, headers=None,
+ original=None, context=None):
+ """Creates a new mail.message attached to the current mail.thread,
+ containing all the details passed as parameters. All attachments
+ will be attached to the thread record as well as to the actual
+ message.
+ If only the ``threads`` and ``subject`` parameters are provided,
+ a *event log* message is created, without the usual envelope
+ attributes (sender, recipients, etc.).
+
+ :param threads: list of thread ids, or list of browse_records representing
+ threads to which a new message should be attached
+ :param subject: subject of the message, or description of the event if this
+ is an *event log* entry.
+ :param email_to: Email-To / Recipient address
+ :param email_from: Email From / Sender address if any
+ :param email_cc: Comma-Separated list of Carbon Copy Emails To addresse if any
+ :param email_bcc: Comma-Separated list of Blind Carbon Copy Emails To addresses if any
+ :param reply_to: reply_to header
+ :param email_date: email date string if different from now, in server timezone
+ :param message_id: optional email identifier
+ :param references: optional email references
+ :param body_text: plaintext contents of the mail or log message
+ :param body_html: html contents of the mail or log message
+ :param subtype: optional type of message: 'plain' or 'html', corresponding to the main
+ body contents (body_text or body_html).
+ :param headers: mail headers to store
+ :param dict attachments: map of attachment filenames to binary contents, if any.
+ :param str original: optional full source of the RFC2822 email, for reference
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the thread to
+ update (instead of the current model).
+ """
+ if context is None:
+ context = {}
+ if attachments is None:
+ attachments = {}
+
+ if email_date:
+ edate = parsedate(email_date)
+ if edate is not None:
+ email_date = time.strftime('%Y-%m-%d %H:%M:%S', edate)
+
+ if all(isinstance(thread_id, (int, long)) for thread_id in threads):
+ model = context.get('thread_model') or self._name
+ model_pool = self.pool.get(model)
+ threads = model_pool.browse(cr, uid, threads, context=context)
+
+ ir_attachment = self.pool.get('ir.attachment')
+ mail_message = self.pool.get('mail.message')
+
+ for thread in threads:
+ to_attach = []
+ for fname, fcontent in attachments.items():
+ if isinstance(fcontent, unicode):
+ fcontent = fcontent.encode('utf-8')
+ data_attach = {
+ 'name': fname,
+ 'datas': base64.b64encode(str(fcontent)),
+ 'datas_fname': fname,
+ 'description': _('Mail attachment'),
+ 'res_model': thread._name,
+ 'res_id': thread.id,
+ }
+ to_attach.append(ir_attachment.create(cr, uid, data_attach, context=context))
+
+ partner_id = hasattr(thread, 'partner_id') and (thread.partner_id and thread.partner_id.id or False) or False
+ if not partner_id and thread._name == 'res.partner':
+ partner_id = thread.id
+ data = {
+ 'subject': subject,
+ 'user_id': uid,
+ 'model' : thread._name,
+ 'partner_id': partner_id,
+ 'res_id': thread.id,
+ 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'message_id': message_id,
+ 'body_text': body_text or (hasattr(thread, 'description') and thread.description or False),
+ 'attachment_ids': [(6, 0, to_attach)],
+ 'state' : 'received',
+ }
+
+ if email_from:
+ for param in (email_to, email_cc, email_bcc):
+ if isinstance(param, list):
+ param = ", ".join(param)
+ data = {
+ 'subject': subject or _('History'),
+ 'user_id': uid,
+ 'model' : thread._name,
+ 'res_id': thread.id,
+ 'date': email_date or time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'body_text': body_text,
+ 'email_to': email_to,
+ 'email_from': email_from or \
+ (hasattr(thread, 'user_id') and thread.user_id and thread.user_id.user_email),
+ 'email_cc': email_cc,
+ 'email_bcc': email_bcc,
+ 'partner_id': partner_id,
+ 'references': references,
+ 'message_id': message_id,
+ 'attachment_ids': [(6, 0, to_attach)],
+ 'state' : 'received',
+ 'body_html': body_html,
+ 'subtype': subtype,
+ 'headers': headers,
+ 'reply_to': reply_to,
+ 'original': original,
+ }
+ mail_message.create(cr, uid, data, context=context)
+ return True
+
+ def message_append_dict(self, cr, uid, ids, msg_dict, context=None):
+ """Creates a new mail.message attached to the given threads (``ids``),
+ with the contents of ``msg_dict``, by calling ``message_append``
+ with the mail details. All attachments in msg_dict will be
+ attached to the object record as well as to the actual
+ mail message.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process()`` and
+ ``mail.message.parse()`` for details on
+ the dict structure.
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the thread to
+ update (instead of the current model).
+ """
+ return self.message_append(cr, uid, ids,
+ subject = msg_dict.get('subject'),
+ body_text = msg_dict.get('body_text'),
+ email_to = msg_dict.get('to'),
+ email_from = msg_dict.get('from'),
+ email_cc = msg_dict.get('cc'),
+ email_bcc = msg_dict.get('bcc'),
+ reply_to = msg_dict.get('reply'),
+ email_date = msg_dict.get('date'),
+ message_id = msg_dict.get('message-id'),
+ references = msg_dict.get('references')\
+ or msg_dict.get('in-reply-to'),
+ attachments = msg_dict.get('attachments'),
+ body_html= msg_dict.get('body_html'),
+ subtype = msg_dict.get('subtype'),
+ headers = msg_dict.get('headers'),
+ original = msg_dict.get('original'),
+ context = context)
+
+
+ def message_process(self, cr, uid, model, message, custom_values=None,
+ save_original=False, strip_attachments=False,
+ context=None):
+ """Process an incoming RFC2822 email message related to the
+ given thread model, relying on ``mail.message.parse()``
+ for the parsing operation, and then calling ``message_new``
+ (if the thread record did not exist) or ``message_update``
+ (if it did), then calling ``message_forward`` to automatically
+ notify other people that should receive this message.
+
+ :param string model: the thread model for which a new message
+ must be processed
+ :param message: source of the RFC2822 mail
+ :type message: string or xmlrpclib.Binary
+ :type dict custom_values: optional dictionary of field values
+ to pass to ``message_new`` if a new
+ record needs to be created. Ignored
+ if the thread record already exists.
+ :param bool save_original: whether to keep a copy of the original
+ email source attached to the message after it is imported.
+ :param bool strip_attachments: whether to strip all attachments
+ before processing the message, in order to save some space.
+ """
+ # extract message bytes - we are forced to pass the message as binary because
+ # we don't know its encoding until we parse its headers and hence can't
+ # convert it to utf-8 for transport between the mailgate script and here.
+ if isinstance(message, xmlrpclib.Binary):
+ message = str(message.data)
+
+ model_pool = self.pool.get(model)
+ if self._name != model:
+ if context is None: context = {}
+ context.update({'thread_model': model})
+
+ mail_message = self.pool.get('mail.message')
+ res_id = False
+
+ # Parse Message
+ # Warning: message_from_string doesn't always work correctly on unicode,
+ # we must use utf-8 strings here :-(
+ if isinstance(message, unicode):
+ message = message.encode('utf-8')
+ msg_txt = email.message_from_string(message)
+ msg = mail_message.parse_message(msg_txt, save_original=save_original)
+
+ if strip_attachments and 'attachments' in msg:
+ del msg['attachments']
+
+ # Create New Record into particular model
+ def create_record(msg):
+ if hasattr(model_pool, 'message_new'):
+ return model_pool.message_new(cr, uid, msg,
+ custom_values,
+ context=context)
+ res_id = False
+ if msg.get('references') or msg.get('in-reply-to'):
+ references = msg.get('references') or msg.get('in-reply-to')
+ if '\r\n' in references:
+ references = references.split('\r\n')
+ else:
+ references = references.split(' ')
+ for ref in references:
+ ref = ref.strip()
+ res_id = tools.reference_re.search(ref)
+ if res_id:
+ res_id = res_id.group(1)
+ else:
+ res_id = tools.res_re.search(msg['subject'])
+ if res_id:
+ res_id = res_id.group(1)
+ if res_id:
+ res_id = int(res_id)
+ if model_pool.exists(cr, uid, res_id):
+ if hasattr(model_pool, 'message_update'):
+ model_pool.message_update(cr, uid, [res_id], msg, {}, context=context)
+ else:
+ # referenced thread was not found, we'll have to create a new one
+ res_id = False
+ if not res_id:
+ res_id = create_record(msg)
+ #To forward the email to other followers
+ self.message_forward(cr, uid, model, [res_id], msg_txt, context=context)
+ return res_id
+
+ # for backwards-compatibility with old scripts
+ process_email = message_process
+
+ def message_forward(self, cr, uid, model, thread_ids, msg, email_error=False, context=None):
+ """Sends an email to all people following the given threads.
+ The emails are forwarded immediately, not queued for sending,
+ and not archived.
+
+ :param str model: thread model
+ :param list thread_ids: ids of the thread records
+ :param msg: email.message.Message object to forward
+ :param email_error: optional email address to notify in case
+ of any delivery error during the forward.
+ :return: True
+ """
+ model_pool = self.pool.get(model)
+ smtp_server_obj = self.pool.get('ir.mail_server')
+ mail_message = self.pool.get('mail.message')
+ for res in model_pool.browse(cr, uid, thread_ids, context=context):
+ if hasattr(model_pool, 'message_thread_followers'):
+ followers = model_pool.message_thread_followers(cr, uid, [res.id])[res.id]
+ else:
+ followers = self.message_thread_followers(cr, uid, [res.id])[res.id]
+ message_followers_emails = to_email(','.join(filter(None, followers)))
+ message_recipients = to_email(','.join(filter(None,
+ [decode(msg['from']),
+ decode(msg['to']),
+ decode(msg['cc'])])))
+ forward_to = [i for i in message_followers_emails if (i and (i not in message_recipients))]
+ if forward_to:
+ # TODO: we need an interface for this for all types of objects, not just leads
+ if hasattr(res, 'section_id'):
+ del msg['reply-to']
+ msg['reply-to'] = res.section_id.reply_to
+
+ smtp_from, = to_email(msg['from'])
+ msg['from'] = smtp_from
+ msg['to'] = ", ".join(forward_to)
+ msg['message-id'] = tools.generate_tracking_message_id(res.id)
+ if not smtp_server_obj.send_email(cr, uid, msg) and email_error:
+ subj = msg['subject']
+ del msg['subject'], msg['to'], msg['cc'], msg['bcc']
+ msg['subject'] = _('[OpenERP-Forward-Failed] %s') % subj
+ msg['to'] = email_error
+ smtp_server_obj.send_email(cr, uid, msg)
+ return True
+
+ def message_partner_by_email(self, cr, uid, email, context=None):
+ """Attempts to return the id of a partner address matching
+ the given ``email``, and the corresponding partner id.
+ Can be used by classes using the ``mail.thread`` mixin
+ to lookup the partner and use it in their implementation
+ of ``message_new`` to link the new record with a
+ corresponding partner.
+ The keys used in the returned dict are meant to map
+ to usual names for relationships towards a partner
+ and one of its addresses.
+
+ :param email: email address for which a partner
+ should be searched for.
+ :rtype: dict
+ :return: a map of the following form::
+
+ { 'partner_address_id': id or False,
+ 'partner_id': pid or False }
+ """
+ address_pool = self.pool.get('res.partner.address')
+ res = {
+ 'partner_address_id': False,
+ 'partner_id': False
+ }
+ if email:
+ email = to_email(email)[0]
+ address_ids = address_pool.search(cr, uid, [('email', '=', email)])
+ if address_ids:
+ address = address_pool.browse(cr, uid, address_ids[0])
+ res['partner_address_id'] = address_ids[0]
+ res['partner_id'] = address.partner_id.id
+ return res
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
new file mode 100644
index 00000000000..bd10a72faf2
--- /dev/null
+++ b/addons/mail/mail_thread_view.xml
@@ -0,0 +1,72 @@
+
+
+
+
+ mail.thread.form
+ mail.thread
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.thread.tree
+ mail.thread
+ tree
+
+
+
+
+
+
+
+
+
+ Email Threads
+ mail.thread
+ tree,form
+ form
+
+
+
+
+ tree
+
+
+
+
+
+ form
+
+
+
+
+
+
diff --git a/addons/mail_gateway/res_partner.py b/addons/mail/res_partner.py
similarity index 86%
rename from addons/mail_gateway/res_partner.py
rename to addons/mail/res_partner.py
index c6f41ff0414..637922ff75c 100644
--- a/addons/mail_gateway/res_partner.py
+++ b/addons/mail/res_partner.py
@@ -19,17 +19,16 @@
#
##############################################################################
-from osv import fields,osv
+from osv import osv
+from osv import fields
class res_partner(osv.osv):
""" Inherits partner and adds CRM information in the partner form """
_inherit = 'res.partner'
_columns = {
- 'emails': fields.one2many('mailgate.message', 'partner_id',\
- 'Emails', readonly=True, domain=[('history','=',True)]),
+ 'emails': fields.one2many('mail.message', 'partner_id', 'Emails', readonly=True, domain=[('email_from','!=',False)]),
}
res_partner()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml
new file mode 100644
index 00000000000..21da7d735b7
--- /dev/null
+++ b/addons/mail/res_partner_view.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ res.partner.crm.history.inherit1
+ res.partner
+ form
+
+
+
+ False
+
+
+
+
+ res.partner.emails.info.inherit
+ res.partner
+ form
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/mail_gateway/scripts/__init__.py b/addons/mail/scripts/__init__.py
similarity index 91%
rename from addons/mail_gateway/scripts/__init__.py
rename to addons/mail/scripts/__init__.py
index 9d7f6bf56c9..3b6a8c7e4b0 100644
--- a/addons/mail_gateway/scripts/__init__.py
+++ b/addons/mail/scripts/__init__.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
+# Copyright (C) 2009-2010 OpenERP SA ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,6 +19,4 @@
#
##############################################################################
-import openerp_mailgate
-
-
+import openerp_mailgate
\ No newline at end of file
diff --git a/addons/mail_gateway/scripts/openerp_mailgate/__init__.py b/addons/mail/scripts/openerp_mailgate/__init__.py
similarity index 93%
rename from addons/mail_gateway/scripts/openerp_mailgate/__init__.py
rename to addons/mail/scripts/openerp_mailgate/__init__.py
index 4f7fae0a190..de2e2bf390d 100644
--- a/addons/mail_gateway/scripts/openerp_mailgate/__init__.py
+++ b/addons/mail/scripts/openerp_mailgate/__init__.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
+# Copyright (C) 2009-2010 OpenERP SA ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
diff --git a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py b/addons/mail/scripts/openerp_mailgate/openerp_mailgate.py
similarity index 86%
rename from addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py
rename to addons/mail/scripts/openerp_mailgate/openerp_mailgate.py
index bcff408ef46..68ae20d35e3 100755
--- a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py
+++ b/addons/mail/scripts/openerp_mailgate/openerp_mailgate.py
@@ -46,13 +46,14 @@ class DefaultConfig(object):
OPENERP_PORT = 8069
OPENERP_DEFAULT_DATABASE = 'openerp'
MAIL_ERROR = 'error@example.com'
- MAIL_SERVER = 'localhost'
- MAIL_ADMINS = ('admin@example.com',)
+ MAIL_SERVER = 'smtp.example.com'
+ MAIL_SERVER_PORT = 25
+ MAIL_ADMINS = ('info@example.com',)
config = DefaultConfig()
-def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER):
+def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER, port=config.MAIL_SERVER_PORT):
assert isinstance(to_, (list, tuple))
if files is None:
@@ -74,7 +75,7 @@ def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER)
% file_name)
msg.attach(part)
- smtp = smtplib.SMTP(server)
+ smtp = smtplib.SMTP(server, port=port)
smtp.sendmail(_from_, to_, msg.as_string() )
smtp.close()
@@ -104,16 +105,15 @@ class EmailParser(object):
self.email_default = email_default
- def parse(self, message, custom_values=None):
- if custom_values is None:
- custom_values = {}
+ def parse(self, message, custom_values=None, save_original=None):
# pass message as bytes because we don't know its encoding until we parse its headers
# and hence can't convert it to utf-8 for transport
- self.rpc('email.server.tools',
- 'process_email',
- self.model,
- xmlrpclib.Binary(message),
- custom_values)
+ res_id = self.rpc('mail.thread',
+ 'message_process',
+ self.model,
+ xmlrpclib.Binary(message),
+ custom_values or {},
+ save_original or False)
def configure_parser():
parser = optparse.OptionParser(usage='usage: %prog [options]', version='%prog v1.1')
@@ -145,6 +145,10 @@ def configure_parser():
parser.add_option("--custom-values", dest="custom_values",
help="Add Custom Values to the object",
default=None)
+ parser.add_option("-s", dest="save_original",
+ action="store_true",
+ help="Attach a copy of original email to the message entry",
+ default=False)
return parser
@@ -171,10 +175,11 @@ def main():
try:
custom_values = dict(eval(options.custom_values or {} ))
except:
- pass
+ import traceback
+ traceback.print_exc()
try:
- email_parser.parse(msg_txt, custom_values)
+ email_parser.parse(msg_txt, custom_values, options.save_original or False)
except Exception:
msg = '\n'.join([
'parameters',
@@ -191,6 +196,7 @@ def main():
config.MAIL_ADMINS,
subject, msg, files=[('message.txt', msg_txt)]
)
+ sys.stderr.write("Failed to deliver email to OpenERP Server, sending error notification to %s\n" % config.MAIL_ADMINS)
if __name__ == '__main__':
main()
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
new file mode 100644
index 00000000000..b508cd68d83
--- /dev/null
+++ b/addons/mail/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_mail_message","mail.message","model_mail_message",,1,0,0,0
+"access_mail_thread","mail.thread","model_mail_thread",,1,0,0,0
diff --git a/addons/mail/wizard/__init__.py b/addons/mail/wizard/__init__.py
new file mode 100644
index 00000000000..88b9581f68e
--- /dev/null
+++ b/addons/mail/wizard/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import mail_compose_message
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..0bfaf06890a
--- /dev/null
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -0,0 +1,272 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import re
+
+import tools
+from mail.mail_message import to_email
+from osv import osv
+from osv import fields
+from tools.safe_eval import safe_eval as eval
+from tools.safe_eval import literal_eval
+from tools.translate import _
+
+# main mako-like expression pattern
+EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
+
+class mail_compose_message(osv.osv_memory):
+ """Generic E-mail composition wizard. This wizard is meant to be inherited
+ at model and view level to provide specific wizard features.
+
+ The behavior of the wizard can be modified through the use of context
+ parameters, among which are:
+
+ * mail.compose.message.mode: if set to 'reply', the wizard is in
+ reply mode and pre-populated with the original quote.
+ If set to 'mass_mail', the wizard is in mass mailing
+ where the mail details can contain template placeholders
+ that will be merged with actual data before being sent
+ to each recipient. Recipients will be derived from the
+ records determined via ``context['active_model']`` and
+ ``context['active_ids']``.
+ * active_model: model name of the document to which the mail being
+ composed is related
+ * active_id: id of the document to which the mail being composed is
+ related, or id of the message to which user is replying,
+ in case ``mail.compose.message.mode == 'reply'``
+ * active_ids: ids of the documents to which the mail being composed is
+ related, in case ``mail.compose.message.mode == 'mass_mail'``.
+ """
+ _name = 'mail.compose.message'
+ _inherit = 'mail.message.common'
+ _description = 'E-mail composition wizard'
+
+ def default_get(self, cr, uid, fields, context=None):
+ """Overridden to provide specific defaults depending on the context
+ parameters.
+
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ """
+ if context is None:
+ context = {}
+ result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
+ vals = {}
+ reply_mode = context.get('mail.compose.message.mode') == 'reply'
+ if (not reply_mode) and context.get('active_model') and context.get('active_id'):
+ # normal mode when sending an email related to any document, as specified by
+ # active_model and active_id in context
+ vals = self.get_value(cr, uid, context.get('active_model'), context.get('active_id'), context)
+ elif reply_mode and context.get('active_id'):
+ # reply mode, consider active_id is the ID of a mail.message to which we're
+ # replying
+ vals = self.get_message_data(cr, uid, int(context['active_id']), context)
+ else:
+ # default mode
+ result['model'] = context.get('active_model', False)
+ if vals:
+ for field in fields:
+ result.update({field : vals.get(field, False)})
+
+ # link to model and record if not done yet
+ if not result.get('model') or not result.get('res_id'):
+ active_model = context.get('active_model')
+ res_id = context.get('active_id')
+ if active_model and active_model not in (self._name, 'mail.message'):
+ result['model'] = active_model
+ if res_id:
+ result['res_id'] = res_id
+
+ # Try to provide default email_from if not specified yet
+ if not result.get('email_from'):
+ current_user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ result['email_from'] = current_user.user_email or False
+ return result
+
+ _columns = {
+ 'attachment_ids': fields.many2many('ir.attachment','email_message_send_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
+ 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"),
+ 'filter_id': fields.many2one('ir.filters', 'Filters'),
+ }
+
+ def get_value(self, cr, uid, model, res_id, context=None):
+ """Returns a defaults-like dict with initial values for the composition
+ wizard when sending an email related to the document record identified
+ by ``model`` and ``res_id``.
+
+ The default implementation returns an empty dictionary, and is meant
+ to be overridden by subclasses.
+
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ """
+ return {}
+
+ def get_message_data(self, cr, uid, message_id, context=None):
+ """Returns a defaults-like dict with initial values for the composition
+ wizard when replying to the given message (e.g. including the quote
+ of the initial message, and the correct recipient).
+ Should not be called unless ``context['mail.compose.message.mode'] == 'reply'``.
+
+ :param int message_id: id of the mail.message to which the user
+ is replying.
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ When calling this method, the ``'mail'`` value
+ in the context should be ``'reply'``.
+ """
+ if context is None:
+ context = {}
+ result = {}
+ mail_message = self.pool.get('mail.message')
+ if message_id:
+ message_data = mail_message.browse(cr, uid, message_id, context)
+ subject = tools.ustr(message_data.subject or '')
+ # we use the plain text version of the original mail, by default,
+ # as it is easier to quote than the HTML version.
+ # XXX TODO: make it possible to switch to HTML on the fly
+ current_user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ body = message_data.body_text or current_user.signature
+ if context.get('mail.compose.message.mode') == 'reply':
+ sent_date = _('On %(date)s, ') % {'date': message_data.date} if message_data.date else ''
+ sender = _('%(sender_name)s wrote:') % {'sender_name': tools.ustr(message_data.email_from or _('You'))}
+ quoted_body = '> %s' % tools.ustr(body.replace('\n', "\n> ") or '')
+ body = '\n'.join(["\n", (sent_date + sender), quoted_body])
+ body += "\n" + current_user.signature
+ re_prefix = _("Re:")
+ if not (subject.startswith('Re:') or subject.startswith(re_prefix)):
+ subject = "%s %s" % (re_prefix, subject)
+ result.update({
+ 'subtype' : 'plain', # default to the text version due to quoting
+ 'body_text' : body,
+ 'subject' : subject,
+ 'attachment_ids' : [],
+ 'model' : message_data.model or False,
+ 'res_id' : message_data.res_id or False,
+ 'email_from' : current_user.user_email or message_data.email_to or False,
+ 'email_to' : message_data.reply_to or message_data.email_from or False,
+ 'email_cc' : message_data.email_cc or False,
+ 'user_id' : uid,
+
+ # pass msg-id and references of mail we're replying to, to construct the
+ # new ones later when sending
+ 'message_id' : message_data.message_id or False,
+ 'references' : message_data.references and tools.ustr(message_data.references) or False,
+ })
+ return result
+
+ def send_mail(self, cr, uid, ids, context=None):
+ '''Process the wizard contents and proceed with sending the corresponding
+ email(s), rendering any template patterns on the fly if needed.
+ If the wizard is in mass-mail mode (context['mail.compose.message.mode'] is
+ set to ``'mass_mail'``), the resulting email(s) are scheduled for being
+ sent the next time the mail.message scheduler runs, or the next time
+ ``mail.message.process_email_queue`` is called.
+ Otherwise the new message is sent immediately.
+
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ '''
+ if context is None:
+ context = {}
+ mail_message = self.pool.get('mail.message')
+ for mail in self.browse(cr, uid, ids, context=context):
+ attachment = {}
+ for attach in mail.attachment_ids:
+ attachment[attach.datas_fname] = attach.datas
+ references = None
+ headers = {}
+
+ body = mail.body_html if mail.subtype == 'html' else mail.body_text
+
+ # Reply Email
+ if context.get('mail.compose.message.mode') == 'reply' and mail.message_id:
+ references = (mail.references or '') + " " + mail.message_id
+ headers['In-Reply-To'] = mail.message_id
+
+ if context.get('mail.compose.message.mode') == 'mass_mail':
+ # Mass mailing: must render the template patterns
+ if context.get('active_ids') and context.get('active_model'):
+ active_ids = context['active_ids']
+ active_model = context['active_model']
+ else:
+ active_model = mail.model
+ active_model_pool = self.pool.get(active_model)
+ active_ids = active_model_pool.search(cr, uid, literal_eval(mail.filter_id.domain), context=literal_eval(mail.filter_id.context))
+
+ for active_id in active_ids:
+ subject = self.render_template(cr, uid, mail.subject, active_model, active_id)
+ rendered_body = self.render_template(cr, uid, body, active_model, active_id)
+ email_from = self.render_template(cr, uid, mail.email_from, active_model, active_id)
+ email_to = self.render_template(cr, uid, mail.email_to, active_model, active_id)
+ email_cc = self.render_template(cr, uid, mail.email_cc, active_model, active_id)
+ email_bcc = self.render_template(cr, uid, mail.email_bcc, active_model, active_id)
+ reply_to = self.render_template(cr, uid, mail.reply_to, active_model, active_id)
+
+ # in mass-mailing mode we only schedule the mail for sending, it will be
+ # processed as soon as the mail scheduler runs.
+ mail_message.schedule_with_attach(cr, uid, email_from, to_email(email_to), subject, rendered_body,
+ model=mail.model, email_cc=to_email(email_cc), email_bcc=to_email(email_bcc), reply_to=reply_to,
+ attachments=attachment, references=references, res_id=int(mail.res_id),
+ subtype=mail.subtype, headers=headers, context=context)
+ else:
+ # normal mode - no mass-mailing
+ msg_id = mail_message.schedule_with_attach(cr, uid, mail.email_from, to_email(mail.email_to), mail.subject, body,
+ model=mail.model, email_cc=to_email(mail.email_cc), email_bcc=to_email(mail.email_bcc), reply_to=mail.reply_to,
+ attachments=attachment, references=references, res_id=int(mail.res_id),
+ subtype=mail.subtype, headers=headers, context=context)
+ # in normal mode, we send the email immediately, as the user expects us to (delay should be sufficiently small)
+ mail_message.send(cr, uid, [msg_id], context)
+
+ return {'type': 'ir.actions.act_window_close'}
+
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ """Render the given template text, replace mako-like expressions ``${expr}``
+ with the result of evaluating these expressions with an evaluation context
+ containing:
+
+ * ``user``: browse_record of the current user
+ * ``object``: browse_record of the document record this mail is
+ related to
+ * ``context``: the context passed to the mail composition wizard
+
+ :param str template: the template text to render
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ """
+ if context is None:
+ context = {}
+ def merge(match):
+ exp = str(match.group()[2:-1]).strip()
+ result = eval(exp,
+ {
+ 'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context),
+ 'object' : self.pool.get(model).browse(cr, uid, res_id, context=context),
+ 'context': dict(context), # copy context to prevent side-effects of eval
+ })
+ if result in (None, False):
+ return ""
+ return tools.ustr(result)
+ return template and EXPRESSION_PATTERN.sub(merge, template)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
new file mode 100644
index 00000000000..d559d679c8b
--- /dev/null
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -0,0 +1,60 @@
+
+
+
+
+ mail.compose.message.form
+ mail.compose.message
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compose Email
+ mail.compose.message
+ mail.compose.message
+ ir.actions.act_window
+ form
+ form
+ new
+
+
+
+
+
+
diff --git a/addons/mail_gateway/mail_gateway.py b/addons/mail_gateway/mail_gateway.py
deleted file mode 100644
index 3afc3fb6ba0..00000000000
--- a/addons/mail_gateway/mail_gateway.py
+++ /dev/null
@@ -1,603 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ()
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see
-#
-##############################################################################
-
-from osv import osv, fields
-import time
-import tools
-import binascii
-import email
-from email.header import decode_header
-from email.utils import parsedate
-import base64
-import re
-from tools.translate import _
-import logging
-import xmlrpclib
-
-_logger = logging.getLogger('mailgate')
-
-class mailgate_thread(osv.osv):
- '''
- Mailgateway Thread
- '''
- _name = 'mailgate.thread'
- _description = 'Mailgateway Thread'
-
- _columns = {
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', readonly=True),
- }
-
- def copy(self, cr, uid, id, default=None, context=None):
- """
- Overrides orm copy method.
- @param self: the object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param id: Id of mailgate thread
- @param default: Dictionary of default values for copy.
- @param context: A standard dictionary for contextual values
- """
- if default is None:
- default = {}
-
- default.update({
- 'message_ids': [],
- 'date_closed': False,
- 'date_open': False
- })
- return super(mailgate_thread, self).copy(cr, uid, id, default, context=context)
-
- def message_new(self, cr, uid, msg, context):
- raise Exception, _('Method is not implemented')
-
- def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
- raise Exception, _('Method is not implemented')
-
- def message_followers(self, cr, uid, ids, context=None):
- """ Get a list of emails of the people following this thread
- """
- res = {}
- if isinstance(ids, (str, int, long)):
- ids = [long(ids)]
- for thread in self.browse(cr, uid, ids, context=context):
- l=[]
- for message in thread.message_ids:
- l.append((message.user_id and message.user_id.user_email) or '')
- l.append(message.email_from or '')
- l.append(message.email_cc or '')
- res[thread.id] = l
- return res
-
- def msg_send(self, cr, uid, id, *args, **argv):
- raise Exception, _('Method is not implemented')
-
- def history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, \
- email_from=False, message_id=False, references=None, attach=None, email_cc=None, \
- email_bcc=None, email_date=None, context=None):
- """
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param cases: a browse record list
- @param keyword: Subject of the history item
- @param history: Value True/False, If True it makes entry in case History otherwise in Case Log
- @param email: Email-To / Recipient address
- @param email_from: Email From / Sender address if any
- @param email_cc: Comma-Separated list of Carbon Copy Emails To addresse if any
- @param email_bcc: Comma-Separated list of Blind Carbon Copy Emails To addresses if any
- @param email_date: Email Date string if different from now, in server Timezone
- @param details: Description, Details of case history if any
- @param atach: Attachment sent in email
- @param context: A standard dictionary for contextual values"""
- if context is None:
- context = {}
- if attach is None:
- attach = []
-
- if email_date:
- edate = parsedate(email_date)
- if edate is not None:
- email_date = time.strftime('%Y-%m-%d %H:%M:%S', edate)
-
- # The mailgate sends the ids of the cases and not the object list
-
- if all(isinstance(case_id, (int, long)) for case_id in cases):
- cases = self.browse(cr, uid, cases, context=context)
-
- att_obj = self.pool.get('ir.attachment')
- obj = self.pool.get('mailgate.message')
-
- for case in cases:
- attachments = []
- for att in attach:
- att_ids = att_obj.search(cr, uid, [('name','=',att[0]), ('res_id', '=', case.id)])
- if att_ids:
- attachments.append(att_ids[0])
- else:
- attachments.append(att_obj.create(cr, uid, {'res_model':case._name,'res_id':case.id, 'name': att[0], 'datas': base64.encodestring(att[1])}))
-
- partner_id = hasattr(case, 'partner_id') and (case.partner_id and case.partner_id.id or False) or False
- if not partner_id and case._name == 'res.partner':
- partner_id = case.id
- data = {
- 'name': keyword,
- 'user_id': uid,
- 'model' : case._name,
- 'partner_id': partner_id,
- 'res_id': case.id,
- 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
- 'message_id': message_id,
- 'description': details,
- 'attachment_ids': [(6, 0, attachments)]
- }
-
- if history:
- for param in (email, email_cc, email_bcc):
- if isinstance(param, list):
- param = ", ".join(param)
-
- data = {
- 'name': subject or _('History'),
- 'history': True,
- 'user_id': uid,
- 'model' : case._name,
- 'res_id': case.id,
- 'date': email_date or time.strftime('%Y-%m-%d %H:%M:%S'),
- 'description': details or (hasattr(case, 'description') and case.description or False),
- 'email_to': email,
- 'email_from': email_from or \
- (hasattr(case, 'user_id') and case.user_id and case.user_id.user_email),
- 'email_cc': email_cc,
- 'email_bcc': email_bcc,
- 'partner_id': partner_id,
- 'references': references,
- 'message_id': message_id,
- 'attachment_ids': [(6, 0, attachments)]
- }
-
- obj.create(cr, uid, data, context=context)
- return True
-mailgate_thread()
-
-def format_date_tz(date, tz=None):
- if not date:
- return 'n/a'
- format = tools.DEFAULT_SERVER_DATETIME_FORMAT
- return tools.server_to_local_timestamp(date, format, format, tz)
-
-class mailgate_message(osv.osv):
- '''
- Mailgateway Message
- '''
- def open_document(self, cr, uid, ids, context=None):
- """ To Open Document
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: the ID of messages
- @param context: A standard dictionary
- """
- action_data = False
- if ids:
- message_id = ids[0]
- mailgate_data = self.browse(cr, uid, message_id, context=context)
- model = mailgate_data.model
- res_id = mailgate_data.res_id
-
- action_pool = self.pool.get('ir.actions.act_window')
- action_ids = action_pool.search(cr, uid, [('res_model', '=', model)])
- if action_ids:
- action_data = action_pool.read(cr, uid, action_ids[0], context=context)
- action_data.update({
- 'domain' : "[('id','=',%d)]"%(res_id),
- 'nodestroy': True,
- 'context': {}
- })
- return action_data
-
- def open_attachment(self, cr, uid, ids, context=None):
- """ To Open attachments
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: the ID of messages
- @param context: A standard dictionary
- """
- action_data = False
- action_pool = self.pool.get('ir.actions.act_window')
- message_pool = self.browse(cr ,uid, ids, context=context)[0]
- att_ids = [x.id for x in message_pool.attachment_ids]
- action_ids = action_pool.search(cr, uid, [('res_model', '=', 'ir.attachment')])
- if action_ids:
- action_data = action_pool.read(cr, uid, action_ids[0], context=context)
- action_data.update({
- 'domain': [('id','in',att_ids)],
- 'nodestroy': True
- })
- return action_data
-
- def truncate_data(self, cr, uid, data, context=None):
- data_list = data and data.split('\n') or []
- if len(data_list) > 3:
- res = '\n\t'.join(data_list[:3]) + '...'
- else:
- res = '\n\t'.join(data_list)
- return res
-
- def _get_display_text(self, cr, uid, ids, name, arg, context=None):
- if context is None:
- context = {}
- tz = context.get('tz')
- result = {}
- for message in self.browse(cr, uid, ids, context=context):
- msg_txt = ''
- if message.history:
- msg_txt += _('%s wrote on %s: \n Subject: %s \n\t') % (message.email_from or '/', format_date_tz(message.date, tz), message.name)
- if message.description:
- msg_txt += self.truncate_data(cr, uid, message.description, context=context)
- else:
- msg_txt = _('%s on %s:\n\t') % (message.user_id.name or '/', format_date_tz(message.date, tz))
- msg_txt += message.name
- result[message.id] = msg_txt
- return result
-
- _name = 'mailgate.message'
- _description = 'Mailgateway Message'
- _order = 'date desc'
- _columns = {
- 'name':fields.text('Subject', readonly=True),
- 'model': fields.char('Object Name', size=128, select=1, readonly=True),
- 'res_id': fields.integer('Resource ID', select=1, readonly=True),
- 'ref_id': fields.char('Reference Id', size=256, readonly=True, help="Message Id in Email Server.", select=True),
- 'date': fields.datetime('Date', readonly=True),
- 'history': fields.boolean('Is History?', readonly=True),
- 'user_id': fields.many2one('res.users', 'User Responsible', readonly=True),
- 'message': fields.text('Message', readonly=True),
- 'email_from': fields.char('From', size=128, help="Email From", readonly=True),
- 'email_to': fields.char('To', help="Email Recipients", size=256, readonly=True),
- 'email_cc': fields.char('Cc', help="Carbon Copy Email Recipients", size=256, readonly=True),
- 'email_bcc': fields.char('Bcc', help='Blind Carbon Copy Email Recipients', size=256, readonly=True),
- 'message_id': fields.char('Message Id', size=1024, readonly=True, help="Message Id on Email.", select=True),
- 'references': fields.text('References', readonly=True, help="References emails."),
- 'description': fields.text('Description', readonly=True),
- 'partner_id': fields.many2one('res.partner', 'Partner', required=False),
- 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments', readonly=True),
- 'display_text': fields.function(_get_display_text, type='text', size="512", string='Display Text'),
- }
-
- def init(self, cr):
- cr.execute("""SELECT indexname
- FROM pg_indexes
- WHERE indexname = 'mailgate_message_res_id_model_idx'""")
- if not cr.fetchone():
- cr.execute("""CREATE INDEX mailgate_message_res_id_model_idx
- ON mailgate_message (model, res_id)""")
-
-mailgate_message()
-
-class mailgate_tool(osv.osv_memory):
-
- _name = 'email.server.tools'
- _description = "Email Server Tools"
-
- def _decode_header(self, text):
- """Returns unicode() string conversion of the the given encoded smtp header"""
- if text:
- text = decode_header(text.replace('\r', ''))
- return ''.join([tools.ustr(x[0], x[1]) for x in text])
-
- def to_email(self,text):
- return re.findall(r'([^ ,<@]+@[^> ,]+)',text)
-
- def history(self, cr, uid, model, res_ids, msg, attach, context=None):
- """This function creates history for mails fetched
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param model: OpenObject Model
- @param res_ids: Ids of the record of OpenObject model created
- @param msg: Email details
- @param attach: Email attachments
- """
- if isinstance(res_ids, (int, long)):
- res_ids = [res_ids]
-
- msg_pool = self.pool.get('mailgate.message')
- for res_id in res_ids:
- case = self.pool.get(model).browse(cr, uid, res_id, context=context)
- partner_id = hasattr(case, 'partner_id') and (case.partner_id and case.partner_id.id or False) or False
- if not partner_id and model == 'res.partner':
- partner_id = res_id
- msg_data = {
- 'name': msg.get('subject', 'No subject'),
- 'date': msg.get('date'),
- 'description': msg.get('body', msg.get('from')),
- 'history': True,
- 'partner_id': partner_id,
- 'model': model,
- 'email_cc': msg.get('cc'),
- 'email_from': msg.get('from'),
- 'email_to': msg.get('to'),
- 'message_id': msg.get('message-id'),
- 'references': msg.get('references') or msg.get('in-reply-to'),
- 'res_id': res_id,
- 'user_id': uid,
- 'attachment_ids': [(6, 0, attach)]
- }
- msg_pool.create(cr, uid, msg_data, context=context)
- return True
-
- def email_forward(self, cr, uid, model, res_ids, msg, email_error=False, context=None):
- """Sends an email to all people following the thread
- @param res_id: Id of the record of OpenObject model created from the email message
- @param msg: email.message.Message to forward
- @param email_error: Default Email address in case of any Problem
- """
- model_pool = self.pool.get(model)
-
- for res in model_pool.browse(cr, uid, res_ids, context=context):
- message_followers = model_pool.message_followers(cr, uid, [res.id])[res.id]
- message_followers_emails = self.to_email(','.join(filter(None, message_followers)))
- message_recipients = self.to_email(','.join(filter(None,
- [self._decode_header(msg['from']),
- self._decode_header(msg['to']),
- self._decode_header(msg['cc'])])))
- message_forward = [i for i in message_followers_emails if (i and (i not in message_recipients))]
-
- if message_forward:
- # TODO: we need an interface for this for all types of objects, not just leads
- if hasattr(res, 'section_id'):
- del msg['reply-to']
- msg['reply-to'] = res.section_id.reply_to
-
- smtp_from = self.to_email(msg['from'])
- if not tools.misc._email_send(smtp_from, message_forward, msg, openobject_id=res.id) and email_error:
- subj = msg['subject']
- del msg['subject'], msg['to'], msg['cc'], msg['bcc']
- msg['subject'] = '[OpenERP-Forward-Failed] %s' % subj
- msg['to'] = email_error
- tools.misc._email_send(smtp_from, self.to_email(email_error), msg, openobject_id=res.id)
-
- def process_email(self, cr, uid, model, message, custom_values=None, attach=True, context=None):
- """This function Processes email and create record for given OpenERP model
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param model: OpenObject Model
- @param message: Email details, passed as a string or an xmlrpclib.Binary
- @param attach: Email attachments
- @param context: A standard dictionary for contextual values"""
-
- # extract message bytes, we are forced to pass the message as binary because
- # we don't know its encoding until we parse its headers and hence can't
- # convert it to utf-8 for transport between the mailgate script and here.
- if isinstance(message, xmlrpclib.Binary):
- message = str(message.data)
-
- if context is None:
- context = {}
-
- if custom_values is None or not isinstance(custom_values, dict):
- custom_values = {}
-
- model_pool = self.pool.get(model)
- res_id = False
-
- # Create New Record into particular model
- def create_record(msg):
- att_ids = []
- if hasattr(model_pool, 'message_new'):
- res_id = model_pool.message_new(cr, uid, msg, context=context)
- if custom_values:
- model_pool.write(cr, uid, [res_id], custom_values, context=context)
- else:
- data = {
- 'name': msg.get('subject'),
- 'email_from': msg.get('from'),
- 'email_cc': msg.get('cc'),
- 'user_id': False,
- 'description': msg.get('body'),
- 'state' : 'draft',
- }
- data.update(self.get_partner(cr, uid, msg.get('from'), context=context))
- res_id = model_pool.create(cr, uid, data, context=context)
-
- if attach:
- for attachment in msg.get('attachments', []):
- data_attach = {
- 'name': attachment,
- 'datas': binascii.b2a_base64(str(attachments.get(attachment))),
- 'datas_fname': attachment,
- 'description': 'Mail attachment',
- 'res_model': model,
- 'res_id': res_id,
- }
- att_ids.append(self.pool.get('ir.attachment').create(cr, uid, data_attach))
-
- return res_id, att_ids
-
- # Warning: message_from_string doesn't always work correctly on unicode,
- # we must use utf-8 strings here :-(
- if isinstance(message, unicode):
- message = message.encode('utf-8')
- msg_txt = email.message_from_string(message)
- message_id = msg_txt.get('message-id', False)
- msg = {}
-
- if not message_id:
- # Very unusual situation, be we should be fault-tolerant here
- message_id = time.time()
- msg_txt['message-id'] = message_id
- _logger.info('Message without message-id, generating a random one: %s', message_id)
-
- fields = msg_txt.keys()
- msg['id'] = message_id
- msg['message-id'] = message_id
-
- if 'Subject' in fields:
- msg['subject'] = self._decode_header(msg_txt.get('Subject'))
-
- if 'Content-Type' in fields:
- msg['content-type'] = msg_txt.get('Content-Type')
-
- if 'From' in fields:
- msg['from'] = self._decode_header(msg_txt.get('From'))
-
- if 'Delivered-To' in fields:
- msg['to'] = self._decode_header(msg_txt.get('Delivered-To'))
-
- if 'CC' in fields:
- msg['cc'] = self._decode_header(msg_txt.get('CC'))
-
- if 'Reply-to' in fields:
- msg['reply'] = self._decode_header(msg_txt.get('Reply-To'))
-
- if 'Date' in fields:
- msg['date'] = self._decode_header(msg_txt.get('Date'))
-
- if 'Content-Transfer-Encoding' in fields:
- msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
-
- if 'References' in fields:
- msg['references'] = msg_txt.get('References')
-
- if 'In-Reply-To' in fields:
- msg['in-reply-to'] = msg_txt.get('In-Reply-To')
-
- if 'X-Priority' in fields:
- msg['priority'] = msg_txt.get('X-Priority', '3 (Normal)').split(' ')[0]
-
- if not msg_txt.is_multipart() or 'text/plain' in msg.get('Content-Type', ''):
- encoding = msg_txt.get_content_charset()
- body = msg_txt.get_payload(decode=True)
- if 'text/html' in msg_txt.get('Content-Type', ''):
- body = tools.html2plaintext(body)
- msg['body'] = tools.ustr(body, encoding)
-
- attachments = {}
- has_plain_text = False
- if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
- body = ""
- for part in msg_txt.walk():
- if part.get_content_maintype() == 'multipart':
- continue
-
- encoding = part.get_content_charset()
- filename = part.get_filename()
- if part.get_content_maintype()=='text':
- content = part.get_payload(decode=True)
- if filename:
- attachments[filename] = content
- elif not has_plain_text:
- # main content parts should have 'text' maintype
- # and no filename. we ignore the html part if
- # there is already a plaintext part without filename,
- # because presumably these are alternatives.
- content = tools.ustr(content, encoding)
- if part.get_content_subtype() == 'html':
- body = tools.ustr(tools.html2plaintext(content))
- elif part.get_content_subtype() == 'plain':
- body = content
- has_plain_text = True
- elif part.get_content_maintype() in ('application', 'image'):
- if filename and attach:
- attachments[filename] = part.get_payload(decode=True)
- else:
- res = part.get_payload(decode=True)
- body += tools.ustr(res, encoding)
-
- msg['body'] = body
- msg['attachments'] = attachments
- res_ids = []
- attachment_ids = []
- new_res_id = False
- if msg.get('references') or msg.get('in-reply-to'):
- references = msg.get('references') or msg.get('in-reply-to')
- if '\r\n' in references:
- references = references.split('\r\n')
- else:
- references = references.split(' ')
- for ref in references:
- ref = ref.strip()
- res_id = tools.misc.reference_re.search(ref)
- if res_id:
- res_id = res_id.group(1)
- else:
- res_id = tools.misc.res_re.search(msg['subject'])
- if res_id:
- res_id = res_id.group(1)
- if res_id:
- res_id = int(res_id)
- model_pool = self.pool.get(model)
- if model_pool.exists(cr, uid, res_id):
- res_ids.append(res_id)
- if hasattr(model_pool, 'message_update'):
- model_pool.message_update(cr, uid, [res_id], {}, msg, context=context)
- else:
- raise NotImplementedError('model %s does not support updating records, mailgate API method message_update() is missing'%model)
-
- if not len(res_ids):
- new_res_id, attachment_ids = create_record(msg)
- res_ids = [new_res_id]
-
- # Store messages
- context.update({'model' : model})
- if hasattr(model_pool, 'history'):
- model_pool.history(cr, uid, res_ids, _('receive'), history=True,
- subject = msg.get('subject'),
- email = msg.get('to'),
- details = msg.get('body'),
- email_from = msg.get('from'),
- email_cc = msg.get('cc'),
- message_id = msg.get('message-id'),
- references = msg.get('references', False) or msg.get('in-reply-to', False),
- attach = attachments.items(),
- email_date = msg.get('date'),
- context = context)
- else:
- self.history(cr, uid, model, res_ids, msg, attachment_ids, context=context)
- self.email_forward(cr, uid, model, res_ids, msg_txt)
- return new_res_id
-
- def get_partner(self, cr, uid, from_email, context=None):
- """This function returns partner Id based on email passed
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks
- @param from_email: email address based on that function will search for the correct
- """
- address_pool = self.pool.get('res.partner.address')
- res = {
- 'partner_address_id': False,
- 'partner_id': False
- }
- from_email = self.to_email(from_email)[0]
- address_ids = address_pool.search(cr, uid, [('email', 'like', from_email)])
- if address_ids:
- address = address_pool.browse(cr, uid, address_ids[0])
- res['partner_address_id'] = address_ids[0]
- res['partner_id'] = address.partner_id.id
-
- return res
-
-mailgate_tool()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail_gateway/mail_gateway_view.xml b/addons/mail_gateway/mail_gateway_view.xml
deleted file mode 100644
index d05bfbc0d91..00000000000
--- a/addons/mail_gateway/mail_gateway_view.xml
+++ /dev/null
@@ -1,220 +0,0 @@
-
-
-
-
-
-
-
- mailgate.message.form
- mailgate.message
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- mailgate.message.tree
- mailgate.message
- tree
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- mailgate.message.search
- mailgate.message
- search
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Messages
- mailgate.message
- form
- tree,form
-
-
-
-
-
-
-
- mailgate.thread.form
- mailgate.thread
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- mailgate.thread.tree
- mailgate.thread
- tree
-
-
-
-
-
-
-
-
-
-
-
-
- Mailgateway Threads
- mailgate.thread
- tree,form
- form
-
-
-
-
-
- tree
-
-
-
-
-
- form
-
-
-
-
-
-
- Emails
- mailgate.message
- tree,form
- form
- [('history', '=', True)]
-
-
-
-
-
- tree
-
-
-
-
-
- form
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/mail_gateway/res_partner_view.xml b/addons/mail_gateway/res_partner_view.xml
deleted file mode 100644
index b3d956f9fc1..00000000000
--- a/addons/mail_gateway/res_partner_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
- res.partner.crm.history.inherit1
- res.partner
- form
-
-
-
- False
-
-
-
-
- res.partner.emails.info.inherit
- res.partner
- form
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/mail_gateway/security/ir.model.access.csv b/addons/mail_gateway/security/ir.model.access.csv
deleted file mode 100644
index 09f13d57b31..00000000000
--- a/addons/mail_gateway/security/ir.model.access.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"mail_gateway_mailgate_message","mail_gateway.mailgate.message","model_mailgate_message","base.group_system",1,1,1,1
-"mail_gateway_mailgate_thread","mail_gateway.mailgate.thread","model_mailgate_thread","base.group_system",1,1,1,1
-"mail_gateway_message_internal_user","mail_gateway.mailgate.message.internal","model_mailgate_message","base.group_user",1,0,0,0
-"mail_gateway_mailgate_message_partner_manager","mail_gateway.mailgate.message.partner.manager","model_mailgate_message","base.group_partner_manager",1,1,1,1
diff --git a/addons/marketing_campaign/marketing_campaign.py b/addons/marketing_campaign/marketing_campaign.py
index ae1ab2309e2..27c91fcc3fe 100644
--- a/addons/marketing_campaign/marketing_campaign.py
+++ b/addons/marketing_campaign/marketing_campaign.py
@@ -144,13 +144,6 @@ Normal - the campaign runs normally and automatically sends all emails and repor
if activity.signal and len(activity.from_ids) == 0:
has_signal_without_from = True
- if activity.type != 'email':
- continue
- if not activity.email_template_id.from_account:
- raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is missing in email activity '%s'")%activity.name)
- if activity.email_template_id.from_account.state != 'approved':
- raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is not approved in email activity '%s'")%activity.name)
-
if not has_start and not has_signal_without_from:
raise osv.except_osv(_("Error"), _("The campaign cannot be started: it doesn't have any starting activity (or any activity with a signal and no previous activity)"))
@@ -484,9 +477,9 @@ class marketing_campaign_activity(osv.osv):
return True
def _process_wi_email(self, cr, uid, activity, workitem, context=None):
- return self.pool.get('email.template').generate_mail(cr, uid,
+ return self.pool.get('email.template').send_mail(cr, uid,
activity.email_template_id.id,
- [workitem.res_id], context=context)
+ workitem.res_id, context=context)
def _process_wi_action(self, cr, uid, activity, workitem, context=None):
if context is None:
@@ -807,7 +800,7 @@ class marketing_campaign_workitem(osv.osv):
'type': 'ir.actions.act_window',
'target': 'new',
'nodestroy':True,
- 'context': "{'template_id':%d,'default_rel_model_ref':%d}"%
+ 'context': "{'template_id':%d,'default_res_id':%d}"%
(wi_obj.activity_id.email_template_id.id,
wi_obj.res_id)
}
@@ -831,7 +824,7 @@ marketing_campaign_workitem()
class email_template(osv.osv):
_inherit = "email.template"
_defaults = {
- 'object_name': lambda obj, cr, uid, context: context.get('object_id',False),
+ 'model_id': lambda obj, cr, uid, context: context.get('object_id',False),
}
# TODO: add constraint to prevent disabling / disapproving an email account used in a running campaign
diff --git a/addons/marketing_campaign/marketing_campaign_view.xml b/addons/marketing_campaign/marketing_campaign_view.xml
index 63bd16740ec..4ecbda6c4f8 100644
--- a/addons/marketing_campaign/marketing_campaign_view.xml
+++ b/addons/marketing_campaign/marketing_campaign_view.xml
@@ -267,7 +267,7 @@
+ context="{'default_model_id':object_id}" />
diff --git a/addons/marketing_campaign/security/ir.model.access.csv b/addons/marketing_campaign/security/ir.model.access.csv
index c2b7b4f4c86..b0157b76c51 100644
--- a/addons/marketing_campaign/security/ir.model.access.csv
+++ b/addons/marketing_campaign/security/ir.model.access.csv
@@ -7,7 +7,7 @@
"access_marketing_campaign_analysis_campaignadmin","campaign.analysis","model_campaign_analysis","marketing.group_marketing_user",1,1,0,0
"access_marketing_campaign_workitem_all","marketing.campaign.workitem","model_marketing_campaign_workitem","base.group_user",1,0,0,0
"access_email_template_user","email.template","model_email_template","marketing.group_marketing_user",1,1,0,0
-"access_email_template_account_user","email_template.account.user","email_template.model_email_template_account","marketing.group_marketing_user",1,1,0,0
+"access_email_template_account_user","ir.mail_server","base.model_ir_mail_server","marketing.group_marketing_user",1,1,0,0
"access_marketing_campaign_system","marketing.campaign system","model_marketing_campaign","base.group_system",1,0,0,0
"access_marketing_campaign_segment_system","marketing.campaign.segment system","model_marketing_campaign_segment","base.group_system",1,0,0,0
"access_marketing_campaign_workitem_system","marketing.campaign.workitem system","model_marketing_campaign_workitem","base.group_system",1,0,0,0
@@ -18,4 +18,4 @@
"access_marketing_campaign_transition_campaign_manager","marketing.campaign.transition","model_marketing_campaign_transition","marketing.group_marketing_manager",1,1,1,1
"access_marketing_campaign_analysis_campaign_manager","campaign.analysis","model_campaign_analysis","marketing.group_marketing_manager",1,1,1,1
"access_email_template_manager","email.template","model_email_template","marketing.group_marketing_manager",1,1,1,1
-"access_email_template_account_manager","email_template.account.manager","email_template.model_email_template_account","marketing.group_marketing_manager",1,1,1,1
+"access_email_template_account_manager","ir.mail_server","base.model_ir_mail_server","marketing.group_marketing_manager",1,1,1,1
diff --git a/addons/outlook/__openerp__.py b/addons/outlook/__openerp__.py
index 53bd587583c..e376bcfa34a 100644
--- a/addons/outlook/__openerp__.py
+++ b/addons/outlook/__openerp__.py
@@ -25,17 +25,15 @@
'version' : '1.0',
'author' : 'OpenERP SA',
'website' : 'http://www.openerp.com/',
- 'depends' : ['base', 'mail_gateway'],
+ 'depends' : ['base', 'mail'],
'category' : 'Tools',
'description': '''
This module provides the Outlook Plug-in.
=========================================
-
Outlook plug-in allows you to select an object that you’d like to add
to your email and its attachments from MS Outlook. You can select a partner, a task,
a project, an analytical account, or any other object and archive selected
-mail in mailgate.messages with attachments.
-
+mail into email.messages with attachments.
''',
'init_xml' : [],
'demo_xml' : [],
diff --git a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py
index a05220ad7e0..87138554e16 100644
--- a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py
+++ b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py
@@ -186,7 +186,6 @@ def check():
win32ui.MessageBox("No server running on host "+ server+" at port "+str(port), "OpenERP Connection", flag_excl)
return False
if str(NewConn.getitem('_login')) == 'False':
- win32ui.MessageBox("Please login to the database first", "OpenERP Connection", flag_excl)
return False
return True
diff --git a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py
index 247b9c7a533..aa417b2cd59 100644
--- a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py
+++ b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py
@@ -1,7 +1,7 @@
-#C:\workspace\openerp-outlook-plugin\dialogs\resources\dialogs.py
-#This is a generated file. Please edit C:\workspace\openerp-outlook-plugin\dialogs\resources\dialogs.rc instead.
+#C:\Program Files\Openerp Outlook Addin\dialogs\resources\dialogs.py
+#This is a generated file. Please edit C:\Program Files\Openerp Outlook Addin\dialogs\resources\dialogs.rc instead.
_rc_size_=16350
-_rc_mtime_=1296563638
+_rc_mtime_=1296608638
class FakeParser:
dialogs = {'IDD_OPEN_DOCUEMNT_DIALOG': [['Open Document', (0, 0, 200, 65), -1865940928, 1024, (8, 'Tahoma')], [130, 'Link to Document : (If this link does not opens directly in web browser then copy an the link and paste in web browser.) ', -1, (5, 3, 190, 25), 1342177280], [129, '', 2086, (5, 31, 190, 12), 1350631552], [128, 'Ok', 2, (150, 45, 45, 14), 1342242816]], 'IDD_VIEW_PARTNER_DIALOG': [['Open Contact', (0, 0, 350, 215), -1865940928, 1024, (8, 'Tahoma')], [130, 'Email ID : ', -1, (32, 17, 40, 12), 1342177280], [129, '', 2051, (70, 15, 200, 12), 1350631552], [128, 'Search Contact ', 2052, (280, 15, 60, 14), 1342242816], [130, 'Partner Name : ', -1, (13, 42, 50, 17), 1342177280], [129, '', 2064, (70, 42, 150, 12), 1350568064], [128, 'Search Partner', 2070, (225, 41, 60, 14), 1342242816], [128, 'New Partner', 2093, (290, 41, 50, 14), 1342242816], [128, 'Postal Address ', 2024, (10, 65, 175, 125), 1342177287], [130, 'Contact Name : ', -1, (18, 83, 50, 17), 1342177280], [129, '', 2046, (76, 82, 100, 12), 1350631552], [130, 'Street : ', -1, (18, 97, 50, 17), 1342177280], [129, '', 2053, (76, 96, 100, 12), 1350631552], [130, 'Street2 : ', -1, (18, 112, 50, 17), 1342177280], [129, '', 2054, (76, 111, 100, 12), 1350631552], [130, 'Zip : ', -1, (18, 126, 50, 14), 1342177280], [129, '', 40010, (76, 125, 100, 12), 1350631552], [130, 'City : ', -1, (18, 140, 50, 17), 1342177280], [129, '', 40011, (76, 139, 100, 12), 1350631552], [130, 'Fed. State : ', -1, (18, 154, 50, 17), 1342177280], [129, '', 2062, (76, 153, 54, 12), 1350568064], [128, 'Search', 2104, (135, 153, 40, 12), 1342242816], [130, 'Country :', -1, (18, 169, 35, 17), 1342177280], [129, '', 2063, (76, 168, 54, 12), 1350568064], [128, 'Search', 2105, (135, 168, 40, 12), 1342242816], [128, 'Communication ', 2024, (188, 65, 152, 125), 1342177287], [130, 'Phone : ', -1, (194, 83, 30, 17), 1342177280], [129, '', 2048, (224, 82, 100, 12), 1350631552], [130, 'Mobile : ', -1, (194, 97, 30, 17), 1342177280], [129, '', 2050, (224, 96, 100, 12), 1350631552], [130, 'Email : ', -1, (194, 111, 30, 17), 1342177280], [129, '', 2058, (224, 110, 100, 12), 1350568064], [130, 'Fax : ', -1, (194, 125, 30, 17), 1342177280], [129, '', 2047, (224, 124, 100, 12), 1350631552], [128, 'Create a New Contact', 2071, (124, 195, 82, 14), 1342242816], [128, 'Save', 2059, (213, 195, 60, 14), 1342242816], [128, 'Cancel', 2, (281, 195, 60, 14), 1342242816]], 'IDD_OPEN_PARTNER_DIALOG': [['Open Partner', (0, 0, 200, 65), -1865940928, 1024, (8, 'Tahoma')], [130, 'Link to Partner : (If this link does not opens directly in web browser then copy an the link and paste in web browser.) ', -1, (5, 3, 190, 25), 1342177280], [129, '', 2085, (5, 31, 190, 12), 1350631552], [128, 'Ok', 2, (150, 45, 45, 13), 1342242816]], 'IDD_SELECT_STATE': [['Search Fed. State', (0, 0, 220, 250), -1865940928, 1024, (8, 'Tahoma')], [130, 'Enter Name : ', -1, (8, 12, 80, 17), 1342177280], [129, '', 2100, (55, 10, 100, 12), 1350631552], [128, 'Search', 40012, (160, 10, 50, 14), 1342242816], ['SysListView32', 'List1', 2101, (8, 40, 200, 185), 1353711657], [128, 'Select', 2102, (170, 230, 40, 14), 1342242816]], 'IDD_GENERAL': [['Connection Parameters', (0, 0, 430, 210), 1355284544, None, (8, 'Tahoma')], [128, 'Connection Parameters', 2024, (10, 10, 250, 130), 1342177287], [130, 'Server : ', -1, (30, 30, 100, 17), 1342177280], [129, '', 2003, (85, 30, 120, 12), 1350633472], [128, 'Change', 2021, (210, 30, 30, 13), 1342242816], [130, 'Database : ', -1, (30, 50, 100, 17), 1342177280], [133, '', 2004, (85, 50, 154, 40), 1344339971], [130, 'Username : ', -1, (30, 70, 100, 17), 1342177280], [129, '', 2005, (85, 70, 154, 12), 1350631552], [130, 'Password : ', -1, (30, 90, 100, 17), 1342177280], [129, '', 2006, (85, 90, 154, 12), 1350631584], [128, 'Connect', 2007, (179, 110, 60, 13), 1342242816], [128, 'WebServer Parameters', 2024, (270, 10, 150, 130), 1342177287], [130, 'Server : ', -1, (280, 30, 50, 15), 1342177280], [129, '', 2080, (310, 29, 90, 12), 1350568064], [128, 'Change', 2082, (295, 50, 50, 12), 1342242816], [128, 'Connect', 2083, (350, 50, 50, 12), 1342242816]], 'IDD_OBJECT_SETTINGS': [['Documents Setting', (0, 0, 430, 210), 1355284672, 1024, (8, 'Tahoma')], [128, 'Document Attributes', 2024, (2, 2, 343, 32), 1342177287], [130, 'Title:', -1, (5, 16, 30, 17), 1342177280], [129, '', 2013, (22, 14, 57, 12), 1350631552], [130, 'Document Name:', -1, (83, 16, 60, 17), 1342177280], [129, '', 2014, (138, 14, 57, 12), 1350631552], [130, 'Image:', -1, (200, 16, 22, 17), 1342177280], [129, '', 2015, (223, 14, 57, 12), 1350631552], [128, 'Load Image', 2010, (289, 13, 50, 15), 1342242816], [128, 'Add', 2011, (350, 13, 38, 15), 1342242816], [128, 'Delete', 2012, (392, 13, 38, 15), 1342242816], ['SysListView32', 'List1', 2016, (8, 40, 535, 160), 1353711657]], 'IDD_NEW_PARTNER_DIALOG': [['Create a New Partner', (0, 0, 140, 40), -1865940928, 1024, (8, 'Tahoma')], [130, 'Name : ', -1, (5, 3, 100, 17), 1342177280], [129, '', 2036, (40, 3, 94, 12), 1350631552], [128, 'Cancel', 2, (90, 22, 45, 14), 1342242816], [128, 'Save', 2035, (40, 22, 45, 14), 1342242817]], 'IDD_MANAGER': [['OpenERP Configuration', (0, 0, 460, 260), -1865940800, None, (8, 'Tahoma')], [128, 'Close', 2008, (400, 239, 50, 14), 1342177281], ['SysTabControl32', '', 1068, (8, 7, 440, 228), 1342177280], ['SysListView32', '', 2016, (0, 0, 0, 0), 1353711657]], 'IDD_WEB_SERVER_PORT_DIALOG': [['OpenERP Connection', (0, 0, 160, 80), -1865940928, 1024, (8, 'Tahoma')], [130, 'Server : ', -1, (5, 3, 150, 17), 1342177280], [129, '', 2089, (40, 3, 100, 12), 1350631552], [130, 'Port : ', -1, (5, 18, 100, 17), 1342177280], [129, '', 2090, (40, 18, 100, 12), 1350631552], [128, 'Close', 2, (110, 50, 45, 14), 1342242816], [128, 'OK', 2091, (50, 50, 50, 14), 1342242817], [128, 'SSL (https)', 2111, (20, 35, 100, 14), 1342242819]], 'IDD_ABOUT': [['About', (0, 0, 430, 210), 1355284672, 1024, (8, 'Tahoma')], [128, 'About Plugin', -1, (7, 3, 422, 200), 1342177287], [130, '1062', 1062, (60, 30, 20, 20), 1342179342], [130, '', 2028, (80, 90, 300, 100), 1342177280]], 'IDD_NEW_CONTACT_DIALOG': [['Create a New Contact', (0, 0, 350, 180), -1865940928, 1024, (8, 'Tahoma')], [130, 'Select Partner : ', -1, (13, 20, 50, 17), 1342177280], [129, '', 2079, (70, 18, 150, 12), 1350568064], [128, 'Search Partner', 2033, (225, 17, 60, 14), 1342242816], [128, 'New Partner', 2092, (290, 17, 50, 14), 1342242816], [128, 'Postal Address ', 2024, (10, 35, 175, 123), 1342177287], [130, 'Contact Name : ', -1, (18, 53, 100, 17), 1342177280], [129, '', 40005, (76, 52, 100, 12), 1350631552], [130, 'Street : ', -1, (18, 67, 40, 17), 1342177280], [129, '', 2062, (76, 66, 100, 12), 1350631552], [130, 'Street2 : ', -1, (18, 81, 40, 17), 1342177280], [129, '', 2063, (76, 80, 100, 12), 1350631552], [130, 'Zip : ', -1, (18, 94, 50, 17), 1342177280], [129, '', 2068, (76, 93, 100, 12), 1350631552], [130, 'City : ', -1, (18, 106, 50, 17), 1342177280], [129, '', 2067, (76, 107, 100, 12), 1350631552], [130, 'Fed. State : ', -1, (18, 134, 50, 15), 1342177280], [129, '', 2106, (76, 135, 54, 12), 1350568064], [128, 'Search', 2107, (135, 136, 40, 12), 1342242816], [130, 'Country :', -1, (18, 121, 35, 15), 1342177280], [129, '', 2108, (76, 122, 54, 12), 1350568064], [128, 'Search', 2109, (135, 121, 40, 12), 1342242816], [128, 'Communication ', 2024, (188, 35, 150, 123), 1342177287], [130, 'Office : ', -1, (194, 53, 30, 17), 1342177280], [129, '', 40006, (224, 52, 100, 12), 1350631552], [130, 'Mobile : ', -1, (194, 67, 30, 17), 1342177280], [129, '', 40007, (224, 66, 100, 12), 1350631552], [130, 'Fax : ', -1, (194, 81, 30, 17), 1342177280], [129, '', 2066, (224, 80, 100, 12), 1350631552], [130, 'Email : ', -1, (194, 95, 30, 17), 1342177280], [129, '', 40008, (224, 94, 100, 12), 1350631552], [128, 'Cancel', 2, (290, 162, 45, 13), 1342242816], [128, 'Save', 40009, (240, 162, 45, 13), 1342242816]], 'IDD_SERVER_PORT_DIALOG': [['OpenERP Connection', (0, 0, 160, 90), -1865940928, 1024, (8, 'Tahoma')], [130, 'Server : ', -1, (5, 3, 150, 17), 1342177280], [129, '', 2001, (45, 3, 100, 12), 1350631552], [130, ' Port : ', -1, (5, 18, 100, 17), 1342177280], [129, '', 2002, (45, 18, 100, 12), 1350631552], [128, '', 2024, (5, 35, 150, 5), 1342177287], [130, 'Protocol Connection :', -1, (5, 45, 80, 17), 1342177280], [133, '', 2110, (75, 43, 75, 50), 1344339971], [128, 'Close', 2, (60, 70, 45, 14), 1342242816], [128, 'OK', 1, (110, 70, 45, 14), 1342242817]], 'IDD_SELECT_PARTNER': [['Search Partner', (0, 0, 220, 250), -1865940928, 1024, (8, 'Tahoma')], [130, 'Enter Name : ', -1, (8, 12, 80, 17), 1342177280], [129, '', 2076, (55, 10, 100, 12), 1350631552], [128, 'Search', 2077, (160, 10, 50, 14), 1342242816], ['SysListView32', 'List1', 2072, (8, 40, 200, 185), 1353711657], [128, 'Create New Partner', 2078, (10, 230, 100, 14), 1342242816], [128, 'Select', 2075, (170, 230, 40, 14), 1342242816]], 'IDD_SYNC': [['Push to OpenERP', (0, 0, 470, 320), -1865940928, 1024, (8, 'Tahoma')], [128, 'Link to an Existing Documents ', 2024, (8, 5, 250, 290), 1342242823], [130, 'Search : ', -1, (15, 17, 40, 12), 1342177280], [129, '', 40003, (60, 15, 120, 12), 1350631552], [128, 'Search', 40004, (187, 15, 40, 14), 1342242816], [130, 'Documents : ', -1, (15, 140, 100, 14), 1342177280], ['SysListView32', 'List1', 2026, (15, 150, 234, 110), 1350631433], [128, 'Push', 2019, (160, 270, 85, 14), 1342242816], [128, ' Create a New Document ', 2024, (263, 5, 202, 100), 1342242823], [130, 'Type of Document : ', -1, (266, 25, 100, 12), 1342177280], [133, '', 2025, (332, 24, 75, 45), 1344339971], [128, 'Create', 2020, (412, 23, 50, 14), 1342242816], [128, ' Create a New Contact ', 2024, (263, 110, 202, 185), 1342242823], [130, 'Create a New Contact : ', -1, (280, 140, 100, 12), 1342177280], [128, 'Create Contact', 2018, (360, 138, 60, 14), 1342242816], [128, 'Close', 2, (385, 300, 60, 14), 1342242816]], 'IDD_SELECT_COUNTRY': [['Search Country', (0, 0, 220, 250), -1865940928, 1024, (8, 'Tahoma')], [130, 'Enter Name : ', -1, (8, 12, 80, 17), 1342177280], [129, '', 2095, (55, 10, 100, 12), 1350631552], [128, 'Search', 2098, (160, 10, 50, 14), 1342242816], ['SysListView32', 'List1', 2096, (8, 40, 200, 185), 1353711657], [128, 'Select', 2097, (170, 230, 40, 14), 1342242816]]}
ids = {'IDC_BUT_DEL_OBJECT': 2012, 'IDD_OPEN_PARTNER_DIALOG': 2084, 'IDD_SELECT_STATE': 2099, 'IDC_DELAY1_SLIDER': 1056, 'IDC_PROGRESS': 1000, 'IDD_MANAGER': 101, 'IDC_ABOUT': 2028, 'IDD_DIAGNOSTIC': 113, 'IDET_PARTNER_COUNTRY': 2063, 'IDD_TRAINING': 102, 'ID_CONTACT_EMAIL_TEXT': 40008, 'IDC_DELAY2_TEXT': 1059, 'IDC_DELAY1_TEXT': 1057, 'IDD_WIZARD': 114, 'IDC_CHKBX': 2023, 'IDC_STATIC_HAM': 1002, 'IDC_PROGRESS_TEXT': 1001, 'IDR_XMLS_PROTOCOL': 2042, 'IDD_GENERAL': 108, 'IDD_ABOUT': 2027, 'IDD_SYNC': 40002, 'IDC_TAB': 1068, 'IDC_FOLDER_UNSURE': 1033, '_APS_NEXT_SYMED_VALUE': 101, 'IDC_VERBOSE_LOG': 1061, 'IDC_EDIT1': 1094, 'IDC_BROWSE': 1037, 'ID_DB_DROPDOWNLIST': 2004, 'IDC_BACK_BTN': 1069, 'ID_CONTACT_NAME_TEXT': 40005, 'IDPB_PARTNER_SEARCH': 2077, 'IDD_WIZARD_FINISHED_UNCONFIGURED': 119, 'IDC_ACTION_CERTAIN': 1025, 'IDC_BUT_ACT_ALL': 1019, 'IDD_FILTER_NOW': 104, 'IDET_WED_SERVER': 2080, 'ID_WEB_OK': 2091, 'ID_PROTOCOL_GRP': 2040, 'IDC_HEADER': 2017, 'IDC_MARK_SPAM_AS_READ': 1047, 'IDPB_NEW_PARTNER_BUTTON': 2092, 'ID_PARTNER_NAME_TEXT': 2036, 'IDC_RECOVER_RS': 1075, 'IDC_NAME_LIST1': 2037, 'ID_CONTACT_OFFICE_TEXT': 40006, 'IDC_STATIC': -1, 'IDC_PAGE_PLACEHOLDER': 1078, 'IDC_BROWSE_WATCH': 1039, 'IDET_ZIP': 40010, 'IDET_PARTNER_CITY': 40011, 'IDET_PARTNER_LINK_TEXT': 2085, 'IDC_FOLDER_HAM': 1083, 'IDC_LIST_COUNTRY': 2096, 'IDD_WIZARD_FOLDERS_REST': 117, 'IDC_SHOW_DATA_FOLDER': 1071, 'IDC_BUT_ACT_SCORE': 1018, 'IDET_STATE_SEARCH_NAME': 2100, 'IDET_WEB_SERVER': 2089, '_APS_NEXT_RESOURCE_VALUE': 128, 'ID_SET_WEB_CONNECTION': 2082, 'ID_DROPDOWNLIST_PROTOCOL': 2110, 'IDC_LIST_STATE': 2101, 'IDC_CONTACT_LIST': 2029, 'IDC_SLIDER_CERTAIN': 1023, 'IDET_PARTNER_STREET': 2053, 'IDC_BUT_UNREAD': 1020, 'ID_PARTNER_DROPDOWNLIST': 2032, 'ID_COUNTRY_DROPLIST': 2065, 'IDC_BUT_ABOUT': 1017, 'IDC_BUT_RESCORE': 1008, 'IDC_BUT_SEARCHSUB': 1041, 'IDC_BUT_TRAIN_FROM_SPAM_FOLDER': 1010, 'IDET_PARTNER_MOBILENO': 2050, 'ID_CONTACT_MOBILE_TEXT': 40007, 'IDD_VIEW_PARTNER_DIALOG': 2044, 'IDD_WIZARD_FOLDERS_TRAIN': 120, 'IDC_BUT_FILTER_ENABLE': 1013, 'IDPB_WEB_CONNECTION': 2083, 'IDC_ABOUT_BTN': 1072, 'IDD_WIZARD_FINISHED_TRAINED': 122, 'ID_SERVER': 2001, 'IDD_SELECT_PARTNER': 2073, 'IDET_NC_PARTNER_STATE': 2106, 'IDD_FOLDER_SELECTOR': 105, 'IDC_BUT_SET_SERVER_PORT': 2021, 'ID_DONE': 2008, 'IDC_LIST_FOLDERS': 1040, 'IDC_IMAGE_PATH': 2015, 'IDB_SBWIZLOGO': 125, 'IDB_OPENERPLOGO': 1062, 'ID_ZIP_TEXT': 2068, 'IDEB_OPENDOC_LINK_TEXT': 2086, 'IDD_NEW_PARTNER_DIALOG': 2034, 'ID_PARTNER_DROPLIST': 2069, 'IDC_BUT_VIEW_LOG': 1093, 'IDC_STATUS2': 1044, 'IDC_STATUS1': 1043, 'IDCANCEL': 2, 'IDC_BROWSE_HAM': 1004, 'ID_BUT_TESTCONNECTION': 2007, 'IDR_NETRPC_PROTOCOL': 2043, 'IDC_BROWSE_SPAM': 1005, 'IDCB_WEB_SECURE': 2111, 'IDD_OPEN_DOCUEMNT_DIALOG': 2087, 'IDD_WIZARD_FINISHED_UNTRAINED': 116, 'IDC_MARK_UNSURE_AS_READ': 1051, 'IDPB_SEARCH_COUNTRY1': 2105, 'ID_PARTNER_CITY_TEXT': 2067, 'IDC_BUT_WIZARD': 1070, 'IDC_VERSION': 1009, 'ID_NEW_PARTNER_BUTTON': 2033, 'IDC_FOLDER_NAMES': 1036, 'ID_ATT_METHOD_DROPDOWNLIST': 2025, 'IDC_BUT_TIMER_ENABLED': 1091, 'IDPB_WRITE_CHANGES': 2059, 'IDC_SLIDER_UNSURE': 1029, 'IDC_BUT_NEW': 1046, 'IDC_FOLDER_WATCH': 1038, 'IDPB_CREATE_NEW_PARTNER': 2078, 'IDC_BUT_UNTRAINED': 1088, 'IDC_STATIC_SPAM': 1003, 'IDD_NEW_CONTACT_DIALOG': 2031, 'IDC_EDIT_UNSURE': 1030, 'IDC_BUT_CLEARALL': 1042, 'IDC_BUT_UNSEEN': 1021, 'IDC_OBJECT_NAME': 2014, 'IDD_WIZARD_FOLDERS_WATCH': 118, 'IDPB_SEARCH_STATE': 2103, 'IDET_COUNTRY_SEARCH_NAME': 2095, 'IDC_BUT_SAVE_OBJECT': 2011, 'ID_FED_STATE_DROPLIST': 2064, 'ID_ALL_COUNTRY_DROPDOWNLIST': 2061, 'IDC_EDIT_CERTAIN': 1024, 'IDC_BUT_FILTER_DEFINE': 1016, 'ID_NEW_PART_BUTTON': 2070, 'ID_FAX_TEXT': 2066, 'IDD_WIZARD_TRAINING_IS_IMPORTANT': 123, 'ID_ALL_STATE_DROPDOWNLIST': 2060, 'IDPB_SEARCH_STATE1': 2104, 'IDC_INBOX_TIMER_ONLY': 1060, 'IDPB_SEARCH_PARTNER': 2052, 'ID_USERNAME': 2005, '_APS_NEXT_CONTROL_VALUE': 1096, 'IDC_WIZ_GRAPHIC': 1092, 'IDD_OBJECT_SETTINGS': 2009, 'IDD_FILTER_UNSURE': 111, 'IDC_DEL_SPAM_RS': 1074, 'IDD_SELECT_COUNTRY': 2094, 'ID_SERVER_PORT': 2003, 'IDR_XML_PROTOCOL': 2041, 'IDET_PARTNER_OFFICENO': 2048, 'IDB_FOLDERS': 127, 'IDC_BUT_PREPARATION': 1081, 'ID_STREET2_TEXT': 2063, 'IDC_DELAY2_SLIDER': 1058, 'IDET_PARTNER': 2064, 'IDC_SAVE_SPAM_SCORE': 1048, 'IDC_OBJECT_TITLE': 2013, 'IDC_FOLDER_CERTAIN': 1027, 'IDET_WEB_PORT': 2090, 'IDC_BROWSE_UNSURE': 1034, 'IDC_STATISTICS': 1095, 'IDPB_STATE_SEARCH': 40012, 'ID_MAKE_ATTACHMENT': 2019, 'IDC_BUT_LOAD_IMAGE': 2010, 'IDC_NAME_LIST': 2026, 'IDC_BUT_TRAIN_TO_SPAM_FOLDER': 1011, 'IDET_PARTNER_SEARCH_NAME': 2076, 'IDC_BUT_RESET': 1073, 'ID_SEARCH': 40004, 'IDET_PARTNER_CONTACT_NAME': 2046, 'IDPB_SELECT_STATE': 2102, 'IDC_ACTION_UNSURE': 1031, 'IDD_WIZARD_TRAIN': 121, 'IDC_STATIC_GROUP': 2024, 'IDPB_NEWPARTNER_BUTTON': 2071, 'ID_PARTNER_TEXT': 2079, 'IDD_WIZARD_FINISHED_TRAIN_LATER': 124, 'IDET_PARTNER_NAME': 2045, 'IDPB_NEW_PART_BUTTON': 2093, 'IDC_BUT_REBUILD': 1007, 'IDET_PARTNER_STREET2': 2054, 'IDPB_SEARCH_COUNTRY': 2098, 'ID_SAVE_PARTNER_BUTTON': 2035, 'IDPB_SELECT_PARTNER': 2075, 'ID_STREET_TEXT': 2062, '_APS_NEXT_COMMAND_VALUE': 40001, 'IDET_SEARCH_PARTNER': 2051, 'IDC_LIST_PARTNER': 2072, 'IDD_WEB_SERVER_PORT_DIALOG': 2088, 'ID_PORT': 2002, 'IDD_SERVER_PORT_DIALOG': 2022, 'IDET_PARTNER_STATE': 2062, 'IDPB_SELECT_COUNTRY': 2097, 'ID_SEARCH_TEXT': 40003, 'IDET_NC_PARTNER_COUNTRY': 2108, 'ID_CREATE_CONTACT': 2018, 'IDC_FORWARD_BTN': 1077, 'IDC_TRAINING_STATUS': 1035, 'IDD_WIZARD_WELCOME': 115, 'IDET_PARTNER_FAX': 2058, 'IDC_BUT_TRAIN': 1089, 'IDC_LIST': 2016, 'ID_CREATE_CASE': 2020, 'IDET_PARTNER_EMAIL': 2047, 'IDC_START': 1006, 'IDD_FILTER': 103, 'IDC_RELOAD': 2038, 'ID_PASSWORD': 2006, 'ID_NAME_TEXT': 2030, 'IDC_FILTER_STATUS': 1014, 'IDPB_NC_SEARCH_STATE1': 2107, 'ID_CONTACT_SAVE_BUTTON': 40009, 'IDOK': 1, 'IDC_BROWSE_CERTAIN': 1028, 'IDC_BUT_SHOW_DIAGNOSTICS': 1080, 'IDC_BUT_TRAIN_NOW': 1012, 'IDPB_NC_SEARCH_COUNTRY1': 2109}
diff --git a/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py b/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py
index 7ecb2aea81f..41346fe7cf9 100644
--- a/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py
+++ b/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py
@@ -20,6 +20,8 @@
##############################################################################
import xmlrpclib
+import binascii
+import base64
import sys
import socket
import os
@@ -146,6 +148,7 @@ class XMLRpcConn(object):
flag = False
new_msg = ext_msg =""
message_id = referances = None
+ context = {}
try:
session = win32com.client.Dispatch("MAPI.session")
session.Logon('Outlook')
@@ -177,15 +180,15 @@ class XMLRpcConn(object):
for rec in recs: #[('res.partner', 3, 'Agrolait')]
model = rec[0]
res_id = rec[1]
- #Check if mailgate installed
- object_id = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=','mailgate.message')])
+ #Check if mail installed
+ object_id = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=','mail.message')])
if not object_id:
- win32ui.MessageBox("Mailgate is not installed on your configured database '%s' !!\n\nPlease install it to archive the mail."%(self._dbname),"Mailgate not installed",win32con.MB_ICONERROR)
+ win32ui.MessageBox("Mail is not installed on your configured database '%s' !!\n\nPlease install it to archive the mail."%(self._dbname),"Mail not installed",win32con.MB_ICONERROR)
return
object_ids = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=',model)])
object_name = execute( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','read',object_ids,['name'])[0]['name']
#Reading the Object ir.model Name
- ext_ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mailgate.message','search',[('message_id','=',message_id),('model','=',model),('res_id','=',res_id)])
+ ext_ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mail.message','search',[('message_id','=',message_id),('model','=',model),('res_id','=',res_id)])
if ext_ids:
name = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,model,'read',res_id,['name'])['name']
ext_msg += """This mail is already archived to {0} '{1}'.\n""".format(object_name,name)
@@ -201,14 +204,30 @@ class XMLRpcConn(object):
'message-id':message_id,
'references':ustr(referances),
}
- obj_list= ['crm.lead','project.issue','hr.applicant','res.partner']
- if rec[0] not in obj_list:
- self.CreateEmailAttachment(rec,mail)
result = {}
+ context['thread_model'] = model
if attachments:
result = self.MakeAttachment([rec], mail)
- attachment_ids = result.get(model, {}).get(res_id, [])
- execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'email.server.tools','history',model, res_id, msg, attachment_ids)
+ execute(conn, 'execute', self. _dbname, int(self._uid), self._pwd,
+ 'mail.thread','message_append',[res_id],
+ msg.get('subject', False),
+ msg.get('body', False),
+ msg.get('to', False),
+ msg.get('from', False),
+ msg.get('cc', False),
+ False, # BCC
+ False, #reply-to
+ msg.get('date', False),
+ msg.get('message-id'),\
+ msg.get('references', False),
+ result, #attachments
+ #FIXME: properly handle plaintext/html body variants?
+ False, #body_html
+ False, #subtype
+ False, #headers
+ False, #original
+ context)
+
new_msg += """- {0} : {1}\n""".format(object_name,str(rec[2]))
flag = True
@@ -298,7 +317,7 @@ class XMLRpcConn(object):
endCut = message_id.find(">")
message_id = message_id[startCut:endCut+1]
email.replace_header('Message-Id',message_id)
- id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'email.server.tools','process_email',section, str(email))
+ id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mail.thread','message_process',section, str(email))
if id > 0:
flag = True
return flag
@@ -310,39 +329,28 @@ class XMLRpcConn(object):
return flag
def MakeAttachment(self, recs, mail):
- attachments = mail.Attachments
- result = {}
- conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
- att_folder_path = os.path.abspath(os.path.dirname("%temp%\\"))
- if not os.path.exists(att_folder_path):
+ attachments = mail.Attachments
+ attachment = {}
+ conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
+ att_folder_path = os.path.abspath(os.path.dirname("%temp%\\"))
+ if not os.path.exists(att_folder_path):
os.makedirs(att_folder_path)
for rec in recs: #[('res.partner', 3, 'Agrolait')]
- obj = rec[0]
- obj_id = rec[1]
- res={}
- res['res_model'] = obj
- attachment_ids = []
- if obj not in result:
- result[obj] = {}
- for i in xrange(1, attachments.Count+1):
- fn = ustr(attachments[i].FileName)
- if len(fn) > 64:
- l = 64 - len(fn)
- f = fn.split('.')
- fn = f[0][0:l] + '.' + f[-1]
- att_path = os.path.join(att_folder_path,fn)
- attachments[i].SaveAsFile(att_path)
- f=open(att_path,"rb")
- content = "".join(f.readlines()).encode('base64')
- f.close()
- res['name'] = ustr(attachments[i].DisplayName)
- res['datas_fname'] = ustr(fn)
- res['datas'] = content
- res['res_id'] = obj_id
- id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.attachment','create',res)
- attachment_ids.append(id)
- result[obj].update({obj_id: attachment_ids})
- return result
+ obj = rec[0]
+ obj_id = rec[1]
+ for i in xrange(1, attachments.Count+1):
+ fn = ustr(attachments[i].FileName)
+ if len(fn) > 64:
+ l = 64 - len(fn)
+ f = fn.split('.')
+ fn = f[0][0:l] + '.' + f[-1]
+ att_path = os.path.join(att_folder_path,fn)
+ attachments[i].SaveAsFile(att_path)
+ f=open(att_path,"rb")
+ content = "".join(f.readlines())
+ f.close()
+ attachment[fn] = content
+ return attachment
def CreateContact(self, res=None):
res=eval(str(res))
@@ -456,17 +464,17 @@ class XMLRpcConn(object):
import win32ui
conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
res_vals = []
- mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message', 'search', [('message_id','=',message_id)])
+ mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message', 'search', [('message_id','=',message_id)])
ref_mail_id = None
if not mail_id:
- ref_mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message', 'search', [('references','=',message_id)])
+ ref_mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message', 'search', [('references','like','%'+message_id+'%')])
if ref_mail_id:
- address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message','read',ref_mail_id[0],['model','res_id'])
+ address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message','read',ref_mail_id[0],['model','res_id'])
for key, vals in address.items():
res_vals.append([key,vals])
return res_vals
return None
- address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message','read',mail_id[0],['model','res_id'])
+ address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message','read',mail_id[0],['model','res_id'])
for key, vals in address.items():
res_vals.append([key,vals])
return res_vals
diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py
index a18ecb2c33e..b966abfef41 100644
--- a/addons/portal/wizard/portal_wizard.py
+++ b/addons/portal/wizard/portal_wizard.py
@@ -26,7 +26,7 @@ from osv import osv, fields
from tools.misc import email_re, email_send
from tools.translate import _
-from base.res.res_user import _lang_get
+from base.res.res_users import _lang_get
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py
index a2387ce24dc..936c7d361fc 100644
--- a/addons/project/__openerp__.py
+++ b/addons/project/__openerp__.py
@@ -28,7 +28,7 @@
"category": "Project Management",
'complexity': "easy",
"images": ["images/gantt.png", "images/project_dashboard.jpeg","images/project_task_tree.jpeg","images/project_task.jpeg","images/project.jpeg","images/task_analysis.jpeg"],
- "depends": ["base_setup", "product", "analytic", "board"],
+ "depends": ["base_setup", "product", "analytic", "board", "mail"],
"description": """
Project management module tracks multi-level projects, tasks, work done on tasks, eso.
======================================================================================
@@ -46,7 +46,6 @@ Dashboard for project members that includes:
"update_xml": [
"security/project_security.xml",
"wizard/project_task_delegate_view.xml",
- "wizard/project_task_close_view.xml",
"wizard/project_task_reevaluate_view.xml",
"security/ir.model.access.csv",
"project_data.xml",
diff --git a/addons/project/project.py b/addons/project/project.py
index 96e39f1f1de..e62570a1a43 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -590,11 +590,12 @@ class task(osv.osv):
'name': _('Send Email after close task'),
'view_type': 'form',
'view_mode': 'form',
- 'res_model': 'project.task.close',
+ 'res_model': 'mail.compose.message',
'type': 'ir.actions.act_window',
'target': 'new',
'nodestroy': True,
- 'context': {'active_id': task.id}
+ 'context': {'active_id': task.id,
+ 'active_model': 'project.task'}
}
return res
diff --git a/addons/project/wizard/__init__.py b/addons/project/wizard/__init__.py
index 69e1051e621..f5324d2b6aa 100644
--- a/addons/project/wizard/__init__.py
+++ b/addons/project/wizard/__init__.py
@@ -19,7 +19,7 @@
#
##############################################################################
-import project_task_close
+import mail_compose_message
import project_task_delegate
import project_task_reevaluate
diff --git a/addons/project/wizard/mail_compose_message.py b/addons/project/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..82d67ea9b92
--- /dev/null
+++ b/addons/project/wizard/mail_compose_message.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+from tools.translate import _
+
+class mail_compose_message(osv.osv_memory):
+ _inherit = 'mail.compose.message'
+
+ def get_value(self, cr, uid, model, resource_id, context=None):
+ '''
+ To get values of the resource_id for the model
+ @param model: Object
+ @param resource_id: id of a record for which values to be read
+
+ @return: Returns a dictionary
+ '''
+ result = super(mail_compose_message, self).get_value(cr, uid, model, resource_id, context=context)
+ if model == 'project.task' and resource_id:
+ task_pool = self.pool.get('project.task')
+ task_data = task_pool.browse(cr, uid, resource_id, context=context)
+ partner = task_data.partner_id or task_data.project_id.partner_id
+ if task_data.project_id.warn_manager and (not task_data.project_id.user_id or task_data.project_id.user_id and not task_data.project_id.user_id.user_email) :
+ raise osv.except_osv(_('Error'), _("Please specify the Project Manager or email address of Project Manager."))
+ elif task_data.project_id.warn_customer and (not partner or not len(partner.address) or (partner and len(partner.address) and not partner.address[0].email)):
+ raise osv.except_osv(_('Error'), _("Please specify the Customer or email address of Customer."))
+
+ result.update({'email_from': task_data.user_id and task_data.user_id.user_email or False})
+ val = {
+ 'name': task_data.name,
+ 'user_id': task_data.user_id.name,
+ 'task_id': "%d/%d" % (task_data.project_id.id, task_data.id),
+ 'date_start': task_data.date_start,
+ 'date': task_data.date_end,
+ 'state': task_data.state
+ }
+ header = (task_data.project_id.warn_header or '') % val
+ footer = (task_data.project_id.warn_footer or '') % val
+ description = u'%s\n %s\n %s\n\n \n%s' % (header, task_data.description or '', footer, task_data.user_id and task_data.user_id.signature)
+ if partner and len(partner.address):
+ result.update({'email_to': result.get('email_to',False) and result.get('email_to') + ',' + partner.address[0].email})
+ result.update({
+ 'body_text': description or False,
+ 'email_to': task_data.project_id.user_id and task_data.project_id.user_id.user_email or False,
+ 'subject': _("Task '%s' Closed") % task_data.name,
+ 'model': model,
+ 'res_id': resource_id,
+ })
+
+ return result
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/wizard/project_task_close.py b/addons/project/wizard/project_task_close.py
deleted file mode 100644
index 627864820f6..00000000000
--- a/addons/project/wizard/project_task_close.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from osv import fields, osv
-import tools
-from tools.translate import _
-import re
-class project_task_close(osv.osv_memory):
- """
- Close Task
- """
- _name = "project.task.close"
- _description = "Project Close Task"
- _columns = {
- 'manager_warn': fields.boolean("Warn Manager", help="Warn Manager by Email"),
- 'partner_warn': fields.boolean("Warn Customer", help="Warn Customer by Email"),
- 'manager_email': fields.char('Manager Email', size=128, help="Email Address of Project's Manager"),
- 'partner_email': fields.char('Customer Email', size=128, help="Email Address of Customer"),
- 'description': fields.text('Description'),
- }
-
- def default_get(self, cr, uid, fields, context=None):
- """
- This function gets default values
- """
- if context is None:
- context = {}
- record_id = context and context.get('active_id', False) or False
- task_pool = self.pool.get('project.task')
-
- res = super(project_task_close, self).default_get(cr, uid, fields, context=context)
- task = task_pool.browse(cr, uid, record_id, context=context)
- project = task.project_id
- manager = project.user_id or False
- partner = task.partner_id or task.project_id.partner_id
-
- if 'description' in fields:
- res.update({'description': task.description or False})
- if 'manager_warn' in fields:
- res.update({'manager_warn': project.warn_manager or False})
- if 'partner_warn' in fields:
- res.update({'partner_warn': project.warn_customer or False})
- if 'manager_email' in fields:
- res.update({'manager_email': manager and manager.user_email or False})
- if partner and len(partner.address) and 'partner_email' in fields:
- res.update({'partner_email': partner.address[0].email})
- return res
-
- def send(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
-
- task_pool = self.pool.get('project.task')
- task_id = context.get('active_id', False)
- if not task_id:
- return {}
- task = task_pool.browse(cr, uid, task_id, context=context)
- for data in self.browse(cr, uid, ids, context=context):
- # Send Warn Message by Email to Manager and Customer
- if data.manager_warn and not data.manager_email:
- raise osv.except_osv(_('Error'), _("Please specify the email address of Project Manager."))
-
- elif data.partner_warn and not data.partner_email:
- raise osv.except_osv(_('Error'), _("Please specify the email address of Customer."))
-
- elif data.manager_warn or data.partner_warn:
- project = task.project_id
- subject = _("Task '%s' Closed") % task.name
- if task.user_id and task.user_id.user_email:
- from_adr = task.user_id.user_email
- signature = task.user_id.signature
- else:
- raise osv.except_osv(_('Error'), _("Couldn't send mail because your email address is not configured!"))
- val = {
- 'name': task.name,
- 'user_id': task.user_id.name,
- 'task_id': "%d/%d" % (project.id, task.id),
- 'date_start': task.date_start,
- 'date_end': task.date_end,
- 'state': task.state
- }
-
- to_adr = []
- header = footer = ''
- try:
- header_str = (project.warn_header or '')
- footer_str = (project.warn_footer or '')
- header = (re.sub(r'\%\W*\(', '%(', header_str)) % val
- footer = (re.sub(r'\%\W*\(', '%(', footer_str)) % val
- except:
- raise osv.except_osv(_('Error'), _("Invlaid automatic variables used in project header or foooter."))
- body = u'%s\n%s\n%s\n\n-- \n%s' % (header, data.description or '', footer, signature)
- if data.manager_warn and data.manager_email:
- to_adr.append(data.manager_email)
- if data.partner_warn and data.partner_email:
- to_adr.append(data.partner_email)
- mail_id = tools.email_send(from_adr, to_adr, subject, tools.ustr(body), email_bcc=[from_adr])
- if not mail_id:
- raise osv.except_osv(_('Error'), _("Couldn't send mail! Check the email ids and smtp configuration settings"))
- return {'type': 'ir.actions.act_window_close'}
-
-project_task_close()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/wizard/project_task_close_view.xml b/addons/project/wizard/project_task_close_view.xml
deleted file mode 100644
index d6bed92824d..00000000000
--- a/addons/project/wizard/project_task_close_view.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
- Send Email
- project.task.close
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send Email
- ir.actions.act_window
- project.task.close
- form
- form
-
- new
-
-
-
-
diff --git a/addons/project_issue/__init__.py b/addons/project_issue/__init__.py
index 21efa34f6f4..f519698a60c 100644
--- a/addons/project_issue/__init__.py
+++ b/addons/project_issue/__init__.py
@@ -22,6 +22,5 @@
import project_issue
import report
-import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 61a45f01753..8237159af7a 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -26,7 +26,9 @@ from tools.translate import _
import binascii
import time
import tools
+from crm import wizard
+wizard.mail_compose_message.SUPPORTED_MODELS.append('project.issue')
class project_issue_version(osv.osv):
_name = "project.issue.version"
@@ -44,8 +46,8 @@ class project_issue(crm.crm_case, osv.osv):
_name = "project.issue"
_description = "Project Issue"
_order = "priority, create_date desc"
- _inherit = ['mailgate.thread']
-
+ _inherit = ['mail.thread']
+
def case_open(self, cr, uid, ids, *args):
"""
@param self: The object pointer
@@ -156,7 +158,7 @@ class project_issue(crm.crm_case, osv.osv):
issues = []
issue_pool = self.pool.get('project.issue')
for task in self.pool.get('project.task').browse(cr, uid, ids, context=context):
- issues += issue_pool.search(cr, uid, [('task_id','=',task.id)])
+ issues += issue_pool.search(cr, uid, [('task_id','=',task.id)])
return issues
def _get_issue_work(self, cr, uid, ids, context=None):
@@ -174,8 +176,8 @@ class project_issue(crm.crm_case, osv.osv):
progress = 0.0
if issue.task_id:
progress = task_pool._hours_get(cr, uid, [issue.task_id.id], field_names, args, context=context)[issue.task_id.id]['progress']
- res[issue.id] = {'progress' : progress}
- return res
+ res[issue.id] = {'progress' : progress}
+ return res
_columns = {
'id': fields.integer('ID'),
@@ -225,7 +227,7 @@ class project_issue(crm.crm_case, osv.osv):
multi='compute_day', type="float", store=True),
'inactivity_days': fields.function(_compute_day, string='Days since last action', \
multi='compute_day', type="integer", help="Difference in days between last action and current date"),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
'date_action_last': fields.datetime('Last Action', readonly=1),
'date_action_next': fields.datetime('Next Action', readonly=1),
'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="Computed as: Time Spent / Total Time.",
@@ -384,26 +386,17 @@ class project_issue(crm.crm_case, osv.osv):
else:
raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
self.write(cr, uid, [case.id], data)
- self._history(cr, uid, cases, _('Escalate'))
+ self.message_append(cr, uid, cases, _('Escalate'))
return True
- def message_new(self, cr, uid, msg, context=None):
- """
- Automatically calls when new email message arrives
-
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks
- """
- if context is None:
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ if context is None:
context = {}
- mailgate_pool = self.pool.get('email.server.tools')
-
subject = msg.get('subject') or _('No Title')
- body = msg.get('body')
+ body = msg.get('body_text')
msg_from = msg.get('from')
priority = msg.get('priority')
-
vals = {
'name': subject,
'email_from': msg_from,
@@ -411,37 +404,20 @@ class project_issue(crm.crm_case, osv.osv):
'description': body,
'user_id': False,
}
- if msg.get('priority', False):
+ if priority:
vals['priority'] = priority
-
- res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
- if res:
- vals.update(res)
+ vals.update(self.message_partner_by_email(cr, uid, msg_from))
context.update({'state_to' : 'draft'})
- res = self.create(cr, uid, vals, context=context)
- self.convert_to_bug(cr, uid, [res], context=context)
- attachents = msg.get('attachments', [])
- for attactment in attachents or []:
- data_attach = {
- 'name': attactment,
- 'datas': binascii.b2a_base64(str(attachents.get(attactment))),
- 'datas_fname': attactment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- self.pool.get('ir.attachment').create(cr, uid, data_attach)
+ if custom_values and isinstance(custom_values, dict):
+ vals.update(custom_values)
- return res
+ res_id = self.create(cr, uid, vals, context)
+ self.message_append_dict(cr, uid, [res_id], msg, context=context)
+ self.convert_to_bug(cr, uid, [res_id], context=context)
+ return res_id
- def message_update(self, cr, uid, ids, vals=None, msg="", default_act='pending', context=None):
- """
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of update mail’s IDs
- """
+ def message_update(self, cr, uid, ids, msg, vals=None, default_act='pending', context=None):
if vals is None:
vals = {}
@@ -450,7 +426,7 @@ class project_issue(crm.crm_case, osv.osv):
ids = [ids]
vals.update({
- 'description': msg['body']
+ 'description': msg['body_text']
})
if msg.get('priority', False):
vals['priority'] = msg.get('priority')
@@ -467,7 +443,7 @@ class project_issue(crm.crm_case, osv.osv):
record.write({'state' : 'open'})
vls = { }
- for line in msg['body'].split('\n'):
+ for line in msg['body_text'].split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower(), False):
@@ -476,20 +452,9 @@ class project_issue(crm.crm_case, osv.osv):
vals.update(vls)
res = self.write(cr, uid, ids, vals)
+ self.message_append_dict(cr, uid, ids, msg, context=context)
return res
- def msg_send(self, cr, uid, id, *args, **argv):
-
- """ Send The Message
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of email’s IDs
- @param *args: Return Tuple Value
- @param **args: Return Dictionary of Keyword Value
- """
- return True
-
def copy(self, cr, uid, id, default=None, context=None):
issue = self.read(cr, uid, id, ['name'], context=context)
if not default:
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index 1154579960e..0bf171d6aeb 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -97,15 +97,15 @@
-
+
-
-
+
+
@@ -113,20 +113,18 @@
-
-
+
-
-
-
+
+
+
-
+
@@ -141,10 +139,9 @@
name="%(crm.action_crm_add_note)d"
context="{'model': 'crm.lead' }"
icon="terp-document-new" type="action" />
-
+
diff --git a/addons/project_issue/report/project_issue_report.py b/addons/project_issue/report/project_issue_report.py
index 5eae252f446..8ab987806ec 100644
--- a/addons/project_issue/report/project_issue_report.py
+++ b/addons/project_issue/report/project_issue_report.py
@@ -102,7 +102,8 @@ class project_issue_report(osv.osv):
date_trunc('day',c.create_date) as create_date,
extract('epoch' from (c.date_open-c.create_date))/(3600*24) as delay_open,
extract('epoch' from (c.date_closed-c.date_open))/(3600*24) as delay_close,
- (SELECT count(id) FROM mailgate_message WHERE model='project.issue' AND res_id=c.id) AS email
+ (SELECT count(id) FROM mail_message WHERE model='project.issue' AND res_id=c.id) AS email
+
FROM
project_issue c
WHERE c.categ_id IN (select id from crm_case_categ where object_id in (select id from ir_model where model = 'project.issue'))
diff --git a/addons/project_issue/security/ir.model.access.csv b/addons/project_issue/security/ir.model.access.csv
index 177533462a0..01afcb73b09 100644
--- a/addons/project_issue/security/ir.model.access.csv
+++ b/addons/project_issue/security/ir.model.access.csv
@@ -5,7 +5,7 @@
"access_crm_case_categ_id","crm.case.categ","crm.model_crm_case_categ","project.group_project_manager",1,1,1,1
"access_project_issue_version_project","project_issue_version manager","model_project_issue_version","project.group_project_manager",1,1,1,1
"access_project_issue_version_project_user","project_issue_version user","model_project_issue_version","project.group_project_user",1,0,0,0
-"access_mailgate_message_project_manager","mailgate.message.manager","mail_gateway.model_mailgate_message","project.group_project_manager",1,1,1,1
+"access_mail_message_project_manager","mail.message.manager","mail.model_mail_message","project.group_project_manager",1,1,1,1
"access_resource_calendar_project_manager","resource.calendar.project.manager","resource.model_resource_calendar","project.group_project_manager",1,1,1,1
"access_project_issue_report_user","project.issue.report user","model_project_issue_report","project.group_project_user",1,0,0,0
-"access_mailgate_message_issue_project_user","project.mailgate.message.issue.user","mail_gateway.model_mailgate_message","project.group_project_user",1,1,1,0
+"access_mail_message_issue_project_user","mail.message.user","mail.model_mail_message","project.group_project_user",1,1,1,0
diff --git a/addons/project_mailgate/__openerp__.py b/addons/project_mailgate/__openerp__.py
index 4551d19bb1f..76ff6dbbc47 100644
--- a/addons/project_mailgate/__openerp__.py
+++ b/addons/project_mailgate/__openerp__.py
@@ -21,20 +21,28 @@
{
- "name": "Project MailGateWay",
+ "name": "Project Tasks - Mail Integration",
"version": "1.1",
"author": "OpenERP SA",
"website": "http://www.openerp.com",
"category": "Project Management",
'complexity': "easy",
"images": ["images/project_mailgate_task.jpeg"],
- "depends": ["project", "mail_gateway"],
+ "depends": ["project", "mail"],
"description": """
-This module is an interface that synchronises mails with OpenERP Project Task.
-==============================================================================
+This module can automatically create Project Tasks based on incoming emails
+===========================================================================
+
+Allows creating tasks based on new emails arriving at a given mailbox,
+similarly to what the CRM application has for Leads/Opportunities.
+There are two common alternatives to configure the mailbox integration:
+
+ * Install the ``fetchmail`` module and configure a new mailbox, then select
+ ``Project Tasks`` as the target for incoming emails.
+ * Set it up manually on your mail server based on the 'mail gateway' script
+ provided in the ``mail`` module - and connect it to the `project.task` model.
+
-It allows creating tasks as soon as a new mail arrives in our configured mail server.
-Moreover, it keeps track of all further communications and task states.
""",
"init_xml": [],
"update_xml": ["security/ir.model.access.csv",
diff --git a/addons/project_mailgate/project_mailgate.py b/addons/project_mailgate/project_mailgate.py
index 2325d533c03..9715a32c151 100644
--- a/addons/project_mailgate/project_mailgate.py
+++ b/addons/project_mailgate/project_mailgate.py
@@ -21,121 +21,85 @@
from osv import fields, osv
from tools.translate import _
+import tools
import binascii
class project_tasks(osv.osv):
_name = "project.task"
- _inherit = ['mailgate.thread', 'project.task']
+ _inherit = ['mail.thread','project.task']
- _columns={
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model', '=', _name)], readonly=True),
- }
+ _columns = {
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)], readonly=True),
+ }
- def message_new(self, cr, uid, msg, context=None):
-# """
-# Automatically calls when new email message arrives
-#
-# @param self: The object pointer
-# @param cr: the current row, from the database cursor,
-# @param uid: the current user’s ID for security checks
-# """
- mailgate_obj = self.pool.get('email.server.tools')
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ res_id = super(project_tasks,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
subject = msg.get('subject')
- body = msg.get('body')
+ body = msg.get('body_text')
msg_from = msg.get('from')
- #TODO map email priority with openerp task priority
- priority = msg.get('priority')
-
data = {
'name': subject,
'description': body,
'planned_hours': 0.0,
}
- res = mailgate_obj.get_partner(cr, uid, msg_from)
- if res:
- data.update(res)
- res = self.create(cr, uid, data)
+ data.update(self.message_partner_by_email(cr, uid, msg_from))
+ self.write(cr, uid, [res_id], data, context)
+ return res_id
- attachments = msg.get('attachments', [])
- for attachment in attachments or []:
- data_attach = {
- 'name': attachment,
- 'datas': binascii.b2a_base64(str(attachments.get(attachment))),
- 'datas_fname': attachment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- self.pool.get('ir.attachment').create(cr, uid, data_attach)
-
- return res
-
- def message_update(self, cr, uid, id, msg, data={}, default_act='pending'):
- mailgate_obj = self.pool.get('email.server.tools')
- msg_actions, body_data = mailgate_obj.msg_act_get(msg)
+ def message_update(self, cr, uid, ids, msg, data={}, default_act='pending'):
data.update({
- 'description': body_data,
+ 'description': msg['body_text'],
})
act = 'do_'+default_act
- if 'state' in msg_actions:
- if msg_actions['state'] in ['draft', 'close', 'cancel', 'open', 'pending']:
- act = 'do_' + msg_actions['state']
- for k1, k2 in [('cost', 'planned_hours')]:
- try:
- data[k2] = float(msg_actions[k1])
- except:
- pass
+ maps = {
+ 'cost':'planned_hours',
+ }
+ for line in msg['body_text'].split('\n'):
+ line = line.strip()
+ res = tools.misc.command_re.match(line)
+ if res:
+ match = res.group(1).lower()
+ field = maps.get(match)
+ if field:
+ try:
+ data[field] = float(res.group(2).lower())
+ except (ValueError, TypeError):
+ pass
+ elif match.lower() == 'state' \
+ and res.group(2).lower() in ['cancel','close','draft','open','pending']:
+ act = 'do_%s' % res.group(2).lower()
- if 'priority' in msg_actions:
- if msg_actions['priority'] in ('1', '2', '3', '4', '5'):
- data['priority'] = msg_actions['priority']
-
- self.write(cr, uid, [id], data)
- getattr(self, act)(cr, uid, [id])
+ self.write(cr, uid, ids, data, context=context)
+ getattr(self,act)(cr, uid, ids, context=context)
+ self.message_append_dict(cr, uid, [res_id], msg, context=context)
return True
- def message_followers(self, cr, uid, ids, context=None):
- res = []
- if isinstance(ids, (str, int, long)):
- select = [ids]
- else:
- select = ids
- for task in self.browse(cr, uid, select, context=context):
- user_email = (task.user_id and task.user_id.user_email) or False
- res += [(user_email, False, False, task.priority)]
- if isinstance(ids, (str, int, long)):
- return len(res) and res[0] or False
- return res
-
- def msg_send(self, cr, uid, id, *args, **argv):
- return True
-
- def _history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, email_from=False, message_id=False, attach=[], context=None):
- mailgate_pool = self.pool.get('mailgate.thread')
- return mailgate_pool.history(cr, uid, cases, keyword, history=history,\
- subject=subject, email=email, \
- details=details, email_from=email_from,\
- message_id=message_id, attach=attach, \
- context=context)
+ def message_thread_followers(self, cr, uid, ids, context=None):
+ followers = super(project_tasks,self).message_thread_followers(cr, uid, ids, context=context)
+ for task in self.browse(cr, uid, followers.keys(), context=context):
+ task_followers = set(followers[task.id])
+ task_followers.add(task.user_id.user_email)
+ followers[task.id] = filter(None, task_followers)
+ return followers
def do_draft(self, cr, uid, ids, context=None):
res = super(project_tasks, self).do_draft(cr, uid, ids, context)
tasks = self.browse(cr, uid, ids, context=context)
- self._history(cr, uid, tasks, _('Draft'), context=context)
+ self.message_append(cr, uid, tasks, _('Draft'), context=context)
return res
def do_open(self, cr, uid, ids, context=None):
res = super(project_tasks, self).do_open(cr, uid, ids, context)
tasks = self.browse(cr, uid, ids, context=context)
- self._history(cr, uid, tasks, _('Open'), context=context)
+ self.message_append(cr, uid, tasks, _('Open'), context=context)
return res
def do_pending(self, cr, uid, ids, context=None):
res = super(project_tasks, self).do_pending(cr, uid, ids, context)
tasks = self.browse(cr, uid, ids, context=context)
- self._history(cr, uid, tasks, _('Pending'), context=context)
+ self.message_append(cr, uid, tasks, _('Pending'), context=context)
return res
def do_close(self, cr, uid, ids, context=None):
@@ -143,13 +107,13 @@ class project_tasks(osv.osv):
tasks = self.browse(cr, uid, ids, context=context)
for task in tasks:
if task.state == 'done':
- self._history(cr, uid, tasks, _('Done'), context=context)
+ self.message_append(cr, uid, tasks, _('Done'), context=context)
return res
def do_cancel(self, cr, uid, ids, context=None):
res = super(project_tasks, self).do_cancel(cr, uid, ids, context=context)
tasks = self.browse(cr, uid, ids, context=context)
- self._history(cr, uid, tasks, _('Cancel'), context=context)
+ self.message_append(cr, uid, tasks, _('Cancel'), context=context)
return res
project_tasks()
diff --git a/addons/project_mailgate/project_mailgate_view.xml b/addons/project_mailgate/project_mailgate_view.xml
index 8c8816ebbd5..c3cd4bf9db2 100644
--- a/addons/project_mailgate/project_mailgate_view.xml
+++ b/addons/project_mailgate/project_mailgate_view.xml
@@ -9,7 +9,7 @@
-
+
@@ -19,15 +19,14 @@
-
-
+
-
-
+
+
-
+
diff --git a/addons/project_mailgate/security/ir.model.access.csv b/addons/project_mailgate/security/ir.model.access.csv
index 1a840d89e9a..cf9a08b958d 100644
--- a/addons/project_mailgate/security/ir.model.access.csv
+++ b/addons/project_mailgate/security/ir.model.access.csv
@@ -1,3 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"access_mailgate_message_project_manager","project.mailgate.message.manager","mail_gateway.model_mailgate_message","project.group_project_manager",1,1,1,0
-"access_mailgate_message_project_user","project.mailgate.message.user","mail_gateway.model_mailgate_message","project.group_project_user",1,1,1,0
+"access_mail_message_project_manager","project.mail.message.manager","mail.model_mail_message","project.group_project_manager",1,1,1,0
+"access_mail_message_project_user","project.mail.message.user","mail.model_mail_message","project.group_project_user",1,1,1,0
diff --git a/addons/project_planning/project_planning_view.xml b/addons/project_planning/project_planning_view.xml
index ef39b5daad9..016a10d940f 100644
--- a/addons/project_planning/project_planning_view.xml
+++ b/addons/project_planning/project_planning_view.xml
@@ -109,7 +109,7 @@
-
+
diff --git a/addons/project_scrum/__openerp__.py b/addons/project_scrum/__openerp__.py
index 9a1713b9014..9b2f4c4e046 100644
--- a/addons/project_scrum/__openerp__.py
+++ b/addons/project_scrum/__openerp__.py
@@ -50,7 +50,7 @@ More information on the methodology:
""",
'author': 'OpenERP SA',
'images': ['images/product_backlogs.jpeg', 'images/project_sprints.jpeg', 'images/scrum_dashboard.jpeg', 'images/scrum_meetings.jpeg'],
- 'depends': ['project', 'process'],
+ 'depends': ['project', 'process', 'mail'],
'init_xml': [],
'update_xml': [
'security/ir.model.access.csv',
@@ -58,7 +58,7 @@ More information on the methodology:
'wizard/project_scrum_backlog_create_task_view.xml',
'wizard/project_scrum_backlog_merger_view.xml',
'wizard/project_scrum_postpone_view.xml',
- "wizard/project_scrum_email_view.xml",
+# "wizard/project_scrum_email_view.xml",
'project_scrum_view.xml',
'wizard/project_scrum_backlog_sprint_view.xml',
'process/project_scrum_process.xml',
diff --git a/addons/project_scrum/project_scrum.py b/addons/project_scrum/project_scrum.py
index 334a415982d..f0db98294e7 100644
--- a/addons/project_scrum/project_scrum.py
+++ b/addons/project_scrum/project_scrum.py
@@ -317,6 +317,7 @@ class project_scrum_meeting(osv.osv):
return True
def email_send(self, cr, uid, ids, email, context=None):
+ mail_message_obj = self.pool.get('mail.message')
meeting_id = self.browse(cr, uid, ids, context=context)[0]
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
user_email = user.user_email or tools.config.get('email_from', False)
@@ -324,7 +325,7 @@ class project_scrum_meeting(osv.osv):
body += _('\n* Tasks since yesterday:\n_______________________\n%s\n\n* Task for Today:\n_______________________ \n%s\n\n* Blocks encountered:\n_______________________ \n\n%s') %(meeting_id.question_yesterday,meeting_id.question_today, meeting_id.question_blocks or _('No Blocks'))
body += _("\n\nThank you,\n%s") % user.name
sub_name = meeting_id.name or _('Scrum Meeting of %s') % meeting_id.date
- flag = tools.email_send(user_email , [email], sub_name, body, reply_to=None, openobject_id=str(meeting_id.id))
+ flag = mail_message_obj.schedule_with_attach(cr, uid, user_email , [email], sub_name, body, model='project.scrum.meeting', res_id=meeting_id.id, context=context)
if not flag:
return False
return True
diff --git a/addons/project_scrum/project_scrum_view.xml b/addons/project_scrum/project_scrum_view.xml
index 18421b784c3..19d1bcc6b84 100644
--- a/addons/project_scrum/project_scrum_view.xml
+++ b/addons/project_scrum/project_scrum_view.xml
@@ -302,7 +302,7 @@
-
@@ -396,7 +396,9 @@
-
+
diff --git a/addons/project_scrum/wizard/__init__.py b/addons/project_scrum/wizard/__init__.py
index 4b5b0919574..fcba1742d75 100644
--- a/addons/project_scrum/wizard/__init__.py
+++ b/addons/project_scrum/wizard/__init__.py
@@ -23,7 +23,7 @@ import project_scrum_backlog_create_task
import project_scrum_backlog_sprint
import project_scrum_backlog_merger
import project_scrum_postpone
-import project_scrum_email
+import mail_compose_message
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_scrum/wizard/mail_compose_message.py b/addons/project_scrum/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..777ca03fa37
--- /dev/null
+++ b/addons/project_scrum/wizard/mail_compose_message.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+from tools.translate import _
+
+class mail_compose_message(osv.osv_memory):
+ _inherit = 'mail.compose.message'
+
+ def get_value(self, cr, uid, model, resource_id, context=None):
+ '''
+ To get values of the resource_id for the model
+ @param model: Object
+ @param resource_id: id of a record for which values to be read
+
+ @return: Returns a dictionary
+ '''
+ if context is None:
+ context = {}
+ result = super(mail_compose_message, self).get_value(cr, uid, model, resource_id, context=context)
+ if model == 'project.scrum.meeting' and resource_id:
+ meeting_pool = self.pool.get('project.scrum.meeting')
+ user_pool = self.pool.get('res.users')
+ meeting = meeting_pool.browse(cr, uid, resource_id, context=context)
+
+ sprint = meeting.sprint_id
+ user_data = user_pool.browse(cr, uid, uid, context=context)
+ result.update({'email_from': user_data.address_id and user_data.address_id.email or False})
+
+ if sprint.scrum_master_id and sprint.scrum_master_id.user_email:
+ result.update({'email_to': sprint.scrum_master_id.user_email})
+ if sprint.product_owner_id and sprint.product_owner_id.user_email:
+ result.update({'email_to': result.get('email_to',False) and result.get('email_to') + ',' + sprint.product_owner_id.user_email or sprint.product_owner_id.user_email})
+
+ subject = _("Scrum Meeting : %s") %(meeting.date)
+ message = _("Hello , \nI am sending you Scrum Meeting : %s for the Sprint '%s' of Project '%s'") %(meeting.date, sprint.name, sprint.project_id.name)
+ result.update({
+ 'subject': subject,
+ 'body_text': message,
+ 'model': model,
+ 'res_id': resource_id
+ })
+ return result
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_scrum/wizard/project_scrum_email.py b/addons/project_scrum/wizard/project_scrum_email.py
index f2e8dd79446..f1d5b6665f7 100644
--- a/addons/project_scrum/wizard/project_scrum_email.py
+++ b/addons/project_scrum/wizard/project_scrum_email.py
@@ -69,6 +69,7 @@ class project_scrum_email(osv.osv_memory):
if context is None:
context = {}
+ mail_message = self.pool.get('mail.message')
active_id = context.get('active_id', False)
scrum_meeting_pool = self.pool.get('project.scrum.meeting')
user_pool = self.pool.get('res.users')
@@ -91,7 +92,7 @@ class project_scrum_email(osv.osv_memory):
body += "\n%s\n" %_("Task for Today")
body += "_______________________ \n"
body += "\n%s\n" %(meeting.question_today or _('None'))
- body += "\n%s\n" % _('Blocking points encountered:')
+ body += "\n%s\n" % _('Blocking points encountered:')
body += "_______________________ \n"
body += "\n%s\n" %(meeting.question_blocks or _('None'))
body += "\n%s\n%s" %(_('Thank you,'), user.name)
@@ -100,8 +101,7 @@ class project_scrum_email(osv.osv_memory):
if data.scrum_master_email == data.product_owner_email:
data.product_owner_email = False
if data.scrum_master_email:
- tools.email_send(user_email, [data.scrum_master_email], data.subject, body, reply_to=user_email)
+ mail_message.schedule_with_attach(cr, uid, user_email, [data.scrum_master_email], data.subject, body, reply_to=user_email, context=context)
if data.product_owner_email:
- tools.email_send(user_email, [data.product_owner_email], data.subject, body, reply_to=user_email)
+ mail_message.schedule_with_attach(cr, uid, user_email, [data.product_owner_email], data.subject, body, reply_to=user_email, context=context)
return {'type': 'ir.actions.act_window_close'}
-project_scrum_email()
diff --git a/addons/sale_crm/wizard/crm_make_sale.py b/addons/sale_crm/wizard/crm_make_sale.py
index 030a7567e57..ae41ced671c 100644
--- a/addons/sale_crm/wizard/crm_make_sale.py
+++ b/addons/sale_crm/wizard/crm_make_sale.py
@@ -108,7 +108,7 @@ class crm_make_sale(osv.osv_memory):
new_ids.append(new_id)
message = _("Opportunity '%s' is converted to Quotation.") % (case.name)
self.log(cr, uid, case.id, message)
- case_obj._history(cr, uid, [case], _("Converted to Sales Quotation(id: %s).") % (new_id))
+ case_obj.message_append(cr, uid, [case], _("Converted to Sales Quotation(id: %s).") % (new_id), context=context)
if make.close:
case_obj.case_close(cr, uid, data)
diff --git a/addons/share/__openerp__.py b/addons/share/__openerp__.py
index f83f69abd1e..52ffa165c14 100644
--- a/addons/share/__openerp__.py
+++ b/addons/share/__openerp__.py
@@ -23,7 +23,7 @@
{
"name" : "Sharing Tools",
"version" : "1.5",
- "depends" : ["base"],
+ "depends" : ["base", "mail"],
"author" : "OpenERP SA",
"category": 'Tools',
'complexity': "easy",
diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py
index eec8b440dfa..b15129fd1bc 100644
--- a/addons/share/wizard/share_wizard.py
+++ b/addons/share/wizard/share_wizard.py
@@ -692,6 +692,7 @@ class share_wizard(osv.osv_memory):
def send_emails(self, cr, uid, wizard_data, context=None):
self._logger.info('Sending share notifications by email...')
+ mail_message = self.pool.get('mail.message')
user = self.pool.get('res.users').browse(cr, UID_ROOT, uid)
if not user.user_email:
raise osv.except_osv(_('Email required'), _('The current user must have an email address configured in User Preferences to be able to send outgoing emails.'))
@@ -726,7 +727,12 @@ class share_wizard(osv.osv_memory):
body += _("OpenERP is a powerful and user-friendly suite of Business Applications (CRM, Sales, HR, etc.)\n"
"It is open source and can be found on http://www.openerp.com.")
- if tools.email_send(user.user_email, [email_to], subject, body):
+ if mail_message.schedule_with_attach(cr, uid,
+ user.user_email,
+ [email_to],
+ subject,
+ body,
+ model='share.wizard'):
emails_sent += 1
else:
self._logger.warning('Failed to send share notification from %s to %s, ignored', user.user_email, email_to)
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 310b1067ff8..8d7e993d817 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -309,7 +309,7 @@ class stock_location(osv.osv):
# Compute based on pricetype
# Choose the right filed standard_price to read
- amount_unit = product.price_get('standard_price', context)[product.id]
+ amount_unit = product.price_get('standard_price', context=context)[product.id]
price = qty[product_id] * amount_unit
total_price += price
@@ -891,7 +891,7 @@ class stock_picking(osv.osv):
if type in ('in_invoice', 'in_refund'):
# Take the user company and pricetype
context['currency_id'] = move_line.company_id.currency_id.id
- amount_unit = move_line.product_id.price_get('standard_price', context)[move_line.product_id.id]
+ amount_unit = move_line.product_id.price_get('standard_price', context=context)[move_line.product_id.id]
return amount_unit
else:
return move_line.product_id.list_price
@@ -1205,7 +1205,7 @@ class stock_picking(osv.osv):
new_std_price = new_price
else:
# Get the standard price
- amount_unit = product.price_get('standard_price', context)[product.id]
+ amount_unit = product.price_get('standard_price', context=context)[product.id]
new_std_price = ((amount_unit * product_avail[product.id])\
+ (new_price * qty))/(product_avail[product.id] + qty)
# Write the field according to price type field
@@ -2057,7 +2057,7 @@ class stock_move(osv.osv):
if context is None:
context = {}
currency_ctx = dict(context, currency_id = move.company_id.currency_id.id)
- amount_unit = move.product_id.price_get('standard_price', currency_ctx)[move.product_id.id]
+ amount_unit = move.product_id.price_get('standard_price', context=currency_ctx)[move.product_id.id]
reference_amount = amount_unit * qty or 1.0
return reference_amount, reference_currency_id
@@ -2443,7 +2443,7 @@ class stock_move(osv.osv):
new_std_price = new_price
else:
# Get the standard price
- amount_unit = product.price_get('standard_price', context)[product.id]
+ amount_unit = product.price_get('standard_price', context=context)[product.id]
new_std_price = ((amount_unit * product.qty_available)\
+ (new_price * qty))/(product.qty_available + qty)
diff --git a/addons/survey/__openerp__.py b/addons/survey/__openerp__.py
index e44efbe0bf9..5a551c28a3d 100644
--- a/addons/survey/__openerp__.py
+++ b/addons/survey/__openerp__.py
@@ -33,7 +33,7 @@ Different users may give different answers of question and according to that sur
Partners are also sent mails with user name and password for the invitation of the survey
""",
'author': 'OpenERP SA',
- 'depends': ['base_tools'],
+ 'depends': ['base_tools', 'mail'],
'update_xml': ['survey_report.xml',
'survey_data.xml',
'wizard/survey_selection.xml',
diff --git a/addons/survey/wizard/survey_answer.py b/addons/survey/wizard/survey_answer.py
index 896bde70d3c..471290af2cb 100644
--- a/addons/survey/wizard/survey_answer.py
+++ b/addons/survey/wizard/survey_answer.py
@@ -49,7 +49,7 @@ class survey_question_wiz(osv.osv_memory):
@param context: A standard dictionary for contextual values
@return : Dictionary value for created view of particular survey pages.
"""
-
+
result = super(survey_question_wiz, self).fields_view_get(cr, uid, view_id, \
view_type, context, toolbar,submenu)
@@ -61,6 +61,7 @@ class survey_question_wiz(osv.osv_memory):
sur_response_obj = self.pool.get('survey.response')
que_col_head = self.pool.get('survey.question.column.heading')
user_obj = self.pool.get('res.users')
+ mail_message = self.pool.get('mail.message')
if context is None:
context = {}
if view_type in ['form']:
@@ -79,7 +80,7 @@ class survey_question_wiz(osv.osv_memory):
wiz_id = surv_name_wiz.create(cr, uid, res_data)
sur_name_rec = surv_name_wiz.browse(cr, uid, wiz_id, context=context)
context.update({'sur_name_id' :wiz_id})
-
+
if context.has_key('active_id'):
context.pop('active_id')
@@ -401,7 +402,7 @@ class survey_question_wiz(osv.osv_memory):
response_id = surv_name_wiz.read(cr, uid, context.get('sur_name_id',False))['response']
context.update({'response_id':response_id})
report = self.create_report(cr, uid, [int(survey_id)], 'report.survey.browse.response', survey_data.title,context)
- attachments = []
+ attachments = {}
file = open(addons.get_module_resource('survey', 'report') + survey_data.title + ".pdf")
file_data = ""
while 1:
@@ -410,7 +411,7 @@ class survey_question_wiz(osv.osv_memory):
if not line:
break
- attachments.append((survey_data.title + ".pdf",file_data))
+ attachments[survey_data.title + ".pdf"] = file_data
file.close()
os.remove(addons.get_module_resource('survey', 'report') + survey_data.title + ".pdf")
user_email = False
@@ -428,7 +429,7 @@ class survey_question_wiz(osv.osv_memory):
if user_email and resp_email:
user_name = user_obj.browse(cr, uid, uid, context=context).name
mail = "Hello " + survey_data.responsible_id.name + ",\n\n " + str(user_name) + " Give Response Of " + survey_data.title + " Survey.\n\n Thanks,"
- tools.email_send(user_email, [resp_email], "Survey Answer Of " + str(user_name) , mail, attach = attachments)
+ mail_message.schedule_with_attach(cr, uid, user_email, [resp_email], "Survey Answer Of " + str(user_name) , mail, attachments=attachments, context=context)
xml_form = etree.Element('form', {'string': _('Complete Survey Answer')})
etree.SubElement(xml_form, 'separator', {'string': 'Complete Survey', 'colspan': "4"})
@@ -447,7 +448,7 @@ class survey_question_wiz(osv.osv_memory):
@param self: The object pointer
@param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
+ @param uid: the current user’s ID for security checks,
@param res_ids: List of survey answer IDs,
@param report_name: name of the report,
@param file_name: To give file name of the report,
diff --git a/addons/survey/wizard/survey_send_invitation.py b/addons/survey/wizard/survey_send_invitation.py
index 49cbac82b46..e57c8f91d8b 100644
--- a/addons/survey/wizard/survey_send_invitation.py
+++ b/addons/survey/wizard/survey_send_invitation.py
@@ -102,6 +102,7 @@ class survey_send_invitation(osv.osv_memory):
partner_ids = record['partner_ids']
user_ref= self.pool.get('res.users')
survey_ref= self.pool.get('survey')
+ mail_message = self.pool.get('mail.message')
model_data_obj = self.pool.get('ir.model.data')
group_id = model_data_obj._get_id(cr, uid, 'base', 'group_survey_user')
@@ -118,7 +119,7 @@ class survey_send_invitation(osv.osv_memory):
res_user = ""
user_exists = False
new_user = []
- attachments = []
+ attachments = {}
current_sur = survey_ref.browse(cr, uid, context.get('active_id'), context=context)
exist_user = current_sur.invited_user_ids
if exist_user:
@@ -133,8 +134,8 @@ class survey_send_invitation(osv.osv_memory):
file_data += line
if not line:
break
- attachments.append((id.title +".pdf",file_data))
file.close()
+ attachments[id.title +".pdf"] = file_data
os.remove(addons.get_module_resource('survey', 'report') + id.title +".pdf")
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids):
@@ -151,8 +152,8 @@ class survey_send_invitation(osv.osv_memory):
mail = record['mail']%{'login':addr.email, 'passwd':user.password, \
'name' : addr.name}
if record['send_mail_existing']:
- tools.email_send(record['mail_from'], [addr.email] , \
- record['mail_subject_existing'] , mail)
+ mail_message.schedule_with_attach(cr, uid, record['mail_from'], [addr.email] , \
+ record['mail_subject_existing'] , mail, context=context)
existing+= "- %s (Login: %s, Password: %s)\n" % (user.name, addr.email, \
user.password)
continue
@@ -162,8 +163,8 @@ class survey_send_invitation(osv.osv_memory):
mail = record['mail']%{'login': user_email.login, \
'passwd': user_email.password, 'name': addr.name}
if record['send_mail_existing']:
- tools.email_send(record['mail_from'], [addr.email],\
- record['mail_subject_existing'], mail)
+ mail_message.schedule_with_attach(cr, uid, record['mail_from'], [addr.email],\
+ record['mail_subject_existing'], mail, context=context)
res_user+= "- %s (Login: %s, Password: %s)\n" % \
(user_email.name, user_email.login, user_email.password)
continue
@@ -175,8 +176,8 @@ class survey_send_invitation(osv.osv_memory):
out+= addr.email + ',' + passwd + '\n'
mail= record['mail'] % {'login' : addr.email, 'passwd' : passwd, 'name' : addr.name}
if record['send_mail']:
- ans = tools.email_send(record['mail_from'], [addr.email], \
- record['mail_subject'], mail,attach = attachments)
+ ans = mail_message.schedule_with_attach(cr, uid, record['mail_from'], [addr.email], \
+ record['mail_subject'], mail, attachments=attachments, context=context)
if ans:
res_data = {'name': addr.name or 'Unknown',
diff --git a/addons/thunderbird/__openerp__.py b/addons/thunderbird/__openerp__.py
index 1729cc430cf..51e4153e897 100644
--- a/addons/thunderbird/__openerp__.py
+++ b/addons/thunderbird/__openerp__.py
@@ -24,7 +24,7 @@
"version" : "1.0",
"author" : ['OpenERP SA', 'Axelor'],
"website" : "http://www.openerp.com/",
- "depends" : ["base","mail_gateway"],
+ "depends" : ["base","mail"],
"category" : "Tools",
"description": """
This module is required for the Thuderbird Plug-in to work properly.
@@ -35,7 +35,6 @@ OpenERP objects. You can select a partner, a task, a project, an analytical
account, or any other object and attach the selected mail as a .eml file in
the attachment of a selected record. You can create documents for CRM Lead,
HR Applicant and Project Issue from selected mails.
-
""",
"init_xml" : [],
"demo_xml" : [],
diff --git a/addons/thunderbird/partner/partner.py b/addons/thunderbird/partner/partner.py
index c506cf58b4f..80a4bfa4d98 100644
--- a/addons/thunderbird/partner/partner.py
+++ b/addons/thunderbird/partner/partner.py
@@ -23,113 +23,7 @@ from osv import osv
import base64
import email
import tools
-import binascii
-import dateutil.parser
-class email_server_tools(osv.osv_memory):
- _inherit = "email.server.tools"
- def history_message(self, cr, uid, model, res_id, message, context=None):
- #@param message: string of mail which is read from EML File
- attachment_pool = self.pool.get('ir.attachment')
- msg = self.parse_message(message)
- attachments = msg.get('attachments', [])
- att_ids = []
- for attachment in attachments:
- data_attach = {
- 'name': attachment,
- 'datas': binascii.b2a_base64(str(attachments.get(attachment))),
- 'datas_fname': attachment,
- 'description': 'Mail attachment From Thunderbird msg_id: %s' %(msg.get('message_id', '')),
- 'res_model': model,
- 'res_id': res_id,
- }
- att_ids.append(attachment_pool.create(cr, uid, data_attach))
- return self.history(cr, uid, model, res_id, msg, att_ids)
-
- def parse_message(self, message):
- #TOCHECK: put this function in mailgateway module
- if isinstance(message, unicode):
- message = message.encode('utf-8')
- msg_txt = email.message_from_string(message)
- message_id = msg_txt.get('message-id', False)
- msg = {}
- fields = msg_txt.keys()
- msg['id'] = message_id
- msg['message-id'] = message_id
- if 'Subject' in fields:
- msg['subject'] = self._decode_header(msg_txt.get('Subject'))
-
- if 'Content-Type' in fields:
- msg['content-type'] = msg_txt.get('Content-Type')
-
- if 'From' in fields:
- msg['from'] = self._decode_header(msg_txt.get('From'))
-
- if 'Delivered-To' in fields:
- msg['to'] = self._decode_header(msg_txt.get('Delivered-To'))
-
- if 'CC' in fields:
- msg['cc'] = self._decode_header(msg_txt.get('CC'))
-
- if 'Reply-to' in fields:
- msg['reply'] = self._decode_header(msg_txt.get('Reply-To'))
-
- if 'Date' in fields:
- date = self._decode_header(msg_txt.get('Date'))
- msg['date'] = dateutil.parser.parse(date).strftime("%Y-%m-%d %H:%M:%S")
-
- if 'Content-Transfer-Encoding' in fields:
- msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
-
- if 'References' in fields:
- msg['references'] = msg_txt.get('References')
-
- if 'In-Reply-To' in fields:
- msg['in-reply-to'] = msg_txt.get('In-Reply-To')
-
- if 'X-Priority' in fields:
- msg['priority'] = msg_txt.get('X-Priority', '3 (Normal)').split(' ')[0]
-
- if not msg_txt.is_multipart() or 'text/plain' in msg.get('Content-Type', ''):
- encoding = msg_txt.get_content_charset()
- body = msg_txt.get_payload(decode=True)
- msg['body'] = tools.ustr(body, encoding)
-
- attachments = {}
- has_plain_text = False
- if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
- body = ""
- for part in msg_txt.walk():
- if part.get_content_maintype() == 'multipart':
- continue
-
- encoding = part.get_content_charset()
- filename = part.get_filename()
- if part.get_content_maintype()=='text':
- content = part.get_payload(decode=True)
- if filename:
- attachments[filename] = content
- elif not has_plain_text:
- # main content parts should have 'text' maintype
- # and no filename. we ignore the html part if
- # there is already a plaintext part without filename,
- # because presumably these are alternatives.
- content = tools.ustr(content, encoding)
- if part.get_content_subtype() == 'html':
- body = tools.ustr(tools.html2plaintext(content))
- elif part.get_content_subtype() == 'plain':
- body = content
- has_plain_text = True
- elif part.get_content_maintype() in ('application', 'image'):
- if filename :
- attachments[filename] = part.get_payload(decode=True)
- else:
- res = part.get_payload(decode=True)
- body += tools.ustr(res, encoding)
-
- msg['body'] = body
- msg['attachments'] = attachments
- return msg
-email_server_tools()
+from tools.translate import _
class thunderbird_partner(osv.osv_memory):
_name = "thunderbird.partner"
@@ -151,21 +45,21 @@ class thunderbird_partner(osv.osv_memory):
ref_ids = str(dictcreate.get('ref_ids')).split(';')
msg = dictcreate.get('message')
mail = msg
- msg = self.pool.get('email.server.tools').parse_message(msg)
- server_tools_pool = self.pool.get('email.server.tools')
+ mail_message = self.pool.get('mail.message')
+ msg = mail_message.parse_message(msg)
+ subject = msg.get('Subject', False)
+ thread_pool = self.pool.get('mail.thread')
message_id = msg.get('message-id', False)
- msg_pool = self.pool.get('mailgate.message')
msg_ids = []
res = {}
res_ids = []
obj_list= ['crm.lead','project.issue','hr.applicant','res.partner']
for ref_id in ref_ids:
- msg_new = dictcreate.get('message')
ref = ref_id.split(',')
model = ref[0]
res_id = int(ref[1])
if message_id:
- msg_ids = msg_pool.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
+ msg_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
if msg_ids and len(msg_ids):
continue
if model not in obj_list:
@@ -193,7 +87,23 @@ class thunderbird_partner(osv.osv_memory):
res['datas'] = base64.b64encode(mail)
res['res_id'] = res_id
obj_attch.create(cr, uid, res)
- server_tools_pool.history_message(cr, uid, model, res_id, msg_new)
+ threads = self.pool.get(model).browse(cr, uid, res_id)
+
+ thread_pool.message_append(cr, uid,
+ [threads],
+ subject = msg.get('subject'),
+ details = msg.get('body_text'),
+ email_to = msg.get('to'),
+ email_from = msg.get('from'),
+ email_cc = msg.get('cc'),
+ message_id = msg.get('message-id'),
+ references = msg.get('references', False) or msg.get('in-reply-to', False),
+ attachments = msg.get('attachments', {}),
+ email_date = msg.get('date'),
+ body_html= msg.get('body_html'),
+ subtype = msg.get('subtype'),
+ headers = msg.get('headers'),
+ reply_to = msg.get('reply'))
res_ids.append(res_id)
return len(res_ids)
@@ -201,7 +111,7 @@ class thunderbird_partner(osv.osv_memory):
dictcreate = dict(vals)
model = str(dictcreate.get('model'))
message = dictcreate.get('message')
- return self.pool.get('email.server.tools').process_email(cr, uid, model, message, attach=True, context=None)
+ return self.pool.get('mail.thread').message_process(cr, uid, model, message)
def search_message(self, cr, uid, message, context=None):
#@param message: string of mail which is read from EML File
@@ -209,26 +119,26 @@ class thunderbird_partner(osv.osv_memory):
references = []
dictcreate = dict(message)
msg = dictcreate.get('message')
- msg = self.pool.get('email.server.tools').parse_message(msg)
+ msg = self.pool.get('mail.message').parse_message(msg)
message_id = msg.get('message-id')
refs = msg.get('references',False)
references = False
if refs:
references = refs.split()
- msg_pool = self.pool.get('mailgate.message')
+ mail_message = self.pool.get('mail.message')
model = ''
res_id = 0
if message_id:
- msg_ids = msg_pool.search(cr, uid, [('message_id','=',message_id)])
+ msg_ids = mail_message.search(cr, uid, [('message_id','=',message_id)])
if msg_ids and len(msg_ids):
- msg = msg_pool.browse(cr, uid, msg_ids[0])
+ msg = mail_message.browse(cr, uid, msg_ids[0])
model = msg.model
res_id = msg.res_id
else:
if references :
- msg_ids = msg_pool.search(cr, uid, [('message_id','in',references)])
+ msg_ids = mail_message.search(cr, uid, [('message_id','in',references)])
if msg_ids and len(msg_ids):
- msg = msg_pool.browse(cr, uid, msg_ids[0])
+ msg = mail_message.browse(cr, uid, msg_ids[0])
model = msg.model
res_id = msg.res_id
return (model,res_id)