[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
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.controllers.main import content_disposition
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
|
||||
class MailController(http.Controller):
|
||||
|
@ -38,10 +37,3 @@ class MailController(http.Controller):
|
|||
except psycopg2.Error:
|
||||
pass
|
||||
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>
|
||||
</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 -->
|
||||
<record id="mt_comment" model="mail.message.subtype">
|
||||
<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
|
||||
'notification': fields.boolean('Is Notification',
|
||||
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 = {
|
||||
|
@ -106,30 +95,6 @@ class mail_mail(osv.Model):
|
|||
def cancel(self, cr, uid, ids, context=None):
|
||||
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):
|
||||
"""Send immediately queued messages, committing after each
|
||||
message is sent - this is not transactional and should
|
||||
|
@ -200,15 +165,6 @@ class mail_mail(osv.Model):
|
|||
else:
|
||||
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):
|
||||
""" If subject is void and record_name defined: '<Author> posted on <Resource>'
|
||||
|
||||
|
@ -233,11 +189,8 @@ class mail_mail(osv.Model):
|
|||
|
||||
# generate footer
|
||||
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:
|
||||
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
|
||||
|
||||
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||
|
|
|
@ -41,11 +41,6 @@
|
|||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group string="Tracking">
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
</div>
|
||||
<div>
|
||||
<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'
|
||||
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
|
||||
message_id = message.get('Message-Id')
|
||||
|
@ -787,25 +786,6 @@ class mail_thread(osv.AbstractModel):
|
|||
references = decode_header(message, 'References')
|
||||
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
|
||||
thread_references = references or in_reply_to
|
||||
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:)." \
|
||||
"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,
|
||||
save_original=False, strip_attachments=False,
|
||||
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)
|
||||
if strip_attachments:
|
||||
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
|
||||
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
|
||||
('message_id', '=', msg.get('message_id')),
|
||||
|
@ -959,36 +972,7 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
# find possible routes for the message
|
||||
routes = self.message_route(cr, uid, msg_txt, msg, model, thread_id, custom_values, context=context)
|
||||
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)
|
||||
|
||||
thread_id = self.message_route_process(cr, uid, msg, routes, context=context)
|
||||
return thread_id
|
||||
|
||||
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)
|
||||
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):
|
||||
"""Extract body as HTML and attachments from the mail message"""
|
||||
attachments = []
|
||||
|
@ -1303,8 +1278,8 @@ class mail_thread(osv.AbstractModel):
|
|||
return result
|
||||
|
||||
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
||||
subtype=None, parent_id=False, attachments=None, context=None,
|
||||
content_subtype='html', **kwargs):
|
||||
subtype=None, parent_id=False, attachments=None, context=None,
|
||||
content_subtype='html', **kwargs):
|
||||
""" Post a new message in an existing thread, returning the new
|
||||
mail.message ID.
|
||||
|
||||
|
|
|
@ -32,14 +32,6 @@ class project_configuration(osv.TransientModel):
|
|||
'Alias Domain',
|
||||
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):
|
||||
|
@ -56,21 +48,3 @@ class project_configuration(osv.TransientModel):
|
|||
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.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"/>
|
||||
<field name="alias_domain" placeholder="mycompany.my.openerp.com" class="oe_inline"/>
|
||||
</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>
|
||||
</field>
|
||||
</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 = [
|
||||
# test_mail_mail,
|
||||
test_mail_group,
|
||||
test_mail_message,
|
||||
test_mail_features,
|
||||
|
|
|
@ -22,3 +22,4 @@
|
|||
import mass_mailing
|
||||
import mail_mail
|
||||
import wizard
|
||||
import controllers
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
{
|
||||
'name': 'Mass Mailing Campaigns',
|
||||
'description': """TODO""",
|
||||
'version': '1.0',
|
||||
'author': 'OpenERP',
|
||||
'website': 'http://www.openerp.com',
|
||||
|
@ -31,11 +32,10 @@
|
|||
'web_kanban_gauge',
|
||||
'web_kanban_sparkline',
|
||||
],
|
||||
'description': """TODO""",
|
||||
'data': [
|
||||
'mail_data.xml',
|
||||
'mass_mailing_view.xml',
|
||||
'mass_mailing_demo.xml',
|
||||
'mail_mail_view.xml',
|
||||
'wizard/mail_compose_message_view.xml',
|
||||
'wizard/mail_mass_mailing_create_segment.xml',
|
||||
'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
|
||||
# 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
|
||||
# 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):
|
||||
|
@ -27,23 +31,29 @@ class MailMail(osv.Model):
|
|||
_name = 'mail.mail'
|
||||
_inherit = ['mail.mail']
|
||||
|
||||
_columns = {
|
||||
'mass_mailing_segment_id': fields.many2one(
|
||||
'mail.mass_mailing.segment', 'Mass Mailing Segment',
|
||||
ondelete='set null',
|
||||
),
|
||||
'mass_mailing_campaign_id': fields.related(
|
||||
'mass_mailing_segment_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_segment_id', 'template_id',
|
||||
type='many2one', ondelete='set null',
|
||||
relation='email.template',
|
||||
string='Email Template',
|
||||
store=True, readonly=True,
|
||||
),
|
||||
}
|
||||
def create(self, cr, uid, values, context=None):
|
||||
""" Override mail_mail creation to create an entry in mail.mail.statistics """
|
||||
# TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
|
||||
mail_id = super(MailMail, self).create(cr, uid, values, context=context)
|
||||
message_id = self.browse(cr, SUPERUSER_ID, mail_id).message_id
|
||||
self.pool['mail.mail.statistics'].create(
|
||||
cr, uid, {
|
||||
'mail_mail_id': mail_id,
|
||||
'message_id': message_id,
|
||||
}, context=context)
|
||||
return mail_id
|
||||
|
||||
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
||||
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)
|
||||
return '<img src="%s" alt=""/>' % track_url
|
||||
|
||||
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)
|
||||
for campaign in self.browse(cr, uid, ids, context=context):
|
||||
results[campaign.id] = {
|
||||
'sent': len(campaign.mail_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]),
|
||||
'sent': len(campaign.statistics_ids),
|
||||
# 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
|
||||
|
||||
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, '')
|
||||
for campaign in self.browse(cr, uid, ids, context=context):
|
||||
segment_results = []
|
||||
for segment in campaign.segment_ids:
|
||||
segment_object = {}
|
||||
mass_mailing_results = []
|
||||
for mass_mailing in campaign.mass_mailing_ids:
|
||||
mass_mailing_object = {}
|
||||
for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
|
||||
segment_object[attr] = getattr(segment, attr)
|
||||
segment_results.append(segment_object)
|
||||
results[campaign.id] = segment_results
|
||||
mass_mailing_object[attr] = getattr(mass_mailing, attr)
|
||||
mass_mailing_results.append(mass_mailing_object)
|
||||
results[campaign.id] = mass_mailing_results
|
||||
return results
|
||||
|
||||
_columns = {
|
||||
'name': fields.char(
|
||||
'Campaign Name', required=True,
|
||||
),
|
||||
'segment_ids': fields.one2many(
|
||||
'mail.mass_mailing.segment', 'mass_mailing_campaign_id',
|
||||
'Segments',
|
||||
'user_id': fields.many2one(
|
||||
'res.users', 'Responsible',
|
||||
required=True,
|
||||
),
|
||||
'segment_kanban_ids': fields.function(
|
||||
_get_segment_kanban_ids,
|
||||
type='text', string='Segments (kanban data)',
|
||||
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',
|
||||
'mass_mailing_ids': fields.one2many(
|
||||
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
||||
'Mass Mailings',
|
||||
),
|
||||
'mail_ids': fields.one2many(
|
||||
'mail.mail', 'mass_mailing_campaign_id',
|
||||
'mass_mailing_kanban_ids': fields.function(
|
||||
_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',
|
||||
),
|
||||
'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.update({
|
||||
'default_mass_mailing_campaign_id': ids[0],
|
||||
})
|
||||
return {
|
||||
'name': _('Create a Segment for the Campaign'),
|
||||
'name': _('Create a Mass Mailing for the Campaign'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.mass_mailing.segment.create',
|
||||
'res_model': 'mail.mass_mailing.create',
|
||||
'views': [(False, 'form')],
|
||||
'view_id': False,
|
||||
'target': 'new',
|
||||
|
@ -123,12 +131,12 @@ class MassMailingCampaign(osv.Model):
|
|||
}
|
||||
|
||||
|
||||
class MassMailingSegment(osv.Model):
|
||||
""" MassMailingSegment models a segment for a mass mailign campaign. A segment
|
||||
is an occurence of sending emails. """
|
||||
class MassMailing(osv.Model):
|
||||
""" MassMailing models a wave of emails for a mass mailign campaign.
|
||||
A mass mailing is an occurence of sending emails. """
|
||||
|
||||
_name = 'mail.mass_mailing.segment'
|
||||
_description = 'Segment of a mass mailing campaign'
|
||||
_name = 'mail.mass_mailing'
|
||||
_description = 'Wave of sending emails'
|
||||
# number of periods for tracking mail_mail statistics
|
||||
_period_number = 6
|
||||
|
||||
|
@ -162,7 +170,7 @@ class MassMailingSegment(osv.Model):
|
|||
def _get_monthly_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
||||
""" TODO
|
||||
"""
|
||||
obj = self.pool['mail.mail']
|
||||
obj = self.pool['mail.mail.statistics']
|
||||
res = {}
|
||||
context['datetime_format'] = {
|
||||
'opened': {
|
||||
|
@ -179,22 +187,22 @@ class MassMailingSegment(osv.Model):
|
|||
for id in ids:
|
||||
res[id] = {}
|
||||
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)
|
||||
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)
|
||||
return res
|
||||
|
||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute statistics of the mass mailing campaign """
|
||||
results = dict.fromkeys(ids, False)
|
||||
for segment in self.browse(cr, uid, ids, context=context):
|
||||
results[segment.id] = {
|
||||
'sent': len(segment.mail_ids),
|
||||
'delivered': len([mail for mail in segment.mail_ids if mail.state == 'sent' and not mail.bounced]),
|
||||
'opened': len([mail for mail in segment.mail_ids if mail.opened]),
|
||||
'replied': len([mail for mail in segment.mail_ids if mail.replied]),
|
||||
'bounced': len([mail for mail in segment.mail_ids if mail.bounced]),
|
||||
for mass_mailing in self.browse(cr, uid, ids, context=context):
|
||||
results[mass_mailing.id] = {
|
||||
'sent': len(mass_mailing.statistics_ids),
|
||||
'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]), # mail.state == 'sent' and
|
||||
'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]),
|
||||
'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]),
|
||||
'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]),
|
||||
}
|
||||
return results
|
||||
|
||||
|
@ -214,9 +222,9 @@ class MassMailingSegment(osv.Model):
|
|||
'mass_mailing_campaign_id', 'color',
|
||||
type='integer', string='Color Index',
|
||||
),
|
||||
# mail_mail data
|
||||
'mail_ids': fields.one2many(
|
||||
'mail.mail', 'mass_mailing_segment_id',
|
||||
# statistics data
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mass_mailing_id',
|
||||
'Send Emails',
|
||||
),
|
||||
'sent': fields.function(
|
||||
|
@ -260,3 +268,95 @@ class MassMailingSegment(osv.Model):
|
|||
_defaults = {
|
||||
'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">
|
||||
<field name="name">Partners Newsletter</field>
|
||||
<field name="user_id" eval="ref('base.user_root')"/>
|
||||
</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="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="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
</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="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="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_email_1" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
||||
<record id="mass_mail_email_1" model="mail.mail.statistics">
|
||||
<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="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
||||
<record id="mass_mail_email_2" model="mail.mail.statistics">
|
||||
<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="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_3" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
||||
<record id="mass_mail_email_3" model="mail.mail.statistics">
|
||||
<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="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_4" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
||||
<record id="mass_mail_email_4" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111003@OpenERP.com</field>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_5" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_1')"/>
|
||||
<record id="mass_mail_email_5" model="mail.mail.statistics">
|
||||
<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="state">sent</field>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_email_2_1" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_2')"/>
|
||||
<record id="mass_mail_email_2_1" model="mail.mail.statistics">
|
||||
<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="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2_2" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_2')"/>
|
||||
<record id="mass_mail_email_2_2" model="mail.mail.statistics">
|
||||
<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="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2_3" model="mail.mail">
|
||||
<field name="mass_mailing_segment_id" eval="ref('mass_mail_segment_2')"/>
|
||||
<record id="mass_mail_email_2_3" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111007@OpenERP.com</field>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing Campaign" version="7.0">
|
||||
<header>
|
||||
<button name="launch_segment_create_wizard" type="object"
|
||||
class="oe_highlight" string="Create a New Segment"/>
|
||||
<button name="launch_mass_mailing_create_wizard" type="object"
|
||||
class="oe_highlight" string="Create a New Mailing"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
|
@ -32,8 +32,8 @@
|
|||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<label for="segment_ids"/>
|
||||
<field name="segment_ids"/>
|
||||
<label for="mass_mailing_ids"/>
|
||||
<field name="mass_mailing_ids"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -46,29 +46,29 @@
|
|||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="segment_kanban_ids"/>
|
||||
<field name="mass_mailing_kanban_ids"/>
|
||||
<field name='sent'/>
|
||||
<field name='color'/>
|
||||
<templates>
|
||||
<t t-name="mass_mailing.segment">
|
||||
<t t-name="mass_mailing.mass_mailing">
|
||||
<div>
|
||||
<h4><t t-raw="segment.name"/></h4>
|
||||
<h4><t t-raw="mass_mailing.name"/></h4>
|
||||
</div>
|
||||
<div>
|
||||
<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
|
||||
</p>
|
||||
<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
|
||||
</p>
|
||||
<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
|
||||
</p>
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
|
@ -93,8 +93,8 @@
|
|||
<field name="replied" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
</div>
|
||||
<t t-foreach='record.segment_kanban_ids.value' t-as='segment'>
|
||||
<t t-call="mass_mailing.segment"/>
|
||||
<t t-foreach='record.mass_mailing_kanban_ids.value' t-as='mass_mailing'>
|
||||
<t t-call="mass_mailing.mass_mailing"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
|
@ -112,13 +112,13 @@
|
|||
<field name="view_mode">kanban,tree,form</field>
|
||||
</record>
|
||||
|
||||
<!-- MASS MAILING SEGMENTS !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_tree">
|
||||
<field name="name">mail.mass_mailing.segment.tree</field>
|
||||
<field name="model">mail.mass_mailing.segment</field>
|
||||
<!-- MASS MAILING !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
||||
<field name="name">mail.mass_mailing.tree</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mass Mailing Segments">
|
||||
<tree string="Mass Mailings">
|
||||
<field name="name"/>
|
||||
<field name="sent"/>
|
||||
<field name="delivered"/>
|
||||
|
@ -128,11 +128,11 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_form">
|
||||
<field name="name">mail.mass_mailing.segment.form</field>
|
||||
<field name="model">mail.mass_mailing.segment</field>
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||
<field name="name">mail.mass_mailing.form</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing Segment" version="7.0">
|
||||
<form string="Mass Mailing" version="7.0">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
|
@ -150,12 +150,12 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_form_readonly">
|
||||
<field name="name">mail.mass_mailing.segment.form</field>
|
||||
<field name="model">mail.mass_mailing.segment</field>
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form_readonly">
|
||||
<field name="name">mail.mass_mailing.form</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="priority">18</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing Segment" version="7.0">
|
||||
<form string="Mass Mailing" version="7.0">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
|
@ -173,9 +173,9 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_kanban">
|
||||
<field name="name">mail.mass_mailing.segment.kanban</field>
|
||||
<field name="model">mail.mass_mailing.segment</field>
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||
<field name="name">mail.mass_mailing.kanban</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name='color'/>
|
||||
|
@ -229,9 +229,9 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailing_segments" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailing Segments</field>
|
||||
<field name="res_model">mail.mass_mailing.segment</field>
|
||||
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
</record>
|
||||
|
@ -245,9 +245,9 @@
|
|||
<menuitem name="Campaigns" id="menu_email_campaigns"
|
||||
parent="mass_mailing_campaign" sequence="1"
|
||||
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"
|
||||
action="action_view_mass_mailing_segments"/>
|
||||
action="action_view_mass_mailings"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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_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_segment_system,mail.mass_mailing.segment.system,model_mail_mass_mailing_segment,base.group_system,1,1,1,1
|
||||
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,,1,1,1,0
|
||||
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(
|
||||
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
||||
),
|
||||
'mass_mailing_segment_id': fields.many2one(
|
||||
'mail.mass_mailing.segment', 'Mass mailing segment',
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass mailing',
|
||||
domain="[('mass_mailing_campaign_id', '=', mass_mailing_campaign_id)]",
|
||||
),
|
||||
}
|
||||
|
@ -44,18 +44,18 @@ class MailComposeMessage(osv.TransientModel):
|
|||
'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):
|
||||
if mass_mail_segment_id:
|
||||
segment = self.pool['mail.mass_mailing.segment'].browse(cr, uid, mass_mail_segment_id, context=context)
|
||||
if segment.mass_mailing_campaign_id.id == mass_mailing_campaign_id:
|
||||
def onchange_mass_mail_campaign_id(self, cr, uid, ids, mass_mailing_campaign_id, mass_mailing_id, context=None):
|
||||
if mass_mailing_id:
|
||||
mass_mailing = self.pool['mail.mass_mailing'].browse(cr, uid, mass_mailing_id, context=context)
|
||||
if mass_mailing.mass_mailing_campaign_id.id == mass_mailing_campaign_id:
|
||||
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):
|
||||
""" Override method that generated the mail content by adding the mass
|
||||
mailing campaign, when doing pure email mass mailing. """
|
||||
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:
|
||||
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
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
<div>
|
||||
<group>
|
||||
<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)],
|
||||
'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)],
|
||||
'required': [('composition_mode', '=', 'mass_mail'), ('use_mass_mailing_campaign', '=', True)]}"
|
||||
context="{'default_mass_mailing_campaign_id': mass_mailing_campaign_id,
|
||||
'default_template_id': template_id,
|
||||
'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>
|
||||
</div>
|
||||
</xpath>
|
||||
|
|
|
@ -24,11 +24,11 @@ from openerp.osv import osv, fields
|
|||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class MailMassMailingSegmentCreate(osv.TransientModel):
|
||||
"""Wizard to help creating mass mailing segments for a campaign. """
|
||||
class MailMassMailingCreate(osv.TransientModel):
|
||||
"""Wizard to help creating mass mailing waves for a campaign. """
|
||||
|
||||
_name = 'mail.mass_mailing.segment.create'
|
||||
_description = 'Mass mailing segment creation'
|
||||
_name = 'mail.mass_mailing.create'
|
||||
_description = 'Mass mailing creation'
|
||||
|
||||
_columns = {
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
|
@ -55,11 +55,11 @@ class MailMassMailingSegmentCreate(osv.TransientModel):
|
|||
'email.template', 'Template', required=True,
|
||||
domain="[('model_id', '=', model_id)]",
|
||||
),
|
||||
'segment_name': fields.char(
|
||||
'Segment name', required=True,
|
||||
'name': fields.char(
|
||||
'Name', required=True,
|
||||
),
|
||||
'mass_mailing_segment_id': fields.many2one(
|
||||
'mail.mass_mailing.segment', 'Mass Mailing Segment',
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass Mailing',
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -80,23 +80,23 @@ class MailMassMailingSegmentCreate(osv.TransientModel):
|
|||
domain = False
|
||||
return {'value': {'domain': domain}}
|
||||
|
||||
def create_segment(self, cr, uid, ids, context=None):
|
||||
""" Create a segment based on wizard data, and update the wizard """
|
||||
def create_mass_mailing(self, cr, uid, ids, context=None):
|
||||
""" Create a mass mailing based on wizard data, and update the wizard """
|
||||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
segment_values = {
|
||||
'name': wizard.segment_name,
|
||||
mass_mailing_values = {
|
||||
'name': wizard.name,
|
||||
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'domain': wizard.domain,
|
||||
'template_id': wizard.template_id.id,
|
||||
}
|
||||
segment_id = self.pool['mail.mass_mailing.segment'].create(cr, uid, segment_values, context=context)
|
||||
self.write(cr, uid, [wizard.id], {'mass_mailing_segment_id': segment_id}, 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_id': mass_mailing_id}, context=context)
|
||||
return True
|
||||
|
||||
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. """
|
||||
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)
|
||||
ctx = dict(context)
|
||||
|
@ -107,7 +107,7 @@ class MailMassMailingSegmentCreate(osv.TransientModel):
|
|||
'default_use_active_domain': True,
|
||||
'default_active_domain': wizard.domain,
|
||||
'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 {
|
||||
'name': _('Compose Email for Mass Mailing'),
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
<data>
|
||||
|
||||
<!-- Wizard form view -->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_segment_create_form">
|
||||
<field name="name">mail.mass_mailing.segment.create.form</field>
|
||||
<field name="model">mail.mass_mailing.segment.create</field>
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_create_form">
|
||||
<field name="name">mail.mass_mailing.create.form</field>
|
||||
<field name="model">mail.mass_mailing.create</field>
|
||||
<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>
|
||||
<field name="model_model" invisible="1"/>
|
||||
<field name="domain" invisible="1"/>
|
||||
<p class="oe_grey" colspan="2"
|
||||
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>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
|
||||
|
@ -43,23 +43,23 @@
|
|||
attrs="{'invisible': [('filter_id', '=', False)]}"/>
|
||||
|
||||
<p class="oe_grey" colspan="2"
|
||||
attrs="{'invisible': ['|', ('segment_name', '!=', False), ('template_id', '=', False)]}">
|
||||
Please choose the name of the campaign segment.
|
||||
attrs="{'invisible': ['|', ('name', '!=', False), ('template_id', '=', False)]}">
|
||||
Please choose the name of the mailing.
|
||||
</p>
|
||||
<field name="segment_name"
|
||||
<field name="name"
|
||||
attrs="{'invisible': [('template_id', '=', False)]}"/>
|
||||
|
||||
<button name="launch_composer" type="object"
|
||||
string="Create segment and launch email composer"
|
||||
attrs="{'invisible': [('segment_name', '=', False)]}"/>
|
||||
string="Create mailing and launch email composer"
|
||||
attrs="{'invisible': [('name', '=', False)]}"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_mail_mass_mailing_segment_create">
|
||||
<field name="name">Create Mass Mailing Segment</field>
|
||||
<field name="res_model">mail.mass_mailing.segment.create</field>
|
||||
<record model="ir.actions.act_window" id="action_mail_mass_mailing_create">
|
||||
<field name="name">Create Mass Mailing</field>
|
||||
<field name="res_model">mail.mass_mailing.create</field>
|
||||
<field name="src_model">mail.mass_mailing.campaign</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
|
|
Loading…
Reference in New Issue