[IMP] mass_mailing: finishing refactoring for the new func flow.

- cleaned reply-to management, now displaying an alert but simplifying the underlying
model about options available / not available for certain documents;
- cleaned a bit the form view;
- date -> create_date, and added a sent_date to distinguish the two;
- removed dead code;
- first draft of improving the chardomain widget

bzr revid: tde@openerp.com-20140415092503-q1hku1rh12ldy9i1
This commit is contained in:
Thibault Delavallée 2014-04-15 11:25:03 +02:00
parent 48d7a6fa6c
commit 6179d9b6a2
4 changed files with 92 additions and 124 deletions

View File

@ -227,9 +227,9 @@ class MassMailing(osv.Model):
_description = 'Mass Mailing' _description = 'Mass Mailing'
# number of periods for tracking mail_mail statistics # number of periods for tracking mail_mail statistics
_period_number = 6 _period_number = 6
_order = 'date DESC' _order = 'sent_date DESC'
def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None): def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, date_begin, context=None):
""" Generic method to generate data for bar chart values using SparklineBarWidget. """ Generic method to generate data for bar chart values using SparklineBarWidget.
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
@ -245,7 +245,7 @@ class MassMailing(osv.Model):
} }
] ]
""" """
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date() date_begin = date_begin.date()
section_result = [{'value': 0, section_result = [{'value': 0,
'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'), 'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
} for i in range(0, self._period_number)] } for i in range(0, self._period_number)]
@ -264,16 +264,17 @@ class MassMailing(osv.Model):
results for the next 6 days following the mass mailing date. """ results for the next 6 days following the mass mailing date. """
obj = self.pool['mail.mail.statistics'] obj = self.pool['mail.mail.statistics']
res = {} res = {}
for id in ids: for mailing in self.browse(cr, uid, ids, context=context):
res[id] = {} res[mailing.id] = {}
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT) date = mailing.sent_date if mailing.sent_date else mailing.create_date
date_begin = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1) date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)] domain = [('mass_mailing_id', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
res[id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened:day', context=context)) res[mailing.id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)] domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
res[id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied:day', context=context)) res[mailing.id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, 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):
@ -298,15 +299,6 @@ class MassMailing(osv.Model):
results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['sent'] or 1) results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['sent'] or 1)
return results return results
def _get_private_models(self, context=None):
return ['res.partner', 'mail.mass_mailing.contact']
def _get_auto_reply_to_available(self, cr, uid, ids, name, arg, context=None):
res = dict.fromkeys(ids, False)
for mailing in self.browse(cr, uid, ids, context=context):
res[mailing.id] = mailing.mailing_model not in self._get_private_models(context=context)
return res
def _get_mailing_model(self, cr, uid, context=None): def _get_mailing_model(self, cr, uid, context=None):
return [ return [
('res.partner', _('Customers')), ('res.partner', _('Customers')),
@ -319,7 +311,8 @@ class MassMailing(osv.Model):
_columns = { _columns = {
'name': fields.char('Subject', required=True), 'name': fields.char('Subject', required=True),
'email_from': fields.char('From', required=True), 'email_from': fields.char('From', required=True),
'date': fields.datetime('Date'), 'create_date': fields.datetime('Creation Date'),
'sent_date': fields.datetime('Sent Date'),
'body_html': fields.html('Body'), 'body_html': fields.html('Body'),
'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',
@ -334,15 +327,12 @@ class MassMailing(osv.Model):
type='integer', string='Color Index', type='integer', string='Color Index',
), ),
# mailing options # mailing options
# TODO: simplify these 4 fields 'reply_to_mode': fields.selection(
'reply_in_thread': fields.boolean('Reply in thread'), [('thread', 'In Document'), ('email', 'Specified Email Address')],
'reply_specified': fields.boolean('Specific Reply-To'), string='Reply-To Mode', required=True,
'auto_reply_to_available': fields.function(
_get_auto_reply_to_available,
type='boolean', string='Reply in thread available'
), ),
'reply_to': fields.char('Reply To'), 'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'),
# Target Emails # recipients
'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True), 'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
'mailing_domain': fields.char('Domain'), 'mailing_domain': fields.char('Domain'),
'contact_list_ids': fields.many2many( 'contact_list_ids': fields.many2many(
@ -412,13 +402,23 @@ class MassMailing(osv.Model):
_get_daily_statistics, string='Replied', _get_daily_statistics, string='Replied',
type='char', multi='_get_daily_statistics', type='char', multi='_get_daily_statistics',
oldname='replied_monthly', oldname='replied_monthly',
), )
} }
def default_get(self, cr, uid, fields, context=None):
res = super(MassMailing, self).default_get(cr, uid, fields, context=context)
if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model'):
if res['mailing_model'] in ['res.partner', 'mail.mass_mailing.contact']:
res['reply_to_mode'] = 'email'
else:
res['reply_to_mode'] = 'thread'
return res
_defaults = { _defaults = {
'state': 'draft', 'state': 'draft',
'date': fields.datetime.now, 'create_date': fields.datetime.now,
'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx), 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
'reply_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
'mailing_model': 'mail.mass_mailing.contact', 'mailing_model': 'mail.mass_mailing.contact',
'contact_ab_pc': 100, 'contact_ab_pc': 100,
} }
@ -468,42 +468,18 @@ class MassMailing(osv.Model):
# Views & Actions # Views & Actions
#------------------------------------------------------ #------------------------------------------------------
def on_change_model(self, cr, uid, ids, mailing_model, list_ids, context=None): def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None):
value = {} value = {}
if mailing_model is 'mail.mass_mailing.contact': if mailing_model == 'mail.mass_mailing.contact':
if list_ids and list_ids[0][0] == 6 and list_ids[0][2]: list_ids = map(lambda item: item if isinstance(item, (int, long)) else [lid for lid in item[2]], list_ids)
value['mailing_domain'] = "[('list_id', 'in', ["+','.join(map(str, list_ids[0][2]))+"])]" if list_ids:
value['mailing_domain'] = "[('list_id', 'in', %s)]" % list_ids
else: else:
value['mailing_domain'] = "[('list_id', '=', False)]" value['mailing_domain'] = "[('list_id', '=', False)]"
else: else:
value['mailing_domain'] = False value['mailing_domain'] = False
return {'value': value} return {'value': value}
def on_change_reply_specified(self, cr, uid, ids, reply_specified, reply_in_thread, context=None):
if reply_specified == reply_in_thread:
return {'value': {'reply_in_thread': not reply_specified}}
return {}
def on_change_reply_in_thread(self, cr, uid, ids, reply_specified, reply_in_thread, context=None):
if reply_in_thread == reply_specified:
return {'value': {'reply_specified': not reply_in_thread}}
return {}
def on_change_contact_list_ids(self, cr, uid, ids, mailing_model, contact_list_ids, context=None):
values = {}
list_ids = []
for command in contact_list_ids:
if command[0] == 6:
list_ids += command[2]
if list_ids:
values['contact_nbr'] = self.pool[mailing_model].search(
cr, uid, [('list_id', 'in', list_ids), ('opt_out', '!=', True)],
count=True, context=context
)
else:
values['contact_nbr'] = 0
return {'value': values}
def action_duplicate(self, cr, uid, ids, context=None): def action_duplicate(self, cr, uid, ids, context=None):
copy_id = None copy_id = None
for mid in ids: for mid in ids:
@ -530,23 +506,9 @@ class MassMailing(osv.Model):
'context': ctx, 'context': ctx,
} }
def action_see_recipients(self, cr, uid, ids, context=None):
mailing = self.browse(cr, uid, ids[0], context=context)
domain = self.pool['mail.mass_mailing.list'].get_global_domain(cr, uid, [c.id for c in mailing.contact_list_ids], context=context)[mailing.mailing_model]
return {
'name': _('See Recipients'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': mailing.mailing_model,
'target': 'new',
'domain': domain,
'context': context,
}
def action_edit_html(self, cr, uid, ids, context=None): def action_edit_html(self, cr, uid, ids, context=None):
# fixme: assert is not correct if not len(ids) == 1:
assert len(ids)==1, "One and only one ID allowed for this action" raise ValueError('One and only one ID allowed for this action')
mail = self.browse(cr, uid, ids[0], context=context) mail = self.browse(cr, uid, ids[0], context=context)
url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d&field_body=body_html&field_from=email_form&field_subject=name&template_model=%s' % (ids[0], mail.mailing_model) url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d&field_body=body_html&field_from=email_form&field_subject=name&template_model=%s' % (ids[0], mail.mailing_model)
return { return {
@ -622,5 +584,5 @@ class MassMailing(osv.Model):
composer_values['reply_to'] = mailing.reply_to composer_values['reply_to'] = mailing.reply_to
composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx) composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx)
self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx) self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx)
self.write(cr, uid, [mailing.id], {'date': fields.datetime.now(), 'state': 'done'}, context=context) self.write(cr, uid, [mailing.id], {'sent_date': fields.datetime.now(), 'state': 'done'}, context=context)
return True return True

