[REF] mass_mailing: first refactor
Mail statistics are now stored onto a separated object (mail.mail.statistics), allowing to handle emails separately from statistics (among other removing mail.mail entries while keeping statistics). Everything linnked to opened/replied/bounce is not managed by mass_mailing, removed added code in mail module. bzr revid: tde@openerp.com-20130913115408-322cyjipdg680as6
This commit is contained in:
parent
23f9324b94
commit
ed62d1dac7
|
@ -5,7 +5,6 @@ import openerp
|
||||||
from openerp import SUPERUSER_ID
|
from openerp import SUPERUSER_ID
|
||||||
import openerp.addons.web.http as http
|
import openerp.addons.web.http as http
|
||||||
from openerp.addons.web.controllers.main import content_disposition
|
from openerp.addons.web.controllers.main import content_disposition
|
||||||
from openerp.addons.web.http import request
|
|
||||||
|
|
||||||
|
|
||||||
class MailController(http.Controller):
|
class MailController(http.Controller):
|
||||||
|
@ -38,10 +37,3 @@ class MailController(http.Controller):
|
||||||
except psycopg2.Error:
|
except psycopg2.Error:
|
||||||
pass
|
pass
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='admin')
|
|
||||||
def track_read_email(self, mail_id):
|
|
||||||
""" Email tracking. """
|
|
||||||
mail_mail = request.registry.get('mail.mail')
|
|
||||||
mail_mail.set_opened(request.cr, request.uid, [mail_id])
|
|
||||||
return False
|
|
||||||
|
|
|
@ -51,12 +51,6 @@
|
||||||
<field name="value">catchall</field>
|
<field name="value">catchall</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Bounce Email Alias -->
|
|
||||||
<record id="icp_mail_bounce_alias" model="ir.config_parameter">
|
|
||||||
<field name="key">mail.bounce.alias</field>
|
|
||||||
<field name="value">bounce</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Discussion subtype for messaging / Chatter -->
|
<!-- Discussion subtype for messaging / Chatter -->
|
||||||
<record id="mt_comment" model="mail.message.subtype">
|
<record id="mt_comment" model="mail.message.subtype">
|
||||||
<field name="name">Discussions</field>
|
<field name="name">Discussions</field>
|
||||||
|
|
|
@ -62,17 +62,6 @@ class mail_mail(osv.Model):
|
||||||
# and during unlink() we will not cascade delete the parent and its attachments
|
# and during unlink() we will not cascade delete the parent and its attachments
|
||||||
'notification': fields.boolean('Is Notification',
|
'notification': fields.boolean('Is Notification',
|
||||||
help='Mail has been created to notify people of an existing mail.message'),
|
help='Mail has been created to notify people of an existing mail.message'),
|
||||||
# Bounce and tracking
|
|
||||||
'opened': fields.datetime(
|
|
||||||
'Opened',
|
|
||||||
help='Date when this email has been opened for the first time.'),
|
|
||||||
'replied': fields.datetime(
|
|
||||||
'Replied',
|
|
||||||
help='Date when this email has been replied for the first time.'),
|
|
||||||
'bounced': fields.datetime(
|
|
||||||
'Bounced',
|
|
||||||
help='Date when this email has bounced.'
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
|
@ -106,30 +95,6 @@ class mail_mail(osv.Model):
|
||||||
def cancel(self, cr, uid, ids, context=None):
|
def cancel(self, cr, uid, ids, context=None):
|
||||||
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
|
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
|
||||||
|
|
||||||
def set_opened(self, cr, uid, ids, context=None):
|
|
||||||
""" Set as opened """
|
|
||||||
existing_ids = self.exists(cr, uid, ids, context=context)
|
|
||||||
for mail in self.browse(cr, uid, existing_ids, context=context):
|
|
||||||
if not mail.opened:
|
|
||||||
self.write(cr, uid, [mail.id], {'opened': fields.datetime.now()}, context=context)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_replied(self, cr, uid, ids, context=None):
|
|
||||||
""" Set as replied """
|
|
||||||
existing_ids = self.exists(cr, uid, ids, context=context)
|
|
||||||
for mail in self.browse(cr, uid, existing_ids, context=context):
|
|
||||||
if not mail.replied:
|
|
||||||
self.write(cr, uid, [mail.id], {'replied': fields.datetime.now()}, context=context)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_bounced(self, cr, uid, ids, context=None):
|
|
||||||
""" Set as bounced """
|
|
||||||
existing_ids = self.exists(cr, uid, ids, context=context)
|
|
||||||
for mail in self.browse(cr, uid, existing_ids, context=context):
|
|
||||||
if not mail.bounced:
|
|
||||||
self.write(cr, uid, [mail.id], {'bounced': fields.datetime.now()}, context=context)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def process_email_queue(self, cr, uid, ids=None, context=None):
|
def process_email_queue(self, cr, uid, ids=None, context=None):
|
||||||
"""Send immediately queued messages, committing after each
|
"""Send immediately queued messages, committing after each
|
||||||
message is sent - this is not transactional and should
|
message is sent - this is not transactional and should
|
||||||
|
@ -200,15 +165,6 @@ class mail_mail(osv.Model):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
|
||||||
if not mail.auto_delete:
|
|
||||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
|
||||||
track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id)
|
|
||||||
print base_url, track_url
|
|
||||||
return '<img src="%s" alt=""/>' % track_url
|
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
|
def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
|
||||||
""" If subject is void and record_name defined: '<Author> posted on <Resource>'
|
""" If subject is void and record_name defined: '<Author> posted on <Resource>'
|
||||||
|
|
||||||
|
@ -233,11 +189,8 @@ class mail_mail(osv.Model):
|
||||||
|
|
||||||
# generate footer
|
# generate footer
|
||||||
link = self._get_partner_access_link(cr, uid, mail, partner, context=context)
|
link = self._get_partner_access_link(cr, uid, mail, partner, context=context)
|
||||||
tracking_url = self._get_tracking_url(cr, uid, mail, partner, context=context)
|
|
||||||
if link:
|
if link:
|
||||||
body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div')
|
body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div')
|
||||||
if tracking_url:
|
|
||||||
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||||
|
|
|
@ -41,11 +41,6 @@
|
||||||
<field name="model"/>
|
<field name="model"/>
|
||||||
<field name="res_id"/>
|
<field name="res_id"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Tracking">
|
|
||||||
<field name="opened"/>
|
|
||||||
<field name="replied"/>
|
|
||||||
<field name="bounced"/>
|
|
||||||
</group>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<group string="Headers">
|
<group string="Headers">
|
||||||
|
|
|
@ -778,7 +778,6 @@ class mail_thread(osv.AbstractModel):
|
||||||
"""
|
"""
|
||||||
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
||||||
fallback_model = model
|
fallback_model = model
|
||||||
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
|
|
||||||
|
|
||||||
# Get email.message.Message variables for future processing
|
# Get email.message.Message variables for future processing
|
||||||
message_id = message.get('Message-Id')
|
message_id = message.get('Message-Id')
|
||||||
|
@ -787,25 +786,6 @@ class mail_thread(osv.AbstractModel):
|
||||||
references = decode_header(message, 'References')
|
references = decode_header(message, 'References')
|
||||||
in_reply_to = decode_header(message, 'In-Reply-To')
|
in_reply_to = decode_header(message, 'In-Reply-To')
|
||||||
|
|
||||||
# 0. Verify whether this is a bounced email (wrong destination,...) -> use it to collect data, such as dead leads
|
|
||||||
if bounce_alias in email_to:
|
|
||||||
bounce_match = tools.bounce_re.search(email_to)
|
|
||||||
if bounce_match:
|
|
||||||
bounced_mail_id = bounce_match.group(1)
|
|
||||||
self.pool['mail.mail'].set_bounced(cr, uid, [bounced_mail_id], context=context)
|
|
||||||
if self.pool['mail.mail'].exists(cr, uid, bounced_mail_id):
|
|
||||||
mail = self.pool['mail.mail'].browse(cr, uid, bounced_mail_id, context=context)
|
|
||||||
bounced_model = mail.model
|
|
||||||
bounced_thread_id = mail.res_id
|
|
||||||
else:
|
|
||||||
bounced_model = bounce_match.group(2)
|
|
||||||
bounced_thread_id = int(bounce_match.group(3)) if bounce_match.group(3) else 0
|
|
||||||
_logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
|
|
||||||
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
|
|
||||||
if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce'):
|
|
||||||
self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
|
|
||||||
return []
|
|
||||||
|
|
||||||
# 1. Verify if this is a reply to an existing thread
|
# 1. Verify if this is a reply to an existing thread
|
||||||
thread_references = references or in_reply_to
|
thread_references = references or in_reply_to
|
||||||
ref_match = thread_references and tools.reference_re.search(thread_references)
|
ref_match = thread_references and tools.reference_re.search(thread_references)
|
||||||
|
@ -894,6 +874,40 @@ class mail_thread(osv.AbstractModel):
|
||||||
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
||||||
"Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
|
"Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
|
||||||
|
|
||||||
|
def message_route_process(self, cr, uid, msg, routes, context=None):
|
||||||
|
# postpone setting msg.partner_ids after message_post, to avoid double notifications
|
||||||
|
partner_ids = msg.pop('partner_ids', [])
|
||||||
|
thread_id = False
|
||||||
|
for model, thread_id, custom_values, user_id, alias in routes:
|
||||||
|
if self._name == 'mail.thread':
|
||||||
|
context.update({'thread_model': model})
|
||||||
|
if model:
|
||||||
|
model_pool = self.pool[model]
|
||||||
|
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||||
|
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
||||||
|
(msg['message_id'], model)
|
||||||
|
|
||||||
|
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||||
|
# email gateway become a follower of all inbound messages
|
||||||
|
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
||||||
|
if thread_id and hasattr(model_pool, 'message_update'):
|
||||||
|
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
|
||||||
|
else:
|
||||||
|
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx)
|
||||||
|
else:
|
||||||
|
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||||
|
model_pool = self.pool.get('mail.thread')
|
||||||
|
if not hasattr(model_pool, 'message_post'):
|
||||||
|
context['thread_model'] = model
|
||||||
|
model_pool = self.pool['mail.thread']
|
||||||
|
new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **msg)
|
||||||
|
|
||||||
|
if partner_ids:
|
||||||
|
# postponed after message_post, because this is an external message and we don't want to create
|
||||||
|
# duplicate emails due to notifications
|
||||||
|
self.pool.get('mail.message').write(cr, uid, [new_msg_id], {'partner_ids': partner_ids}, context=context)
|
||||||
|
return thread_id
|
||||||
|
|
||||||
def message_process(self, cr, uid, model, message, custom_values=None,
|
def message_process(self, cr, uid, model, message, custom_values=None,
|
||||||
save_original=False, strip_attachments=False,
|
save_original=False, strip_attachments=False,
|
||||||
thread_id=None, context=None):
|
thread_id=None, context=None):
|
||||||
|
@ -946,8 +960,7 @@ class mail_thread(osv.AbstractModel):
|
||||||
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
||||||
if strip_attachments:
|
if strip_attachments:
|
||||||
msg.pop('attachments', None)
|
msg.pop('attachments', None)
|
||||||
# postpone setting msg.partner_ids after message_post, to avoid double notifications
|
|
||||||
partner_ids = msg.pop('partner_ids', [])
|
|
||||||
if msg.get('message_id'): # should always be True as message_parse generate one if missing
|
if msg.get('message_id'): # should always be True as message_parse generate one if missing
|
||||||
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
|
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
|
||||||
('message_id', '=', msg.get('message_id')),
|
('message_id', '=', msg.get('message_id')),
|
||||||
|
@ -959,36 +972,7 @@ class mail_thread(osv.AbstractModel):
|
||||||
|
|
||||||
# find possible routes for the message
|
# find possible routes for the message
|
||||||
routes = self.message_route(cr, uid, msg_txt, msg, model, thread_id, custom_values, context=context)
|
routes = self.message_route(cr, uid, msg_txt, msg, model, thread_id, custom_values, context=context)
|
||||||
thread_id = False
|
thread_id = self.message_route_process(cr, uid, msg, routes, context=context)
|
||||||
for model, thread_id, custom_values, user_id, alias in routes:
|
|
||||||
if self._name == 'mail.thread':
|
|
||||||
context.update({'thread_model': model})
|
|
||||||
if model:
|
|
||||||
model_pool = self.pool[model]
|
|
||||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
|
||||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
|
||||||
(msg['message_id'], model)
|
|
||||||
|
|
||||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
|
||||||
# email gateway become a follower of all inbound messages
|
|
||||||
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
|
||||||
if thread_id and hasattr(model_pool, 'message_update'):
|
|
||||||
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
|
|
||||||
else:
|
|
||||||
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx)
|
|
||||||
else:
|
|
||||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
|
||||||
model_pool = self.pool.get('mail.thread')
|
|
||||||
if not hasattr(model_pool, 'message_post'):
|
|
||||||
context['thread_model'] = model
|
|
||||||
model_pool = self.pool['mail.thread']
|
|
||||||
new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **msg)
|
|
||||||
|
|
||||||
if partner_ids:
|
|
||||||
# postponed after message_post, because this is an external message and we don't want to create
|
|
||||||
# duplicate emails due to notifications
|
|
||||||
self.pool.get('mail.message').write(cr, uid, [new_msg_id], {'partner_ids': partner_ids}, context=context)
|
|
||||||
|
|
||||||
return thread_id
|
return thread_id
|
||||||
|
|
||||||
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
|
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
|
||||||
|
@ -1044,15 +1028,6 @@ class mail_thread(osv.AbstractModel):
|
||||||
self.write(cr, uid, ids, update_vals, context=context)
|
self.write(cr, uid, ids, update_vals, context=context)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
|
|
||||||
"""Called by ``message_process`` when a bounce email (such as Undelivered
|
|
||||||
Mail Returned to Sender) is received for an existing thread. The default
|
|
||||||
behavior is to check is an integer ``message_bounce`` column exists.
|
|
||||||
If it is the case, its content is incremented. """
|
|
||||||
if self._all_columns.get('message_bounce'):
|
|
||||||
for obj in self.browse(cr, uid, ids, context=context):
|
|
||||||
self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
|
|
||||||
|
|
||||||
def _message_extract_payload(self, message, save_original=False):
|
def _message_extract_payload(self, message, save_original=False):
|
||||||
"""Extract body as HTML and attachments from the mail message"""
|
"""Extract body as HTML and attachments from the mail message"""
|
||||||
attachments = []
|
attachments = []
|
||||||
|
@ -1303,8 +1278,8 @@ class mail_thread(osv.AbstractModel):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
||||||
subtype=None, parent_id=False, attachments=None, context=None,
|
subtype=None, parent_id=False, attachments=None, context=None,
|
||||||
content_subtype='html', **kwargs):
|
content_subtype='html', **kwargs):
|
||||||
""" Post a new message in an existing thread, returning the new
|
""" Post a new message in an existing thread, returning the new
|
||||||
mail.message ID.
|
mail.message ID.
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,6 @@ class project_configuration(osv.TransientModel):
|
||||||
'Alias Domain',
|
'Alias Domain',
|
||||||
help="If you have setup a catch-all email domain redirected to the OpenERP server, enter the domain name here."
|
help="If you have setup a catch-all email domain redirected to the OpenERP server, enter the domain name here."
|
||||||
),
|
),
|
||||||
'alias_bounce': fields.char(
|
|
||||||
'Return-Path for Emails',
|
|
||||||
help="Return-Path of send Emails. Used to compute bounced emails.",
|
|
||||||
),
|
|
||||||
'alias_catchall': fields.char(
|
|
||||||
'Default Alias',
|
|
||||||
help='Default email alias',
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_default_alias_domain(self, cr, uid, ids, context=None):
|
def get_default_alias_domain(self, cr, uid, ids, context=None):
|
||||||
|
@ -56,21 +48,3 @@ class project_configuration(osv.TransientModel):
|
||||||
config_parameters = self.pool.get("ir.config_parameter")
|
config_parameters = self.pool.get("ir.config_parameter")
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
for record in self.browse(cr, uid, ids, context=context):
|
||||||
config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context)
|
config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context)
|
||||||
|
|
||||||
def get_default_alias_bounce(self, cr, uid, ids, context=None):
|
|
||||||
alias_bounce = self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.bounce.alias", context=context)
|
|
||||||
return {'alias_bounce': alias_bounce}
|
|
||||||
|
|
||||||
def set_alias_bounce(self, cr, uid, ids, context=None):
|
|
||||||
config_parameters = self.pool.get("ir.config_parameter")
|
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
|
||||||
config_parameters.set_param(cr, uid, "mail.bounce.alias", record.alias_bounce or '', context=context)
|
|
||||||
|
|
||||||
def get_default_alias_catchall(self, cr, uid, ids, context=None):
|
|
||||||
alias_catchall = self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.alias", context=context)
|
|
||||||
return {'alias_catchall': alias_catchall}
|
|
||||||
|
|
||||||
def set_alias_catchall(self, cr, uid, ids, context=None):
|
|
||||||
config_parameters = self.pool.get("ir.config_parameter")
|
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
|
||||||
config_parameters.set_param(cr, uid, "mail.catchall.alias", record.alias_catchall or '', context=context)
|
|
||||||
|
|
|
@ -11,14 +11,6 @@
|
||||||
<label for="alias_domain" class="oe_inline"/>
|
<label for="alias_domain" class="oe_inline"/>
|
||||||
<field name="alias_domain" placeholder="mycompany.my.openerp.com" class="oe_inline"/>
|
<field name="alias_domain" placeholder="mycompany.my.openerp.com" class="oe_inline"/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label for="alias_bounce" class="oe_inline"/>
|
|
||||||
<field name="alias_bounce" placeholder="bounce" class="oe_inline"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="alias_catchall" class="oe_inline"/>
|
|
||||||
<field name="alias_catchall" placeholder="catchall" class="oe_inline"/>
|
|
||||||
</div>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -19,10 +19,9 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from . import test_mail_mail, test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
|
from . import test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
|
||||||
|
|
||||||
checks = [
|
checks = [
|
||||||
# test_mail_mail,
|
|
||||||
test_mail_group,
|
test_mail_group,
|
||||||
test_mail_message,
|
test_mail_message,
|
||||||
test_mail_features,
|
test_mail_features,
|
||||||
|
|
|
@ -22,3 +22,4 @@
|
||||||
import mass_mailing
|
import mass_mailing
|
||||||
import mail_mail
|
import mail_mail
|
||||||
import wizard
|
import wizard
|
||||||
|
import controllers
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Mass Mailing Campaigns',
|
'name': 'Mass Mailing Campaigns',
|
||||||
|
'description': """TODO""",
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'author': 'OpenERP',
|
'author': 'OpenERP',
|
||||||
'website': 'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
|
@ -31,11 +32,10 @@
|
||||||
'web_kanban_gauge',
|
'web_kanban_gauge',
|
||||||
'web_kanban_sparkline',
|
'web_kanban_sparkline',
|
||||||
],
|
],
|
||||||
'description': """TODO""",
|
|
||||||
'data': [
|
'data': [
|
||||||
|
'mail_data.xml',
|
||||||
'mass_mailing_view.xml',
|
'mass_mailing_view.xml',
|
||||||
'mass_mailing_demo.xml',
|
'mass_mailing_demo.xml',
|
||||||
'mail_mail_view.xml',
|
|
||||||
'wizard/mail_compose_message_view.xml',
|
'wizard/mail_compose_message_view.xml',
|
||||||
'wizard/mail_mass_mailing_create_segment.xml',
|
'wizard/mail_mass_mailing_create_segment.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import main
|
||||||
|
|
||||||
|
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
import openerp.addons.web.http as http
|
||||||
|
from openerp.addons.web.http import request
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailController(http.Controller):
|
||||||
|
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='admin')
|
||||||
|
def track_mail_open(self, mail_id):
|
||||||
|
""" Email tracking. """
|
||||||
|
mail_mail_stats = request.registry.get('mail.mail.statistics')
|
||||||
|
mail_mail_stats.set_opened(request.cr, request.uid, mail_ids=[mail_id])
|
||||||
|
return False
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<!-- Bounce Email Alias -->
|
||||||
|
<record id="icp_mail_bounce_alias" model="ir.config_parameter">
|
||||||
|
<field name="key">mail.bounce.alias</field>
|
||||||
|
<field name="value">bounce</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Management Solution
|
# OpenERP, Open Source Management Solution
|
||||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -19,7 +19,11 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from openerp.osv import osv, fields
|
from urlparse import urljoin
|
||||||
|
|
||||||
|
from openerp import tools
|
||||||
|
from openerp import SUPERUSER_ID
|
||||||
|
from openerp.osv import osv
|
||||||
|
|
||||||
|
|
||||||
class MailMail(osv.Model):
|
class MailMail(osv.Model):
|
||||||
|
@ -27,23 +31,29 @@ class MailMail(osv.Model):
|
||||||
_name = 'mail.mail'
|
_name = 'mail.mail'
|
||||||
_inherit = ['mail.mail']
|
_inherit = ['mail.mail']
|
||||||
|
|
||||||
_columns = {
|
def create(self, cr, uid, values, context=None):
|
||||||
'mass_mailing_segment_id': fields.many2one(
|
""" Override mail_mail creation to create an entry in mail.mail.statistics """
|
||||||
'mail.mass_mailing.segment', 'Mass Mailing Segment',
|
# TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
|
||||||
ondelete='set null',
|
mail_id = super(MailMail, self).create(cr, uid, values, context=context)
|
||||||
),
|
message_id = self.browse(cr, SUPERUSER_ID, mail_id).message_id
|
||||||
'mass_mailing_campaign_id': fields.related(
|
self.pool['mail.mail.statistics'].create(
|
||||||
'mass_mailing_segment_id', 'mass_mailing_campaign_id',
|
cr, uid, {
|
||||||
type='many2one', ondelete='set null',
|
'mail_mail_id': mail_id,
|
||||||
relation='mail.mass_mailing.campaign',
|
'message_id': message_id,
|
||||||
string='Mass Mailing Campaign',
|
}, context=context)
|
||||||
store=True, readonly=True,
|
return mail_id
|
||||||
),
|
|
||||||
'template_id': fields.related(
|
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
||||||
'mass_mailing_segment_id', 'template_id',
|
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||||
type='many2one', ondelete='set null',
|
track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id)
|
||||||
relation='email.template',
|
return '<img src="%s" alt=""/>' % track_url
|
||||||
string='Email Template',
|
|
||||||
store=True, readonly=True,
|
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||||
),
|
""" Override to add the tracking URL to the body. """
|
||||||
}
|
body = super(MailMail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||||
|
|
||||||
|
# generate tracking URL
|
||||||
|
tracking_url = self._get_tracking_url(cr, uid, mail, partner, context=context)
|
||||||
|
if tracking_url:
|
||||||
|
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
||||||
|
return body
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<!-- FOLLOWERS !-->
|
|
||||||
<record model="ir.ui.view" id="mail_mail_form_mass_mailing">
|
|
||||||
<field name="name">mail.mail.form.mass_mailing</field>
|
|
||||||
<field name="model">mail.mail</field>
|
|
||||||
<field name="inherit_id" ref="mail.view_mail_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='opened']" position="before">
|
|
||||||
<field name="mass_mailing_campaign_id"/>
|
|
||||||
<field name="mass_mailing_segment_id"/>
|
|
||||||
<field name="template_id"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openerp import tools
|
||||||
|
from openerp.addons.mail.mail_thread import decode_header
|
||||||
|
from openerp.osv import osv
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MailThread(osv.Model):
|
||||||
|
""" Update MailThread to add the feature of bounced emails and replied emails
|
||||||
|
in message_process. """
|
||||||
|
_name = 'mail.thread'
|
||||||
|
_inherit = ['mail.thread']
|
||||||
|
|
||||||
|
def message_route_check_bounce(self, cr, uid, message, context=None):
|
||||||
|
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
|
||||||
|
message_id = message.get('Message-Id')
|
||||||
|
email_from = decode_header(message, 'From')
|
||||||
|
email_to = decode_header(message, 'To')
|
||||||
|
|
||||||
|
# 0. Verify whether this is a bounced email (wrong destination,...) -> use it to collect data, such as dead leads
|
||||||
|
if bounce_alias in email_to:
|
||||||
|
bounce_match = tools.bounce_re.search(email_to)
|
||||||
|
if bounce_match:
|
||||||
|
bounced_mail_id = bounce_match.group(1)
|
||||||
|
self.pool['mail.mail'].set_bounced(cr, uid, [bounced_mail_id], context=context)
|
||||||
|
if self.pool['mail.mail'].exists(cr, uid, bounced_mail_id):
|
||||||
|
mail = self.pool['mail.mail'].browse(cr, uid, bounced_mail_id, context=context)
|
||||||
|
bounced_model = mail.model
|
||||||
|
bounced_thread_id = mail.res_id
|
||||||
|
else:
|
||||||
|
bounced_model = bounce_match.group(2)
|
||||||
|
bounced_thread_id = int(bounce_match.group(3)) if bounce_match.group(3) else 0
|
||||||
|
_logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
|
||||||
|
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
|
||||||
|
if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce'):
|
||||||
|
self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
|
||||||
|
custom_values=None, context=None):
|
||||||
|
if not self.message_route_check_bounce(cr, uid, message, context=context):
|
||||||
|
return []
|
||||||
|
return super(MailThread, self).message_route(cr, uid, message, message_dict, model, thread_id, custom_values, context)
|
||||||
|
|
||||||
|
def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
|
||||||
|
"""Called by ``message_process`` when a bounce email (such as Undelivered
|
||||||
|
Mail Returned to Sender) is received for an existing thread. The default
|
||||||
|
behavior is to check is an integer ``message_bounce`` column exists.
|
||||||
|
If it is the case, its content is incremented. """
|
||||||
|
if self._all_columns.get('message_bounce'):
|
||||||
|
for obj in self.browse(cr, uid, ids, context=context):
|
||||||
|
self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
|
||||||
|
|
||||||
|
def message_route_process(self, cr, uid, msg, routes, context=None):
|
||||||
|
if msg.get('message_id'):
|
||||||
|
self.pool['mail.mail.statistics'].set_replied(cr, uid, mail_message_ids=[msg.get('message_id')], context=context)
|
||||||
|
return super(MailThread, self).message_route_process(cr, uid, msg, routes, context=context)
|
|
@ -38,42 +38,46 @@ class MassMailingCampaign(osv.Model):
|
||||||
results = dict.fromkeys(ids, False)
|
results = dict.fromkeys(ids, False)
|
||||||
for campaign in self.browse(cr, uid, ids, context=context):
|
for campaign in self.browse(cr, uid, ids, context=context):
|
||||||
results[campaign.id] = {
|
results[campaign.id] = {
|
||||||
'sent': len(campaign.mail_ids),
|
'sent': len(campaign.statistics_ids),
|
||||||
'opened': len([mail for mail in campaign.mail_ids if mail.opened]),
|
|
||||||
'replied': len([mail for mail in campaign.mail_ids if mail.replied]),
|
|
||||||
'bounced': len([mail for mail in campaign.mail_ids if mail.bounced]),
|
|
||||||
# delivered: shouldn't be: all mails - (failed + bounced) ?
|
# delivered: shouldn't be: all mails - (failed + bounced) ?
|
||||||
'delivered': len([mail for mail in campaign.mail_ids if mail.state == 'sent' and not mail.bounced]),
|
'delivered': len([stat for stat in campaign.statistics_ids if not stat.bounced]), # stat.state == 'sent' and
|
||||||
|
'opened': len([stat for stat in campaign.statistics_ids if stat.opened]),
|
||||||
|
'replied': len([stat for stat in campaign.statistics_ids if stat.replied]),
|
||||||
|
'bounced': len([stat for stat in campaign.statistics_ids if stat.bounced]),
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _get_segment_kanban_ids(self, cr, uid, ids, name, arg, context=None):
|
def _get_mass_mailing_kanban_ids(self, cr, uid, ids, name, arg, context=None):
|
||||||
results = dict.fromkeys(ids, '')
|
results = dict.fromkeys(ids, '')
|
||||||
for campaign in self.browse(cr, uid, ids, context=context):
|
for campaign in self.browse(cr, uid, ids, context=context):
|
||||||
segment_results = []
|
mass_mailing_results = []
|
||||||
for segment in campaign.segment_ids:
|
for mass_mailing in campaign.mass_mailing_ids:
|
||||||
segment_object = {}
|
mass_mailing_object = {}
|
||||||
for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
|
for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
|
||||||
segment_object[attr] = getattr(segment, attr)
|
mass_mailing_object[attr] = getattr(mass_mailing, attr)
|
||||||
segment_results.append(segment_object)
|
mass_mailing_results.append(mass_mailing_object)
|
||||||
results[campaign.id] = segment_results
|
results[campaign.id] = mass_mailing_results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.char(
|
'name': fields.char(
|
||||||
'Campaign Name', required=True,
|
'Campaign Name', required=True,
|
||||||
),
|
),
|
||||||
'segment_ids': fields.one2many(
|
'user_id': fields.many2one(
|
||||||
'mail.mass_mailing.segment', 'mass_mailing_campaign_id',
|
'res.users', 'Responsible',
|
||||||
'Segments',
|
required=True,
|
||||||
),
|
),
|
||||||
'segment_kanban_ids': fields.function(
|
'mass_mailing_ids': fields.one2many(
|
||||||
_get_segment_kanban_ids,
|
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
||||||
type='text', string='Segments (kanban data)',
|
'Mass Mailings',
|
||||||
help='This field has for purpose to gather data about segment to display them in kanban view as nested kanban views is not possible currently',
|
|
||||||
),
|
),
|
||||||
'mail_ids': fields.one2many(
|
'mass_mailing_kanban_ids': fields.function(
|
||||||
'mail.mail', 'mass_mailing_campaign_id',
|
_get_mass_mailing_kanban_ids,
|
||||||
|
type='text', string='Mass Mailings (kanban data)',
|
||||||
|
help='This field has for purpose to gather data about mass mailings to display them in kanban view as nested kanban views is not possible currently',
|
||||||
|
),
|
||||||
|
'statistics_ids': fields.one2many(
|
||||||
|
'mail.mail.statistics', 'mass_mailing_campaign_id',
|
||||||
'Sent Emails',
|
'Sent Emails',
|
||||||
),
|
),
|
||||||
'color': fields.integer('Color Index'),
|
'color': fields.integer('Color Index'),
|
||||||
|
@ -105,17 +109,21 @@ class MassMailingCampaign(osv.Model):
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def launch_segment_create_wizard(self, cr, uid, ids, context=None):
|
# _defaults = {
|
||||||
|
# 'user_id': lambda self, cr, uid, ctx=None: uid,
|
||||||
|
# },
|
||||||
|
|
||||||
|
def launch_mass_mailing_create_wizard(self, cr, uid, ids, context=None):
|
||||||
ctx = dict(context)
|
ctx = dict(context)
|
||||||
ctx.update({
|
ctx.update({
|
||||||
'default_mass_mailing_campaign_id': ids[0],
|
'default_mass_mailing_campaign_id': ids[0],
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
'name': _('Create a Segment for the Campaign'),
|
'name': _('Create a Mass Mailing for the Campaign'),
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
'view_type': 'form',
|
'view_type': 'form',
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'res_model': 'mail.mass_mailing.segment.create',
|
'res_model': 'mail.mass_mailing.create',
|
||||||
'views': [(False, 'form')],
|
'views': [(False, 'form')],
|
||||||
'view_id': False,
|
'view_id': False,
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
|
@ -123,12 +131,12 @@ class MassMailingCampaign(osv.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MassMailingSegment(osv.Model):
|
class MassMailing(osv.Model):
|
||||||
""" MassMailingSegment models a segment for a mass mailign campaign. A segment
|
""" MassMailing models a wave of emails for a mass mailign campaign.
|
||||||
is an occurence of sending emails. """
|
A mass mailing is an occurence of sending emails. """
|
||||||
|
|
||||||
_name = 'mail.mass_mailing.segment'
|
_name = 'mail.mass_mailing'
|
||||||
_description = 'Segment of a mass mailing campaign'
|
_description = 'Wave of sending emails'
|
||||||
# number of periods for tracking mail_mail statistics
|
# number of periods for tracking mail_mail statistics
|
||||||
_period_number = 6
|
_period_number = 6
|
||||||
|
|
||||||
|
@ -162,7 +170,7 @@ class MassMailingSegment(osv.Model):
|
||||||
def _get_monthly_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
def _get_monthly_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
||||||
""" TODO
|
""" TODO
|
||||||
"""
|
"""
|
||||||
obj = self.pool['mail.mail']
|
obj = self.pool['mail.mail.statistics']
|
||||||
res = {}
|
res = {}
|
||||||
context['datetime_format'] = {
|
context['datetime_format'] = {
|
||||||
'opened': {
|
'opened': {
|
||||||
|
@ -179,22 +187,22 @@ class MassMailingSegment(osv.Model):
|
||||||
for id in ids:
|
for id in ids:
|
||||||
res[id] = {}
|
res[id] = {}
|
||||||
date_begin = self.browse(cr, uid, id, context=context).date
|
date_begin = self.browse(cr, uid, id, context=context).date
|
||||||
domain = [('mass_mailing_segment_id', '=', id), ('opened', '>=', date_begin)]
|
domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin)]
|
||||||
res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened', context=context)
|
res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened', context=context)
|
||||||
domain = [('mass_mailing_segment_id', '=', id), ('replied', '>=', date_begin)]
|
domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin)]
|
||||||
res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied', context=context)
|
res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied', context=context)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||||
""" Compute statistics of the mass mailing campaign """
|
""" Compute statistics of the mass mailing campaign """
|
||||||
results = dict.fromkeys(ids, False)
|
results = dict.fromkeys(ids, False)
|
||||||
for segment in self.browse(cr, uid, ids, context=context):
|
for mass_mailing in self.browse(cr, uid, ids, context=context):
|
||||||
results[segment.id] = {
|
results[mass_mailing.id] = {
|
||||||
'sent': len(segment.mail_ids),
|
'sent': len(mass_mailing.statistics_ids),
|
||||||
'delivered': len([mail for mail in segment.mail_ids if mail.state == 'sent' and not mail.bounced]),
|
'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]), # mail.state == 'sent' and
|
||||||
'opened': len([mail for mail in segment.mail_ids if mail.opened]),
|
'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]),
|
||||||
'replied': len([mail for mail in segment.mail_ids if mail.replied]),
|
'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]),
|
||||||
'bounced': len([mail for mail in segment.mail_ids if mail.bounced]),
|
'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]),
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@ -214,9 +222,9 @@ class MassMailingSegment(osv.Model):
|
||||||
'mass_mailing_campaign_id', 'color',
|
'mass_mailing_campaign_id', 'color',
|
||||||
type='integer', string='Color Index',
|
type='integer', string='Color Index',
|
||||||
),
|
),
|
||||||
# mail_mail data
|
# statistics data
|
||||||
'mail_ids': fields.one2many(
|
'statistics_ids': fields.one2many(
|
||||||
'mail.mail', 'mass_mailing_segment_id',
|
'mail.mail.statistics', 'mass_mailing_id',
|
||||||
'Send Emails',
|
'Send Emails',
|
||||||
),
|
),
|
||||||
'sent': fields.function(
|
'sent': fields.function(
|
||||||
|
@ -260,3 +268,95 @@ class MassMailingSegment(osv.Model):
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'date': fields.datetime.now(),
|
'date': fields.datetime.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MailMailStats(osv.Model):
|
||||||
|
""" MailMailStats models the statistics collected about emails. Those statistics
|
||||||
|
are stored in a separated model and table to avoid bloating the mail_mail table
|
||||||
|
with statistics values. This also allows to delete emails send with mass mailing
|
||||||
|
without loosing the statistics about them. """
|
||||||
|
|
||||||
|
_name = 'mail.mail.statistics'
|
||||||
|
_description = 'Email Statistics'
|
||||||
|
_rec_name = 'message_id'
|
||||||
|
_order = 'message_id'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'mail_mail_id': fields.integer(
|
||||||
|
'Mail ID',
|
||||||
|
help='ID of the related mail_mail. This field is an integer field because'
|
||||||
|
'the related mail_mail can be deleted separately from its statistics.'
|
||||||
|
),
|
||||||
|
'message_id': fields.char(
|
||||||
|
'Message-ID', required=True,
|
||||||
|
),
|
||||||
|
# campaign / wave data
|
||||||
|
'mass_mailing_id': fields.many2one(
|
||||||
|
'mail.mass_mailing', 'Mass Mailing',
|
||||||
|
ondelete='set null',
|
||||||
|
),
|
||||||
|
'mass_mailing_campaign_id': fields.related(
|
||||||
|
'mass_mailing_id', 'mass_mailing_campaign_id',
|
||||||
|
type='many2one', ondelete='set null',
|
||||||
|
relation='mail.mass_mailing.campaign',
|
||||||
|
string='Mass Mailing Campaign',
|
||||||
|
store=True, readonly=True,
|
||||||
|
),
|
||||||
|
'template_id': fields.related(
|
||||||
|
'mass_mailing_id', 'template_id',
|
||||||
|
type='many2one', ondelete='set null',
|
||||||
|
relation='email.template',
|
||||||
|
string='Email Template',
|
||||||
|
store=True, readonly=True,
|
||||||
|
),
|
||||||
|
# Bounce and tracking
|
||||||
|
'opened': fields.datetime(
|
||||||
|
'Opened',
|
||||||
|
help='Date when this email has been opened for the first time.'),
|
||||||
|
'replied': fields.datetime(
|
||||||
|
'Replied',
|
||||||
|
help='Date when this email has been replied for the first time.'),
|
||||||
|
'bounced': fields.datetime(
|
||||||
|
'Bounced',
|
||||||
|
help='Date when this email has bounced.'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||||
|
""" Set as opened """
|
||||||
|
if not ids and mail_mail_ids:
|
||||||
|
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||||
|
elif not ids and mail_message_ids:
|
||||||
|
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||||
|
else:
|
||||||
|
ids = []
|
||||||
|
for stat in self.browse(cr, uid, ids, context=context):
|
||||||
|
if not stat.opened:
|
||||||
|
self.write(cr, uid, [stat.id], {'opened': fields.datetime.now()}, context=context)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||||
|
""" Set as replied """
|
||||||
|
if not ids and mail_mail_ids:
|
||||||
|
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||||
|
elif not ids and mail_message_ids:
|
||||||
|
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||||
|
else:
|
||||||
|
ids = []
|
||||||
|
for stat in self.browse(cr, uid, ids, context=context):
|
||||||
|
if not stat.replied:
|
||||||
|
self.write(cr, uid, [stat.id], {'replied': fields.datetime.now()}, context=context)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||||
|
""" Set as bounced """
|
||||||
|
if not ids and mail_mail_ids:
|
||||||
|
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||||
|
elif not ids and mail_message_ids:
|
||||||
|
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||||
|
else:
|
||||||
|
ids = []
|
||||||
|
for stat in self.browse(cr, uid, ids, context=context):
|
||||||
|
if not stat.bounced:
|
||||||
|
self.write(cr, uid, [stat.id], {'bounced': fields.datetime.now()}, context=context)
|
||||||
|
return True
|
||||||
|
|
|
@ -20,60 +20,69 @@
|
||||||
|
|
||||||
<record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
|
<record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
|
||||||
<field name="name">Partners Newsletter</field>
|
<field name="name">Partners Newsletter</field>
|
||||||
|
<field name="user_id" eval="ref('base.user_root')"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mass_mail_segment_1" model="mail.mass_mailing.segment">
|
<record id="mass_mail_1" model="mail.mass_mailing">
|
||||||
<field name="name">First Newsletter</field>
|
<field name="name">First Newsletter</field>
|
||||||
<field name="template_id" eval="ref('mass_mail_template_1')"/>
|
<field name="template_id" eval="ref('mass_mail_template_1')"/>
|
||||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_segment_2" model="mail.mass_mailing.segment">
|
<record id="mass_mail_2" model="mail.mass_mailing">
|
||||||
<field name="name">Second Newsletter</field>
|
<field name="name">Second Newsletter</field>
|
||||||
<field name="template_id" eval="ref('mass_mail_template_2')"/>
|
<field name="template_id" eval="ref('mass_mail_template_2')"/>
|
||||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mass_mail_email_1" model="mail.mail">
|
<record id="mass_mail_email_1" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111000@OpenERP.com</field>
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_email_2" model="mail.mail">
|
<record id="mass_mail_email_2" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111001@OpenERP.com</field>
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_email_3" model="mail.mail">
|
<record id="mass_mail_email_3" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111002@OpenERP.com</field>
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_email_4" model="mail.mail">
|
<record id="mass_mail_email_4" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111003@OpenERP.com</field>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_email_5" model="mail.mail">
|
<record id="mass_mail_email_5" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111004@OpenERP.com</field>
|
||||||
<field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mass_mail_email_2_1" model="mail.mail">
|
<record id="mass_mail_email_2_1" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_2')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||||
|
<field name="message_id">1111005@OpenERP.com</field>
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_email_2_2" model="mail.mail">
|
<record id="mass_mail_email_2_2" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_2')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||||
|
<field name="message_id">1111006@OpenERP.com</field>
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="mass_mail_email_2_3" model="mail.mail">
|
<record id="mass_mail_email_2_3" model="mail.mail.statistics">
|
||||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_2')"/>
|
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||||
|
<field name="message_id">1111007@OpenERP.com</field>
|
||||||
<field name="state">sent</field>
|
<field name="state">sent</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Mass Mailing Campaign" version="7.0">
|
<form string="Mass Mailing Campaign" version="7.0">
|
||||||
<header>
|
<header>
|
||||||
<button name="launch_segment_create_wizard" type="object"
|
<button name="launch_mass_mailing_create_wizard" type="object"
|
||||||
class="oe_highlight" string="Create a New Segment"/>
|
class="oe_highlight" string="Create a New Mailing"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
|
@ -32,8 +32,8 @@
|
||||||
<field name="replied"/>
|
<field name="replied"/>
|
||||||
<field name="bounced"/>
|
<field name="bounced"/>
|
||||||
</group>
|
</group>
|
||||||
<label for="segment_ids"/>
|
<label for="mass_mailing_ids"/>
|
||||||
<field name="segment_ids"/>
|
<field name="mass_mailing_ids"/>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
@ -46,29 +46,29 @@
|
||||||
<field name="model">mail.mass_mailing.campaign</field>
|
<field name="model">mail.mass_mailing.campaign</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<kanban>
|
<kanban>
|
||||||
<field name="segment_kanban_ids"/>
|
<field name="mass_mailing_kanban_ids"/>
|
||||||
<field name='sent'/>
|
<field name='sent'/>
|
||||||
<field name='color'/>
|
<field name='color'/>
|
||||||
<templates>
|
<templates>
|
||||||
<t t-name="mass_mailing.segment">
|
<t t-name="mass_mailing.mass_mailing">
|
||||||
<div>
|
<div>
|
||||||
<h4><t t-raw="segment.name"/></h4>
|
<h4><t t-raw="mass_mailing.name"/></h4>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="oe_mail_stats">
|
<p class="oe_mail_stats">
|
||||||
<span class="oe_mail_result"><t t-raw="segment.sent"/></span><br />
|
<span class="oe_mail_result"><t t-raw="mass_mailing.sent"/></span><br />
|
||||||
Sent
|
Sent
|
||||||
</p>
|
</p>
|
||||||
<p class="oe_mail_stats">
|
<p class="oe_mail_stats">
|
||||||
<span class="oe_mail_result"><t t-raw="segment.delivered"/></span><br />
|
<span class="oe_mail_result"><t t-raw="mass_mailing.delivered"/></span><br />
|
||||||
Delivered
|
Delivered
|
||||||
</p>
|
</p>
|
||||||
<p class="oe_mail_stats">
|
<p class="oe_mail_stats">
|
||||||
<span class="oe_mail_result"><t t-raw="segment.opened"/></span><br />
|
<span class="oe_mail_result"><t t-raw="mass_mailing.opened"/></span><br />
|
||||||
Opened
|
Opened
|
||||||
</p>
|
</p>
|
||||||
<p class="oe_mail_stats">
|
<p class="oe_mail_stats">
|
||||||
<span class="oe_mail_result"><t t-raw="segment.replied"/></span><br />
|
<span class="oe_mail_result"><t t-raw="mass_mailing.replied"/></span><br />
|
||||||
Replied
|
Replied
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,8 +93,8 @@
|
||||||
<field name="replied" widget="gauge" style="width:160px; height: 120px;"
|
<field name="replied" widget="gauge" style="width:160px; height: 120px;"
|
||||||
options="{'max_field': 'sent'}"/>
|
options="{'max_field': 'sent'}"/>
|
||||||
</div>
|
</div>
|
||||||
<t t-foreach='record.segment_kanban_ids.value' t-as='segment'>
|
<t t-foreach='record.mass_mailing_kanban_ids.value' t-as='mass_mailing'>
|
||||||
<t t-call="mass_mailing.segment"/>
|
<t t-call="mass_mailing.mass_mailing"/>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="oe_clear"></div>
|
<div class="oe_clear"></div>
|
||||||
|
@ -112,13 +112,13 @@
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- MASS MAILING SEGMENTS !-->
|
<!-- MASS MAILING !-->
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_tree">
|
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
||||||
<field name="name">mail.mass_mailing.segment.tree</field>
|
<field name="name">mail.mass_mailing.tree</field>
|
||||||
<field name="model">mail.mass_mailing.segment</field>
|
<field name="model">mail.mass_mailing</field>
|
||||||
<field name="priority">10</field>
|
<field name="priority">10</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Mass Mailing Segments">
|
<tree string="Mass Mailings">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="sent"/>
|
<field name="sent"/>
|
||||||
<field name="delivered"/>
|
<field name="delivered"/>
|
||||||
|
@ -128,11 +128,11 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_form">
|
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||||
<field name="name">mail.mass_mailing.segment.form</field>
|
<field name="name">mail.mass_mailing.form</field>
|
||||||
<field name="model">mail.mass_mailing.segment</field>
|
<field name="model">mail.mass_mailing</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Mass Mailing Segment" version="7.0">
|
<form string="Mass Mailing" version="7.0">
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
|
@ -150,12 +150,12 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_form_readonly">
|
<record model="ir.ui.view" id="view_mail_mass_mailing_form_readonly">
|
||||||
<field name="name">mail.mass_mailing.segment.form</field>
|
<field name="name">mail.mass_mailing.form</field>
|
||||||
<field name="model">mail.mass_mailing.segment</field>
|
<field name="model">mail.mass_mailing</field>
|
||||||
<field name="priority">18</field>
|
<field name="priority">18</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Mass Mailing Segment" version="7.0">
|
<form string="Mass Mailing" version="7.0">
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
|
@ -173,9 +173,9 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_kanban">
|
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||||
<field name="name">mail.mass_mailing.segment.kanban</field>
|
<field name="name">mail.mass_mailing.kanban</field>
|
||||||
<field name="model">mail.mass_mailing.segment</field>
|
<field name="model">mail.mass_mailing</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<kanban>
|
<kanban>
|
||||||
<field name='color'/>
|
<field name='color'/>
|
||||||
|
@ -229,9 +229,9 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_view_mass_mailing_segments" model="ir.actions.act_window">
|
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
||||||
<field name="name">Mass Mailing Segments</field>
|
<field name="name">Mass Mailings</field>
|
||||||
<field name="res_model">mail.mass_mailing.segment</field>
|
<field name="res_model">mail.mass_mailing</field>
|
||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
@ -245,9 +245,9 @@
|
||||||
<menuitem name="Campaigns" id="menu_email_campaigns"
|
<menuitem name="Campaigns" id="menu_email_campaigns"
|
||||||
parent="mass_mailing_campaign" sequence="1"
|
parent="mass_mailing_campaign" sequence="1"
|
||||||
action="action_view_mass_mailing_campaigns"/>
|
action="action_view_mass_mailing_campaigns"/>
|
||||||
<menuitem name="Segments" id="menu_email_segments"
|
<menuitem name="Mass Mailings" id="menu_email_mass_mailings"
|
||||||
parent="mass_mailing_campaign" sequence="2"
|
parent="mass_mailing_campaign" sequence="2"
|
||||||
action="action_view_mass_mailing_segments"/>
|
action="action_view_mass_mailings"/>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,,1,1,1,0
|
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,,1,1,1,0
|
||||||
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
||||||
access_mass_mailing_segment,mail.mass_mailing.segment,model_mail_mass_mailing_segment,,1,1,1,0
|
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,,1,1,1,0
|
||||||
access_mass_mailing_segment_system,mail.mass_mailing.segment.system,model_mail_mass_mailing_segment,base.group_system,1,1,1,1
|
access_mass_mailing_system,mail.mass_mailing.system,model_mail_mass_mailing,base.group_system,1,1,1,1
|
||||||
|
access_mail_mail_statistics,mail.mail.statistics,model_mail_mail_statistics,,1,1,1,1
|
|
|
@ -34,8 +34,8 @@ class MailComposeMessage(osv.TransientModel):
|
||||||
'mass_mailing_campaign_id': fields.many2one(
|
'mass_mailing_campaign_id': fields.many2one(
|
||||||
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
||||||
),
|
),
|
||||||
'mass_mailing_segment_id': fields.many2one(
|
'mass_mailing_id': fields.many2one(
|
||||||
'mail.mass_mailing.segment', 'Mass mailing segment',
|
'mail.mass_mailing', 'Mass mailing',
|
||||||
domain="[('mass_mailing_campaign_id', '=', mass_mailing_campaign_id)]",
|
domain="[('mass_mailing_campaign_id', '=', mass_mailing_campaign_id)]",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -44,18 +44,18 @@ class MailComposeMessage(osv.TransientModel):
|
||||||
'use_mass_mailing_campaign': False,
|
'use_mass_mailing_campaign': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
def onchange_mass_mail_campaign_id(self, cr, uid, ids, mass_mailing_campaign_id, mass_mail_segment_id, context=None):
|
def onchange_mass_mail_campaign_id(self, cr, uid, ids, mass_mailing_campaign_id, mass_mailing_id, context=None):
|
||||||
if mass_mail_segment_id:
|
if mass_mailing_id:
|
||||||
segment = self.pool['mail.mass_mailing.segment'].browse(cr, uid, mass_mail_segment_id, context=context)
|
mass_mailing = self.pool['mail.mass_mailing'].browse(cr, uid, mass_mailing_id, context=context)
|
||||||
if segment.mass_mailing_campaign_id.id == mass_mailing_campaign_id:
|
if mass_mailing.mass_mailing_campaign_id.id == mass_mailing_campaign_id:
|
||||||
return {}
|
return {}
|
||||||
return {'value': {'mass_mailing_segment_id': False}}
|
return {'value': {'mass_mailing_id': False}}
|
||||||
|
|
||||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||||
""" Override method that generated the mail content by adding the mass
|
""" Override method that generated the mail content by adding the mass
|
||||||
mailing campaign, when doing pure email mass mailing. """
|
mailing campaign, when doing pure email mass mailing. """
|
||||||
res = super(MailComposeMessage, self).render_message_batch(cr, uid, wizard, res_ids, context=context)
|
res = super(MailComposeMessage, self).render_message_batch(cr, uid, wizard, res_ids, context=context)
|
||||||
if wizard.composition_mode == 'mass_mail' and wizard.use_mass_mailing_campaign and wizard.mass_mailing_segment_id: # TODO: which kind of mass mailing ?
|
if wizard.composition_mode == 'mass_mail' and wizard.use_mass_mailing_campaign and wizard.mass_mailing_id: # TODO: which kind of mass mailing ?
|
||||||
for res_id in res_ids:
|
for res_id in res_ids:
|
||||||
res[res_id]['mass_mailing_segment_id'] = wizard.mass_mailing_segment_id.id
|
res[res_id]['mass_mailing_id'] = wizard.mass_mailing_id.id
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
<div>
|
<div>
|
||||||
<group>
|
<group>
|
||||||
<field name="mass_mailing_campaign_id"
|
<field name="mass_mailing_campaign_id"
|
||||||
on_change="onchange_mass_mail_campaign_id(mass_mailing_campaign_id, mass_mailing_segment_id, context)"
|
on_change="onchange_mass_mail_campaign_id(mass_mailing_campaign_id, mass_mailing_id, context)"
|
||||||
attrs="{'invisible': ['|', ('composition_mode', '!=', 'mass_mail'), ('use_mass_mailing_campaign', '=', False)],
|
attrs="{'invisible': ['|', ('composition_mode', '!=', 'mass_mail'), ('use_mass_mailing_campaign', '=', False)],
|
||||||
'required': [('composition_mode', '=', 'mass_mail'), ('use_mass_mailing_campaign', '=', True)]}"/>
|
'required': [('composition_mode', '=', 'mass_mail'), ('use_mass_mailing_campaign', '=', True)]}"/>
|
||||||
<field name="mass_mailing_segment_id"
|
<field name="mass_mailing_id"
|
||||||
attrs="{'invisible': ['|', ('composition_mode', '!=', 'mass_mail'), ('use_mass_mailing_campaign', '=', False)],
|
attrs="{'invisible': ['|', ('composition_mode', '!=', 'mass_mail'), ('use_mass_mailing_campaign', '=', False)],
|
||||||
'required': [('composition_mode', '=', 'mass_mail'), ('use_mass_mailing_campaign', '=', True)]}"
|
'required': [('composition_mode', '=', 'mass_mail'), ('use_mass_mailing_campaign', '=', True)]}"
|
||||||
context="{'default_mass_mailing_campaign_id': mass_mailing_campaign_id,
|
context="{'default_mass_mailing_campaign_id': mass_mailing_campaign_id,
|
||||||
'default_template_id': template_id,
|
'default_template_id': template_id,
|
||||||
'default_domain': active_domain,
|
'default_domain': active_domain,
|
||||||
'form_view_ref': 'mass_mailing.view_mail_mass_mailing_segment_form_readonly'}"/>
|
'form_view_ref': 'mass_mailing.view_mail_mass_mailing_form_readonly'}"/>
|
||||||
</group>
|
</group>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
|
@ -24,11 +24,11 @@ from openerp.osv import osv, fields
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
class MailMassMailingSegmentCreate(osv.TransientModel):
|
class MailMassMailingCreate(osv.TransientModel):
|
||||||
"""Wizard to help creating mass mailing segments for a campaign. """
|
"""Wizard to help creating mass mailing waves for a campaign. """
|
||||||
|
|
||||||
_name = 'mail.mass_mailing.segment.create'
|
_name = 'mail.mass_mailing.create'
|
||||||
_description = 'Mass mailing segment creation'
|
_description = 'Mass mailing creation'
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'mass_mailing_campaign_id': fields.many2one(
|
'mass_mailing_campaign_id': fields.many2one(
|
||||||
|
@ -55,11 +55,11 @@ class MailMassMailingSegmentCreate(osv.TransientModel):
|
||||||
'email.template', 'Template', required=True,
|
'email.template', 'Template', required=True,
|
||||||
domain="[('model_id', '=', model_id)]",
|
domain="[('model_id', '=', model_id)]",
|
||||||
),
|
),
|
||||||
'segment_name': fields.char(
|
'name': fields.char(
|
||||||
'Segment name', required=True,
|
'Name', required=True,
|
||||||
),
|
),
|
||||||
'mass_mailing_segment_id': fields.many2one(
|
'mass_mailing_id': fields.many2one(
|
||||||
'mail.mass_mailing.segment', 'Mass Mailing Segment',
|
'mail.mass_mailing', 'Mass Mailing',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,23 +80,23 @@ class MailMassMailingSegmentCreate(osv.TransientModel):
|
||||||
domain = False
|
domain = False
|
||||||
return {'value': {'domain': domain}}
|
return {'value': {'domain': domain}}
|
||||||
|
|
||||||
def create_segment(self, cr, uid, ids, context=None):
|
def create_mass_mailing(self, cr, uid, ids, context=None):
|
||||||
""" Create a segment based on wizard data, and update the wizard """
|
""" Create a mass mailing based on wizard data, and update the wizard """
|
||||||
for wizard in self.browse(cr, uid, ids, context=context):
|
for wizard in self.browse(cr, uid, ids, context=context):
|
||||||
segment_values = {
|
mass_mailing_values = {
|
||||||
'name': wizard.segment_name,
|
'name': wizard.name,
|
||||||
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||||
'domain': wizard.domain,
|
'domain': wizard.domain,
|
||||||
'template_id': wizard.template_id.id,
|
'template_id': wizard.template_id.id,
|
||||||
}
|
}
|
||||||
segment_id = self.pool['mail.mass_mailing.segment'].create(cr, uid, segment_values, context=context)
|
mass_mailing_id = self.pool['mail.mass_mailing'].create(cr, uid, mass_mailing_values, context=context)
|
||||||
self.write(cr, uid, [wizard.id], {'mass_mailing_segment_id': segment_id}, context=context)
|
self.write(cr, uid, [wizard.id], {'mass_mailing_id': mass_mailing_id}, context=context)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def launch_composer(self, cr, uid, ids, context=None):
|
def launch_composer(self, cr, uid, ids, context=None):
|
||||||
""" Main wizard action: create a new segment and launch the mail.compose.message
|
""" Main wizard action: create a new mailing and launch the mail.compose.message
|
||||||
email composer with wizard data. """
|
email composer with wizard data. """
|
||||||
self.create_segment(cr, uid, ids, context=context)
|
self.create_mass_mailing(cr, uid, ids, context=context)
|
||||||
|
|
||||||
wizard = self.browse(cr, uid, ids[0], context=context)
|
wizard = self.browse(cr, uid, ids[0], context=context)
|
||||||
ctx = dict(context)
|
ctx = dict(context)
|
||||||
|
@ -107,7 +107,7 @@ class MailMassMailingSegmentCreate(osv.TransientModel):
|
||||||
'default_use_active_domain': True,
|
'default_use_active_domain': True,
|
||||||
'default_active_domain': wizard.domain,
|
'default_active_domain': wizard.domain,
|
||||||
'default_mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
'default_mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||||
'default_mass_mailing_segment_id': wizard.mass_mailing_segment_id.id,
|
'default_mass_mailing_id': wizard.mass_mailing_id.id,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
'name': _('Compose Email for Mass Mailing'),
|
'name': _('Compose Email for Mass Mailing'),
|
||||||
|
|
|
@ -3,17 +3,17 @@
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
<!-- Wizard form view -->
|
<!-- Wizard form view -->
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_create_form">
|
<record model="ir.ui.view" id="view_mail_mass_mailing_create_form">
|
||||||
<field name="name">mail.mass_mailing.segment.create.form</field>
|
<field name="name">mail.mass_mailing.create.form</field>
|
||||||
<field name="model">mail.mass_mailing.segment.create</field>
|
<field name="model">mail.mass_mailing.create</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Create a Mass Mailing Segment" version="7.0">
|
<form string="Create a Mass Mailing" version="7.0">
|
||||||
<group>
|
<group>
|
||||||
<field name="model_model" invisible="1"/>
|
<field name="model_model" invisible="1"/>
|
||||||
<field name="domain" invisible="1"/>
|
<field name="domain" invisible="1"/>
|
||||||
<p class="oe_grey" colspan="2"
|
<p class="oe_grey" colspan="2"
|
||||||
attrs="{'invisible': [('mass_mailing_campaign_id', '!=', False)]}">
|
attrs="{'invisible': [('mass_mailing_campaign_id', '!=', False)]}">
|
||||||
Please choose a mass mailing campaign that will hold the new segment.
|
Please choose a mass mailing campaign that will hold the new mailing.
|
||||||
</p>
|
</p>
|
||||||
<field name="mass_mailing_campaign_id"/>
|
<field name="mass_mailing_campaign_id"/>
|
||||||
|
|
||||||
|
@ -43,23 +43,23 @@
|
||||||
attrs="{'invisible': [('filter_id', '=', False)]}"/>
|
attrs="{'invisible': [('filter_id', '=', False)]}"/>
|
||||||
|
|
||||||
<p class="oe_grey" colspan="2"
|
<p class="oe_grey" colspan="2"
|
||||||
attrs="{'invisible': ['|', ('segment_name', '!=', False), ('template_id', '=', False)]}">
|
attrs="{'invisible': ['|', ('name', '!=', False), ('template_id', '=', False)]}">
|
||||||
Please choose the name of the campaign segment.
|
Please choose the name of the mailing.
|
||||||
</p>
|
</p>
|
||||||
<field name="segment_name"
|
<field name="name"
|
||||||
attrs="{'invisible': [('template_id', '=', False)]}"/>
|
attrs="{'invisible': [('template_id', '=', False)]}"/>
|
||||||
|
|
||||||
<button name="launch_composer" type="object"
|
<button name="launch_composer" type="object"
|
||||||
string="Create segment and launch email composer"
|
string="Create mailing and launch email composer"
|
||||||
attrs="{'invisible': [('segment_name', '=', False)]}"/>
|
attrs="{'invisible': [('name', '=', False)]}"/>
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.actions.act_window" id="action_mail_mass_mailing_segment_create">
|
<record model="ir.actions.act_window" id="action_mail_mass_mailing_create">
|
||||||
<field name="name">Create Mass Mailing Segment</field>
|
<field name="name">Create Mass Mailing</field>
|
||||||
<field name="res_model">mail.mass_mailing.segment.create</field>
|
<field name="res_model">mail.mass_mailing.create</field>
|
||||||
<field name="src_model">mail.mass_mailing.campaign</field>
|
<field name="src_model">mail.mass_mailing.campaign</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
|
|
Loading…
Reference in New Issue