View File

@ -24,7 +24,7 @@ class MassMailingnReport(osv.Model):
SELECT SELECT
id, id,
date_trunc('day', m.date) as mailing_date date_trunc('day', m.sent_date) as mailing_date
FROM FROM
mail_mass_mailing m mail_mass_mailing m
)""") )""")

View File

@ -16,17 +16,28 @@ openerp.mass_mailing = function (instance) {
init: function(field_manager, node) { init: function(field_manager, node) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
initialize_content: function () {
this.on("change:effective_readonly", this, this.render_value);
},
start: function() { start: function() {
var self=this; var self=this;
this._super.apply(this, arguments); this._super.apply(this, arguments);
$('button.select_records', this.$el).on('click', self.on_click); $('button.select_records', this.$el).on('click', self.on_click);
this.set_button(); this.set_button();
}, },
render_value: function () {
console.log('cacaprout', this.$('.select_records'));
var self = this;
this.$('.select_records').toggle(! this.get('effective_readonly'));
},
set_value: function(value_) {
var self = this;
this.set('value', value_ || false);
this.set_button();
},
set_button: function() { set_button: function() {
var self = this; var self = this;
// debugger
if (this.get('value')) { if (this.get('value')) {
// TODO: rpc to copute X
var domain = instance.web.pyeval.eval('domain', this.get('value')); var domain = instance.web.pyeval.eval('domain', this.get('value'));
var relation = this.getParent().fields.mailing_model.get('value')[0]; var relation = this.getParent().fields.mailing_model.get('value')[0];
var ds = new instance.web.DataSetStatic(self, relation, self.build_context()); var ds = new instance.web.DataSetStatic(self, relation, self.build_context());
@ -40,32 +51,35 @@ openerp.mass_mailing = function (instance) {
}; };
}, },
on_click: function(ev) { on_click: function(ev) {
console.log(this.get('effective_readonly'));
var self = this; var self = this;
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field); var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
this.pop = new instance.web.form.SelectCreatePopup(this); this.pop = new instance.web.form.SelectCreatePopup(this);
this.pop.select_element( this.pop.select_element(
model, {title: 'Select records...'}, model, {title: 'Select records...'},
[], this.build_context()); [], this.build_context());
this.pop.on("elements_selected", self, function() { this.pop.on("elements_selected", self, function(element_ids) {
var self2 = this; if (this.pop.$('input.oe_list_record_selector').prop('checked')) {
var search_data = this.pop.searchview.build_search_data() var search_data = this.pop.searchview.build_search_data();
instance.web.pyeval.eval_domains_and_contexts({ var domain_done = instance.web.pyeval.eval_domains_and_contexts({
domains: search_data.domains, domains: search_data.domains,
contexts: search_data.contexts, contexts: search_data.contexts,
group_by_seq: search_data.groupbys || [] group_by_seq: search_data.groupbys || []
}).then(function (results) { }).then(function (results) {
// if selected IDS change domain return results.domain;
var domain = self2.pop.dataset.domain.concat(results.domain || []); });
}
else {
var domain = ["id", "in", element_ids];
var domain_done = $.Deferred().resolve(domain);
}
$.when(domain_done).then(function (domain) {
var domain = self.pop.dataset.domain.concat(domain || []);
self.set_value(JSON.stringify(domain)) self.set_value(JSON.stringify(domain))
}); });
}); });
event.preventDefault(); event.preventDefault();
}, },
set_value: function(value_) {
var self = this;
this.set('value', value_ || false);
this.set_button();
},
}); });
instance.web.form.widgets = instance.web.form.widgets.extend( instance.web.form.widgets = instance.web.form.widgets.extend(

View File

@ -232,10 +232,9 @@
<label for="mailing_model" string="Recipients"/> <label for="mailing_model" string="Recipients"/>
<div> <div>
<field name="mailing_model" widget="radio" style="margin-bottom: 8px" <field name="mailing_model" widget="radio" style="margin-bottom: 8px"
on_change="on_change_model(mailing_model, contact_list_ids)"/> on_change="on_change_model_and_list(mailing_model, contact_list_ids)"/>
<field name="mailing_domain" widget="char_domain" <field name="mailing_domain" widget="char_domain"
attrs="{'invisible': [('mailing_model', '=', 'mail.mass_mailing.contact')]}"
placeholder="Select recipients" placeholder="Select recipients"
options="{'model_field': 'mailing_model'}"/> options="{'model_field': 'mailing_model'}"/>
@ -243,7 +242,7 @@
<label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/> <label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/>
<field name="contact_list_ids" widget="many2many_tags" <field name="contact_list_ids" widget="many2many_tags"
placeholder="Select mailing lists..." class="oe_inline" placeholder="Select mailing lists..." class="oe_inline"
on_change="on_change_model(mailing_model, contact_list_ids)"/> on_change="on_change_model_and_list(mailing_model, contact_list_ids)"/>
</div> </div>
</div> </div>
</group> </group>
@ -260,33 +259,26 @@
</page> </page>
<page string="Options"> <page string="Options">
<group> <group>
<group string="Campaign" groups="mass_mailing.group_mass_mailing_campaign"> <group string="Mailing">
<field name="mass_mailing_campaign_id"/>
<label for="contact_ab_pc"/>
<div>
<field name="contact_ab_pc" class="oe_inline"/> %
</div>
</group><group>
<field name="date"/>
</group><group>
<label for="reply_to"/> <label for="reply_to"/>
<div> <div>
<field name="auto_reply_to_available" invisible="1"/> <p class="alert alert-danger"
<field name="reply_in_thread" class="oe_inline" attrs="{'invisible': ['|', ('reply_to_mode', '!=', 'thread'), ('mailing_model', 'not in', ['mail.mass_mailing.contact', 'res.partner'])]}">
on_change="on_change_reply_in_thread(reply_specified, reply_in_thread, context)" This option is not available for the recipients you selected.
attrs="{'readonly': [('auto_reply_to_available', '=', False)]}"/> Please use a specific reply-to email address.
<span attrs="{'invisible': [('auto_reply_to_available', '=', False)]}"> </p>
Replies go into the original document <field name="reply_to_mode" widget="radio"/>
</span> <field name="reply_to" style="margin-left: 16px;"
<span class="oe_grey" attrs="{'invisible': [('auto_reply_to_available', '=', True)]}"> attrs="{'required': [('reply_to_mode', '=', 'email')]}"/>
Replies go into the original document (not available for those recipients) </div>
</span> <field name="create_date" readonly="1"/>
<br /> <field name="sent_date" readonly="1"/>
<field name="reply_specified" class="oe_inline" </group>
on_change="on_change_reply_specified(reply_specified, reply_in_thread, context)"/> Use a specific reply-to address <group string="Campaign">
<field name="reply_to" class="oe_inline" <field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/>
style="margin-left: 8px;" <label for="contact_ab_pc" groups="mass_mailing.group_mass_mailing_campaign"/>
attrs="{'required': [('reply_specified', '=', True)]}"/> <div>
<field name="contact_ab_pc" class="oe_inline"/> %
</div> </div>
</group> </group>
</group> </group>
@ -319,7 +311,7 @@
<div> <div>
<h3><field name="name"/></h3> <h3><field name="name"/></h3>
<h4 style="display: inline;"><field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/></h4> <h4 style="display: inline;"><field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/></h4>
<t t-if="record.mass_mailing_campaign_id.raw_value" groups="mass_mailing.group_mass_mailing_campaign"> - </t><field name="date"/> <t t-if="record.mass_mailing_campaign_id.raw_value" groups="mass_mailing.group_mass_mailing_campaign"> - </t><field name="sent_date"/>
</div> </div>
<div> <div>
<div style="display: inline-block"> <div style="display: inline-block">
@ -486,7 +478,7 @@
<field name="mass_mailing_ids" readonly="1" string="Related Mailing(s)"> <field name="mass_mailing_ids" readonly="1" string="Related Mailing(s)">
<tree> <tree>
<field name="name"/> <field name="name"/>
<field name="date"/> <field name="sent_date"/>
<field name="state"/> <field name="state"/>
<field name="delivered"/> <field name="delivered"/>
<field name="opened"/> <field name="opened"/>