From 851e3d8d87f200b0263b0da8e30e11f20ea03519 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Fri, 20 Dec 2013 11:49:35 +0530 Subject: [PATCH 001/186] [ADD] Added timesheet accounts menu in hr configuration bzr revid: cod@tinyerp.com-20131220061935-p0dqxrer1mp3sll4 --- addons/hr_timesheet/hr_timesheet_view.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index 0cd8a880a68..a997af54beb 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -125,7 +125,17 @@ + + + Timesheet Accounts + ir.actions.act_window + account.analytic.account + form + tree,form + + + Timesheet Activities ir.actions.act_window From f794a8c7af7bdf09bffe901f7ba47c0e25f08909 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Fri, 20 Dec 2013 16:40:22 +0530 Subject: [PATCH 002/186] [IMP]Improved code to set action bzr revid: cod@tinyerp.com-20131220111022-hnf8jfwr8h0h74hw --- addons/hr_timesheet/hr_timesheet_view.xml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index a997af54beb..e9f06db6919 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -125,16 +125,8 @@ - - - Timesheet Accounts - ir.actions.act_window - account.analytic.account - form - tree,form - - + Timesheet Activities From 03176d176bfaa740eef12a21f9092759653a83c1 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Mon, 23 Dec 2013 15:30:35 +0530 Subject: [PATCH 003/186] [IMP]Improved code to set use_timesheets default true and added action for menu Timesheets Accounts bzr revid: cod@tinyerp.com-20131223100035-us5sljbq2iyq9shs --- addons/hr_timesheet/hr_timesheet.py | 3 +++ addons/hr_timesheet/hr_timesheet_view.xml | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py index 5baf8ec262b..dabc5b5a125 100644 --- a/addons/hr_timesheet/hr_timesheet.py +++ b/addons/hr_timesheet/hr_timesheet.py @@ -206,6 +206,9 @@ class account_analytic_account(osv.osv): _columns = { 'use_timesheets': fields.boolean('Timesheets', help="Check this field if this project manages timesheets"), } + _defaults = { + 'use_timesheets': True + } def on_change_template(self, cr, uid, ids, template_id, context=None): res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context) diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index e9f06db6919..1a4ccf5953d 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -125,8 +125,17 @@ + + + Timesheet Accounts + ir.actions.act_window + account.analytic.account + form + tree,form + [('use_timesheets','=', True)] + - + Timesheet Activities From fbfe9936a190b6fe9fe3c0d3c011ed80f2b1a0d4 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Mon, 23 Dec 2013 17:01:38 +0530 Subject: [PATCH 004/186] [IMP]override name_create method of analytic account for not do not open contract page on quick create bzr revid: cod@tinyerp.com-20131223113138-pj0bgtf31lh5jcg8 --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index 9226787e908..defdb8a9767 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -250,7 +250,15 @@ class account_analytic_line(osv.osv): #if we don't get the dates from the timesheet, we return the default value from super() return res - +class account_analytic_account(osv.osv): + _inherit = "account.analytic.account" + + def name_create(self, cr, uid, name, context=None): + if not context.get('default_use_timesheets') == True: + return super(account_analytic_account, self).name_create(cr, uid, name, context=context) + rec_id = self.create(cr, uid, {self._rec_name: name}, context) + return self.name_get(cr, uid, [rec_id], context)[0] + class hr_timesheet_line(osv.osv): _inherit = "hr.analytic.timesheet" From 7db3c7aaac260d7670f8a958ebc80a8c09c43ba6 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Mon, 23 Dec 2013 17:26:19 +0530 Subject: [PATCH 005/186] [REM]Removed use_timesheets field from defaults bzr revid: cod@tinyerp.com-20131223115619-oimjlyrimgbaa7ie --- addons/hr_timesheet/hr_timesheet.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py index dabc5b5a125..5baf8ec262b 100644 --- a/addons/hr_timesheet/hr_timesheet.py +++ b/addons/hr_timesheet/hr_timesheet.py @@ -206,9 +206,6 @@ class account_analytic_account(osv.osv): _columns = { 'use_timesheets': fields.boolean('Timesheets', help="Check this field if this project manages timesheets"), } - _defaults = { - 'use_timesheets': True - } def on_change_template(self, cr, uid, ids, template_id, context=None): res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context) From bda62ace298f77395123107d62ab2c7f074d951c Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Mon, 23 Dec 2013 17:38:12 +0530 Subject: [PATCH 006/186] [IMP]Improved code bzr revid: cod@tinyerp.com-20131223120812-0u2w42i9neaqn76n --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index defdb8a9767..43defba113f 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -254,7 +254,7 @@ class account_analytic_account(osv.osv): _inherit = "account.analytic.account" def name_create(self, cr, uid, name, context=None): - if not context.get('default_use_timesheets') == True: + if context.get('default_use_timesheets') == False: return super(account_analytic_account, self).name_create(cr, uid, name, context=context) rec_id = self.create(cr, uid, {self._rec_name: name}, context) return self.name_get(cr, uid, [rec_id], context)[0] From d9ab20e1b91fa0f2c78557e672424930943f74ac Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Mon, 23 Dec 2013 17:54:33 +0530 Subject: [PATCH 007/186] [IMP] Improved code of name_create method bzr revid: cod@tinyerp.com-20131223122433-9svu8iyoruutdmjd --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index 43defba113f..93ef30e984b 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -254,7 +254,9 @@ class account_analytic_account(osv.osv): _inherit = "account.analytic.account" def name_create(self, cr, uid, name, context=None): - if context.get('default_use_timesheets') == False: + if context is None: + context = {} + if not context.get('default_use_timesheets', False): return super(account_analytic_account, self).name_create(cr, uid, name, context=context) rec_id = self.create(cr, uid, {self._rec_name: name}, context) return self.name_get(cr, uid, [rec_id], context)[0] From 9a3ce1a5ccaea171b2919084711756ddd753e821 Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Tue, 7 Jan 2014 11:41:57 +0530 Subject: [PATCH 008/186] [IMP]Improved code to open contract wizard from hr timesheet bzr revid: cod@tinyerp.com-20140107061157-lt1b0qvvms8a0qdi --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index 93ef30e984b..5bddf20ae69 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -256,7 +256,8 @@ class account_analytic_account(osv.osv): def name_create(self, cr, uid, name, context=None): if context is None: context = {} - if not context.get('default_use_timesheets', False): + group_template_required = self.pool['res.users'].has_group(cr, uid, 'account_analytic_analysis.group_template_required') + if not context.get('default_use_timesheets', False) or group_template_required: return super(account_analytic_account, self).name_create(cr, uid, name, context=context) rec_id = self.create(cr, uid, {self._rec_name: name}, context) return self.name_get(cr, uid, [rec_id], context)[0] From f777b31ac20eb1a7080ee24b55dfcfba6d38210f Mon Sep 17 00:00:00 2001 From: "Chirag Dodiya (OpenERP)" Date: Tue, 7 Jan 2014 16:29:59 +0530 Subject: [PATCH 009/186] [IMP]set default_use_timesheets true in analytic user function bzr revid: cod@tinyerp.com-20140107105959-kc7t6fav73xvegn8 --- addons/analytic_user_function/analytic_user_function_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/analytic_user_function/analytic_user_function_view.xml b/addons/analytic_user_function/analytic_user_function_view.xml index ca148175c5a..147e96f0be0 100644 --- a/addons/analytic_user_function/analytic_user_function_view.xml +++ b/addons/analytic_user_function/analytic_user_function_view.xml @@ -66,7 +66,7 @@ - + @@ -79,7 +79,7 @@ - + From 1ac8e7cdfddbf8ac6b7c4b843582392705f10a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 20 Feb 2014 16:29:45 +0100 Subject: [PATCH 010/186] [IMP] mail, email_template: mass mailing feature cleaning - now differentiates mass mailing and mass post. - mass mailing is a true mass mailing - using same_thread log a copy in the document, without using message_post (using notification field, + model and res_id) - improved form view of composer, adding a filed explaining a bit the various recipients - removed unnecessary fields coming from the template (partner_to, ...) because they are confusing -> composer should be easier to understand and use - removed some unnecessary code - removed double body computation when using templates (one for tmeplate, then the wizard -> not necessary) This commit will be followed by other to try to improve the mass mailing and mass post. bzr revid: tde@openerp.com-20140220152945-ash0hfkzevzamihq --- addons/email_template/email_template.py | 13 +- .../wizard/mail_compose_message.py | 38 ++++-- .../wizard/mail_compose_message_view.xml | 16 --- addons/mail/mail_mail_view.xml | 1 + addons/mail/static/src/js/mail.js | 2 +- addons/mail/wizard/mail_compose_message.py | 116 +++++++++--------- .../mail/wizard/mail_compose_message_view.xml | 42 ++++--- 7 files changed, 120 insertions(+), 108 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index fcfe869da90..5167bdc3d79 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -356,18 +356,19 @@ class email_template(osv.osv): results = dict() for template, template_res_ids in templates_to_res_ids.iteritems(): # generate fields value for all res_ids linked to the current template - for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']: + for field in fields: generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context) for res_id, field_value in generated_field_values.iteritems(): results.setdefault(res_id, dict())[field] = field_value # update values for all res_ids for res_id in template_res_ids: values = results[res_id] - if template.user_signature: - signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature - values['body_html'] = tools.append_content_to_html(values['body_html'], signature) - if values['body_html']: - values['body'] = tools.html_sanitize(values['body_html']) + if 'body_html' in fields: + if template.user_signature: + signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature + values['body_html'] = tools.append_content_to_html(values['body_html'], signature) + if values['body_html']: + values['body'] = tools.html_sanitize(values['body_html']) values.update( mail_server_id=template.mail_server_id.id or False, auto_delete=template.auto_delete, diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index beede1122dc..959fea94420 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -55,14 +55,23 @@ class mail_compose_message(osv.TransientModel): ) return res + def get_recipients_data(self, cr, uid, values, context=None): + if values['composition_mode'] != 'mass_mail': + return super(mail_compose_message, self).get_recipients_data(cr, uid, values, context=context) + model, res_id, template_id = values['model'], values['res_id'], values.get('template_id') + active_ids = context.get('active_ids', list()) + if not active_ids or not template_id: + return False + template = self.pool['email.template'].browse(cr, uid, template_id, context=context) + partner_to = self.render_template_batch(cr, uid, template.partner_to, model, active_ids[:3], context=context) + partner_ids = [int(data) for key, data in partner_to.iteritems() if data] + rec_names = [rec_name[1] for rec_name in self.pool['res.partner'].name_get(cr, SUPERUSER_ID, partner_ids, context=context)] + recipients = ', '.join(rec_names) + recipients += ' and %d more.' % (len(active_ids) - 3) if len(active_ids) > 3 else '.' + return recipients + _columns = { 'template_id': fields.many2one('email.template', 'Use template', select=True), - 'partner_to': fields.char('To (Partner IDs)', - help="Comma-separated list of recipient partners ids (placeholders may be used here)"), - 'email_to': fields.char('To (Emails)', - help="Comma-separated recipient addresses (placeholders may be used here)",), - 'email_cc': fields.char('Cc (Emails)', - help="Carbon copy recipients (placeholders may be used here)"), } def send_mail(self, cr, uid, ids, context=None): @@ -92,7 +101,7 @@ class mail_compose_message(osv.TransientModel): """ - mass_mailing: we cannot render, so return the template values - normal mode: return rendered values """ if template_id and composition_mode == 'mass_mail': - fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id'] + fields = ['subject', 'body_html', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context) values = dict((field, template_values[field]) for field in fields if template_values.get(field)) elif template_id: @@ -162,16 +171,18 @@ class mail_compose_message(osv.TransientModel): partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context) return partner_ids - def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None): + def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, fields=None, context=None): """ Call email_template.generate_email(), get fields relevant for mail.compose.message, transform email_cc and email_to into partner_ids """ # filter template values - fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id'] + if fields is None: + fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id'] + returned_fields = fields + ['attachments'] values = dict.fromkeys(res_ids, False) - template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context) + template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=context) for res_id in res_ids: - res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field)) + res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field)) res_id_values['body'] = res_id_values.pop('body_html', '') # transform email_to, email_cc into partner_ids @@ -189,7 +200,10 @@ class mail_compose_message(osv.TransientModel): """ Override to handle templates. """ # generate template-based values if wizard.template_id: - template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context) + template_values = self.generate_email_for_composer_batch( + cr, uid, wizard.template_id.id, res_ids, + fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'], + context=context) else: template_values = dict.fromkeys(res_ids, dict()) # generate composer values diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml index 44145114fff..2f755a625d6 100644 --- a/addons/email_template/wizard/mail_compose_message_view.xml +++ b/addons/email_template/wizard/mail_compose_message_view.xml @@ -7,22 +7,6 @@ mail.compose.message - -
Use template diff --git a/addons/mail/mail_mail_view.xml b/addons/mail/mail_mail_view.xml index cf3dd1e8233..e259d28a2b7 100644 --- a/addons/mail/mail_mail_view.xml +++ b/addons/mail/mail_mail_view.xml @@ -36,6 +36,7 @@
+ diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 58d44bab50e..3687961bb6a 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -507,7 +507,7 @@ openerp.mail = function (session) { } $.when(recipient_done).done(function (partner_ids) { var context = { - 'default_composition_mode': default_composition_mode, + // 'default_composition_mode': default_composition_mode, 'default_parent_id': self.id, 'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''), 'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}), diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 74aea3dfc01..c8326ed48cd 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -68,28 +68,25 @@ class mail_compose_message(osv.TransientModel): if context is None: context = {} result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context) - # get some important values from context - composition_mode = context.get('default_composition_mode', context.get('mail.compose.message.mode')) - model = context.get('default_model', context.get('active_model')) - res_id = context.get('default_res_id', context.get('active_id')) - message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id'))) - active_ids = context.get('active_ids') + + # v6.1 compatibility mode + result['composition_mode'] = result.get('composition_mode', context.get('mail.compose.message.mode')) + result['model'] = result.get('model', context.get('active_model')) + result['res_id'] = result.get('res_id', context.get('active_id')) + result['parent_id'] = result.get('parent_id', context.get('message_id')) + + # default values according to composition mode - NOTE: reply is deprecated, fall back on comment + if result['composition_mode'] == 'reply': + result['composition_mode'] = 'comment' + vals = {} if 'active_domain' in context: # not context.get() because we want to keep global [] domains - result['use_active_domain'] = True - result['active_domain'] = '%s' % context.get('active_domain') - elif not result.get('active_domain'): - result['active_domain'] = '' - # get default values according to the composition mode - if composition_mode == 'reply': - vals = self.get_message_data(cr, uid, message_id, context=context) - elif composition_mode == 'comment' and model and res_id: - vals = self.get_record_data(cr, uid, model, res_id, context=context) - elif composition_mode == 'mass_mail' and model and active_ids: - vals = {'model': model, 'res_id': res_id} - else: - vals = {'model': model, 'res_id': res_id} - if composition_mode: - vals['composition_mode'] = composition_mode + vals['use_active_domain'] = True + vals['active_domain'] = '%s' % context.get('active_domain') + if result.get('parent_id'): + vals.update(self.get_message_data(cr, uid, result.get('parent_id'), context=context)) + if result['composition_mode'] == 'comment' and result['model'] and result['res_id']: + vals.update(self.get_record_data(cr, uid, result['model'], result['res_id'], context=context)) + result['recipients_data'] = self.get_recipients_data(cr, uid, result, context=context) for field in vals: if field in fields: @@ -102,13 +99,15 @@ class mail_compose_message(osv.TransientModel): # but when creating the mail.message to create the mail.compose.message # access rights issues may rise # We therefore directly change the model and res_id - if result.get('model') == 'res.users' and result.get('res_id') == uid: + if result['model'] == 'res.users' and result['res_id'] == uid: result['model'] = 'res.partner' result['res_id'] = self.pool.get('res.users').browse(cr, uid, uid).partner_id.id return result def _get_composition_mode_selection(self, cr, uid, context=None): - return [('comment', 'Comment a document'), ('reply', 'Reply to a message'), ('mass_mail', 'Mass mailing')] + return [('comment', 'Post on a document'), + ('mass_mail', 'Email Mass Mailing'), + ('mass_post', 'Post on Multiple Documents')] _columns = { 'composition_mode': fields.selection( @@ -116,11 +115,11 @@ class mail_compose_message(osv.TransientModel): string='Composition mode'), 'partner_ids': fields.many2many('res.partner', 'mail_compose_message_res_partner_rel', - 'wizard_id', 'partner_id', 'Additional contacts'), + 'wizard_id', 'partner_id', 'Additional Contacts'), + 'recipients_data': fields.text(string='Recipients Data', + help='Helper field used in mass mailing to display a sample of recipients'), 'use_active_domain': fields.boolean('Use active domain'), 'active_domain': fields.char('Active domain', readonly=True), - 'post': fields.boolean('Post a copy in the document', - help='Post a copy of the message on the document communication history.'), 'notify': fields.boolean('Notify followers', help='Notify followers of the document'), 'same_thread': fields.boolean('Replies in the document', @@ -136,8 +135,7 @@ class mail_compose_message(osv.TransientModel): 'body': lambda self, cr, uid, ctx={}: '', 'subject': lambda self, cr, uid, ctx={}: False, 'partner_ids': lambda self, cr, uid, ctx={}: [], - 'post': False, - 'notify': False, + 'notify': True, 'same_thread': True, } @@ -169,6 +167,21 @@ class mail_compose_message(osv.TransientModel): not want that feature in the wizard. """ return + def get_recipients_data(self, cr, uid, values, context=None): + """ Returns a string explaining the targetted recipients, to ease the use + of the wizard. """ + composition_mode, model, res_id = values['composition_mode'], values['model'], values['res_id'] + if composition_mode == 'comment' and model and res_id: + doc_name = self.pool[model].name_get(cr, uid, [res_id], context=context) + return doc_name and 'Followers of %s' % doc_name[0][1] or False + elif composition_mode == 'mass_post' and model: + active_ids = context.get('active_ids', list()) + if not active_ids: + return False + name_gets = [rec_name[1] for rec_name in self.pool[model].name_get(cr, uid, active_ids[:3], context=context)] + return 'Followers of selected documents (' + ', '.join(name_gets) + len(active_ids) > 3 and ', ...' or '' + ')' + return False + def get_record_data(self, cr, uid, model, res_id, context=None): """ Returns a defaults-like dict with initial values for the composition wizard when sending an email related to the document record @@ -179,17 +192,10 @@ class mail_compose_message(osv.TransientModel): :param int res_id: id of the document record this mail is related to """ doc_name_get = self.pool[model].name_get(cr, uid, [res_id], context=context) - record_name = False - if doc_name_get: - record_name = doc_name_get[0][1] - values = { - 'model': model, - 'res_id': res_id, - 'record_name': record_name, + return { + 'record_name': doc_name_get and doc_name_get[0][1] or False, + 'subject': doc_name_get and 'Re: %s' % doc_name_get[0][1] or False, } - if record_name: - values['subject'] = 'Re: %s' % record_name - return values def get_message_data(self, cr, uid, message_id, context=None): """ Returns a defaults-like dict with initial values for the composition @@ -208,23 +214,20 @@ class mail_compose_message(osv.TransientModel): # create subject re_prefix = _('Re:') reply_subject = tools.ustr(message_data.subject or message_data.record_name or '') - if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject: + if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)): reply_subject = "%s %s" % (re_prefix, reply_subject) + # get partner_ids from original message partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else [] partner_ids += context.get('default_partner_ids', []) - if context.get('is_private',False) and message_data.author_id : #check message is private then add author also in partner list. + if context.get('is_private') and message_data.author_id: # check message is private then add author also in partner list. partner_ids += [message_data.author_id.id] # update the result - result = { + return { 'record_name': message_data.record_name, - 'model': message_data.model, - 'res_id': message_data.res_id, - 'parent_id': message_data.id, 'subject': reply_subject, 'partner_ids': partner_ids, } - return result #------------------------------------------------------ # Wizard validation and send @@ -244,16 +247,16 @@ class mail_compose_message(osv.TransientModel): is_log = context.get('mail_compose_log', False) for wizard in self.browse(cr, uid, ids, context=context): - mass_mail_mode = wizard.composition_mode == 'mass_mail' + mass_mode = wizard.composition_mode in ('mass_mail', 'mass_post') active_model_pool = self.pool[wizard.model if wizard.model else 'mail.thread'] if not hasattr(active_model_pool, 'message_post'): context['thread_model'] = wizard.model active_model_pool = self.pool['mail.thread'] # wizard works in batch mode: [res_id] or active_ids or active_domain - if mass_mail_mode and wizard.use_active_domain and wizard.model: + if mass_mode and wizard.use_active_domain and wizard.model: res_ids = self.pool[wizard.model].search(cr, uid, eval(wizard.active_domain), context=context) - elif mass_mail_mode and wizard.model and active_ids: + elif mass_mode and wizard.model and active_ids: res_ids = active_ids else: res_ids = [wizard.res_id] @@ -261,7 +264,9 @@ class mail_compose_message(osv.TransientModel): all_mail_values = self.get_mail_values(cr, uid, wizard, res_ids, context=context) for res_id, mail_values in all_mail_values.iteritems(): - if mass_mail_mode and not wizard.post: + if wizard.composition_mode == 'mass_mail': + if wizard.same_thread: # same thread: keep a copy of the message in the chatter to enable the reply redirection + mail_values.update(notification=True, model=wizard.model, res_id=res_id) m2m_attachment_ids = self.pool['mail.thread']._message_preprocess_attachments( cr, uid, mail_values.pop('attachments', []), mail_values.pop('attachment_ids', []), @@ -271,11 +276,9 @@ class mail_compose_message(osv.TransientModel): self.pool.get('mail.mail').create(cr, uid, mail_values, context=context) else: subtype = 'mail.mt_comment' - if is_log: # log a note: subtype is False + if is_log or (wizard.composition_mode == 'mass_post' and not wizard.notify): # log a note: subtype is False subtype = False - elif mass_mail_mode: # mass mail: is a log pushed to recipients unless specified, author not added - if not wizard.notify: - subtype = False + if wizard.composition_mode == 'mass_post': context = dict(context, mail_notify_force_send=False, # do not send emails directly but use the queue instead mail_create_nosubscribe=True) # add context key to avoid subscribing the author @@ -326,8 +329,7 @@ class mail_compose_message(osv.TransientModel): else: email_dict.pop('reply_to', None) mail_values.update(email_dict) - # mass mailing without post: mail_mail values - if mass_mail_mode and not wizard.post: + # mail_mail values if 'mail_auto_delete' in context: mail_values['auto_delete'] = context.get('mail_auto_delete') mail_values['body_html'] = mail_values.get('body', '') @@ -335,6 +337,10 @@ class mail_compose_message(osv.TransientModel): results[res_id] = mail_values return results + #------------------------------------------------------ + # Template rendering + #------------------------------------------------------ + def render_message_batch(self, cr, uid, wizard, res_ids, context=None): """Generate template-based values of wizard, for the document records given by res_ids. This method is meant to be inherited by email_template that diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 948d9b3832c..4c539e426d9 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -28,29 +28,35 @@ - - + + + Imported Contacts + mail.mass_mailing.contact + + + Customers + res.partner + + [('customer', '=', True)] + + + + + Aristide Antario + aa@example.com + + + + Beverly Bridge + bb@example.com + + + + Carol Cartridge + cc@example.com + + + + + Partners Newsletter @@ -25,12 +162,14 @@ First Newsletter + done - + Second Newsletter + test @@ -63,20 +202,5 @@ - - - 1111005@OpenERP.com - - - - - 1111006@OpenERP.com - - - - - 1111007@OpenERP.com - - diff --git a/addons/mass_mailing/mass_mailing_view.xml b/addons/mass_mailing/mass_mailing_view.xml index e8848fba44f..b9549ebd176 100644 --- a/addons/mass_mailing/mass_mailing_view.xml +++ b/addons/mass_mailing/mass_mailing_view.xml @@ -2,6 +2,147 @@ + + + + + + + + mail.mass_mailing.contact.search + mail.mass_mailing.contact + + + + + + + + + + + + + + + + mail.mass_mailing.contact.tree + mail.mass_mailing.contact + 10 + + + + + + + + + + + + mail.mass_mailing.contact.form + mail.mass_mailing.contact + +
+ + + + + + + + +
+
+
+ + + Mass Mailing Contacts + mail.mass_mailing.contact + form + tree,form + {'search_default_not_opt_out': 1} + + + + + + + mail.mass_mailing.list.search + mail.mass_mailing.list + + + + + + + + + + mail.mass_mailing.list.tree + mail.mass_mailing.list + 10 + + + + + + + + + + + mail.mass_mailing.list.form + mail.mass_mailing.list + +
+
+
+ + + + + +
+
+
+ + + Contact Lists + mail.mass_mailing.list + form + tree,form + + + + mail.mass_mailing.search @@ -13,6 +154,7 @@ @@ -32,7 +174,8 @@ - + @@ -43,19 +186,51 @@ mail.mass_mailing
+
+
- + + + - - - +

Here be some graphs

- + + +
@@ -76,7 +251,7 @@ mail.mass_mailing.kanban mail.mass_mailing - + @@ -87,8 +262,9 @@

- Sent:
- Campaign: + Sent:
+ Campaign: +

@@ -112,11 +288,11 @@

Opened


- +

Replied


- +
@@ -147,6 +323,10 @@ + + mail.mass_mailing.campaign.search @@ -154,10 +334,13 @@ + - + @@ -171,6 +354,8 @@ + + @@ -182,14 +367,16 @@
+ - + - + + + + + + + + + + + + +
  • From 1a086bca821df7fa95fb00d6a6474061cd444926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 18 Mar 2014 11:25:01 +0100 Subject: [PATCH 037/186] [IMP] sidebar in list view: context should be the one of the dataset, allowing context propagation, not void default context. bzr revid: tde@openerp.com-20140318102501-pi6qcaz1zbu71tsh --- addons/web/static/src/js/views.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 829a6251404..e24de77ea4f 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1531,7 +1531,7 @@ instance.web.View = instance.web.Widget.extend({ new instance.web.DataExport(this, this.dataset).open(); }, sidebar_eval_context: function () { - return $.when({}); + return $.when(new instance.web.CompoundContext(this.dataset.get_context())); }, /** * Asks the view to reload itself, if the reloading is asynchronous should From aef967eefc9d0209d6793d7f9bb5b48b632e9d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 18 Mar 2014 14:04:28 +0100 Subject: [PATCH 038/186] [IMP] mass_mailing : - cleaned management of domains in contact lists, now having a method to compute the global domain of several lists; - added action to see the contacts of a mailing + contact number correctly counter bzr revid: tde@openerp.com-20140318130428-8f4kp0ve2lf1rafe --- addons/mass_mailing/models/mass_mailing.py | 124 ++++++++++++++------- addons/mass_mailing/views/mass_mailing.xml | 19 ++-- 2 files changed, 95 insertions(+), 48 deletions(-) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index d46880c792b..7abdc6785c6 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -65,20 +65,28 @@ class MassMailingList(osv.Model): def default_get(self, cr, uid, fields, context=None): """Override default_get to handle active_domain coming from the list view. """ res = super(MassMailingList, self).default_get(cr, uid, fields, context=context) - if 'domain' in fields and 'active_domain' in context: - res['domain'] = '%s' % context['active_domain'] + if 'domain' in fields: res['model'] = context.get('active_model', 'res.partner') + if 'active_domain' in context: + res['domain'] = '%s' % context['active_domain'] + elif 'active_ids' in context: + res['domain'] = '%s' % [('id', 'in', context['active_ids'])] + else: + res['domain'] = '%s' % [('id', 'in', context.get('active_id', 0))] return res def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): """Compute the number of contacts linked to the mailing list. """ results = dict.fromkeys(ids, 0) for contact_list in self.browse(cr, uid, ids, context=context): - if contact_list.model == 'mail.mass_mailing.contact': - results[contact_list.id] = len(contact_list.contact_ids) - else: - domain = self.compute_domain(cr, uid, [contact_list.id], context=context) - results[contact_list.id] = self.pool[contact_list.model].search(cr, uid, domain, count=True, context=context) + print contact_list, contact_list.model, contact_list.domain + domain = self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id] + print domain + results[contact_list.id] = self.pool[contact_list.model].search( + cr, uid, + domain, + count=True, context=context + ) return results def _get_model_list(self, cr, uid, context=None): @@ -105,7 +113,7 @@ class MassMailingList(osv.Model): ), 'filter_id': fields.many2one( 'ir.filters', string='Custom Filter', - # domain="[('model_id', '=', model_id)]", + domain="[('model_id.model', '=', model)]", ), 'domain': fields.text('Domain'), } @@ -144,26 +152,56 @@ class MassMailingList(osv.Model): 'domain': contact_list.domain, } - def compute_domain(self, cr, uid, ids, context=None): - domains = [] + def action_add_to_mailing(self, cr, uid, ids, context=None): + mass_mailing_id = context.get('default_mass_mailing_id') + if not mass_mailing_id: + return False + self.pool['mail.mass_mailing'].write(cr, uid, [mass_mailing_id], {'contact_list_ids': [(4, list_id) for list_id in ids]}, context=context) + return { + 'name': _('New Mass Mailing'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'mail.mass_mailing', + 'res_id': mass_mailing_id, + 'context': context, + } + + def _get_domain(self, cr, uid, ids, context=None): + domains = {} for contact_list in self.browse(cr, uid, ids, context=context): - domain = eval(contact_list.domain) + if contact_list.model == 'mail.mass_mailing.contact': + domain = [('list_id', '=', contact_list.id)] + elif not contact_list.domain: # domain is a string like False or None -> void list + domain = [('id', '=', '0')] + else: + domain = eval(contact_list.domain) + # force the addition of opt_out filter if domain: domain = ['&', ('opt_out', '=', False)] + domain else: domain = [('opt_out', '=', False)] + domains[contact_list.id] = domain + return domains + + def get_global_domain(self, cr, uid, ids, context=None): + model_to_domains = dict((mailing_model[0], list()) + for mailing_model in self.pool['mail.mass_mailing']._get_mailing_model(cr, uid, context=context)) + for contact_list in self.browse(cr, uid, ids, context=context): + domain = self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id] if domain is not False: - domains.append(domain) - if domains: - final_domain = ['|'] * (len(domains) - 1) + [leaf for dom in domains for leaf in dom] - else: - final_domain = domains - return final_domain + model_to_domains[contact_list.model].append(domain) + for model, domains in model_to_domains.iteritems(): + if domains: + final_domain = ['|'] * (len(domains) - 1) + [leaf for dom in domains for leaf in dom] + else: + final_domain = [('id', '=', '0')] + model_to_domains[model] = final_domain + return model_to_domains class MassMailingCampaign(osv.Model): - """Model of mass mailing campaigns. - """ + """Model of mass mailing campaigns. """ _name = "mail.mass_mailing.campaign" _description = 'Mass Mailing Campaign' # number of embedded mailings in kanban view @@ -358,6 +396,16 @@ class MassMailing(osv.Model): results[mid]['delivered'] = results[mid]['sent'] - results[mid]['bounced'] return results + def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): + res = dict.fromkeys(ids, 0) + for mailing in self.browse(cr, uid, ids, context=context): + res[mailing.id] = self.pool[mailing.mailing_model].search( + cr, uid, + 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], + count=True, context=context + ) + return res + def _get_mailing_model(self, cr, uid, context=None): return [ ('res.partner', 'Customers'), @@ -405,7 +453,7 @@ class MassMailing(osv.Model): string='Mailing Lists', domain="[('model', '=', mailing_model)]", ), - 'contact_nbr': fields.integer('Contact Number'), + 'contact_nbr': fields.function(_get_contact_nbr, type='integer', string='Contact Number'), # statistics data 'statistics_ids': fields.one2many( 'mail.mail.statistics', 'mass_mailing_id', @@ -506,10 +554,10 @@ class MassMailing(osv.Model): values['body_html'] = False return {'value': values} - def select_customers(self, cr, uid, ids, context=None): + def action_new_list(self, cr, uid, ids, context=None): wizard = self.browse(cr, uid, ids[0], context=context) - aid = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') - ctx = dict(context, view_manager_highlight=[aid]) + action_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') + ctx = dict(context, view_manager_highlight=[action_id], default_mass_mailing_id=ids[0]) return { 'name': _('Choose Recipients'), 'type': 'ir.actions.act_window', @@ -519,28 +567,28 @@ class MassMailing(osv.Model): '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': _('Mailing Recipients'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': mailing.mailing_model, + 'domain': domain, + 'context': context, + } + #------------------------------------------------------ # Email Sending #------------------------------------------------------ - def send_get_recipients(self, cr, uid, ids, context=None): - recipients = dict.fromkeys(ids, list()) - for mailing in self.browse(cr, uid, ids, context=context): - list_ids = [contact_list.id for contact_list in mailing.contact_list_ids] - # contact-based list: aggregate all contacts - if mailing.mailing_model == 'mail.mass_mailing.list': - res_ids = [contact.id for contact in contact_list.contact_ids for contact_list in mailing.contact_list_ids] - else: - domain = self.pool['mail.mass_mailing.list'].compute_domain(cr, uid, list_ids, context=context) - res_ids = self.pool[contact_list.model].search(cr, uid, domain, context=context) - recipients[mailing.id] = res_ids - return recipients - def send_mail(self, cr, uid, ids, context=None): Mail = self.pool['mail.mail'] - recipients = self.send_get_recipients(cr, uid, ids, context=context) for mailing in self.browse(cr, uid, ids, context=context): - res_ids = recipients.get(mailing.id, list()) + domain = self.pool['mail.mass_mailing.list'].get_global_domain(cr, uid, [l.id for l in mailing.contact_list_ids], context=context)[mailing.mailing_model] + res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context) all_mail_values = self.pool['mail.compose.message'].generate_email_for_composer_batch( cr, uid, mailing.template_id.id, res_ids, context=context, diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index ca95b151f47..110439e3e55 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -103,15 +103,17 @@
    -
@@ -520,10 +523,8 @@ parent="base.menu_email" sequence="50" action="action_view_mail_mail_statistics"/> - - - + context="{'default_mass_mailing_id': context.get('default_mass_mailing_id')}"/> From feadcf402c1a3b995fe14727d62389c99a051846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 18 Mar 2014 14:20:17 +0100 Subject: [PATCH 039/186] [FIX] mass_mailing: fixed wrong method called in composer bzr revid: tde@openerp.com-20140318132017-lx3c4yd9rbelc1an --- addons/mass_mailing/wizard/mail_compose_message.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py index d2d9ee1156d..792f1c346e3 100644 --- a/addons/mass_mailing/wizard/mail_compose_message.py +++ b/addons/mass_mailing/wizard/mail_compose_message.py @@ -40,9 +40,8 @@ class MailComposeMessage(osv.TransientModel): email mass mailing. """ res = super(MailComposeMessage, self).get_mail_values(cr, uid, wizard, res_ids, context=context) # use only for allowed models in mass mailing - if wizard.model not in [t[0] for t in self.pool['mail.mass_mailing']._get_mailing_type()]: - return res - if wizard.composition_mode == 'mass_mail' and wizard.mass_mailing_name: + if wizard.composition_mode == 'mass_mail' and wizard.mass_mailing_name and \ + wizard.model in [item[0] for item in self.pool['mail.mass_mailing']._get_mailing_model()]: list_id = self.pool['mail.mass_mailing.list'].create( cr, uid, { 'name': wizard.mass_mailing_name, From 4583c1829ba36393b0ded29938779a67f2f059d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 18 Mar 2014 17:39:18 +0100 Subject: [PATCH 040/186] [DEMO] mass_mailing: updated demo data bzr revid: tde@openerp.com-20140318163918-x1jg382boqdci0l3 --- addons/mass_mailing/data/mass_mailing_demo.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml index 071877a8af8..f5695cd1080 100644 --- a/addons/mass_mailing/data/mass_mailing_demo.xml +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -155,10 +155,16 @@ - - Partners Newsletter + + Marketing + + Newsletter + design + + + First Newsletter @@ -166,6 +172,7 @@ + Second Newsletter From a581d864a2db67f2490570cc7187af4cebd8adc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 18 Mar 2014 17:41:17 +0100 Subject: [PATCH 041/186] [IMP] mass_mailing: improved test send / send to all - now workign on various models, taking the correct recipient (email, partner_id for partner or lead) - improved test send: use email_to, now a many2many on contact model to ease the use of templates - added action to create a mailing list from leads - moved a res.partner dedicated action to its own file - added an actoin to create a mailing list from contacts - fixed mail_mail post_process: use mail_sent variable as mail.state is not available bzr revid: tde@openerp.com-20140318164117-f2aou25u633j8m57 --- addons/mail/mail_mail.py | 4 +- addons/mass_mailing/__openerp__.py | 1 + addons/mass_mailing/models/mail_mail.py | 6 +- addons/mass_mailing/models/mass_mailing.py | 93 ++++++++++++------- addons/mass_mailing/views/mass_mailing.xml | 36 +++---- addons/mass_mailing/views/res_partner.xml | 17 ++++ addons/mass_mailing_crm/__openerp__.py | 4 +- .../mass_mailing_crm/models/mass_mailing.py | 17 ++++ addons/mass_mailing_crm/views/crm_lead.xml | 17 ++++ 9 files changed, 141 insertions(+), 54 deletions(-) create mode 100644 addons/mass_mailing/views/res_partner.xml create mode 100644 addons/mass_mailing_crm/views/crm_lead.xml diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 1554caf3d3c..87f8c9ce7ef 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -126,7 +126,7 @@ class mail_mail(osv.Model): _logger.exception("Failed processing mail queue") return res - def _postprocess_sent_message(self, cr, uid, mail, context=None): + def _postprocess_sent_message(self, cr, uid, mail, context=None, mail_sent=True): """Perform any post-processing necessary after sending ``mail`` successfully, including deleting it completely along with its attachment if the ``auto_delete`` flag of the mail was set. @@ -295,7 +295,7 @@ class mail_mail(osv.Model): # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1 if mail_sent: - self._postprocess_sent_message(cr, uid, mail, context=context) + self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent) except Exception as e: _logger.exception('failed sending mail.mail %s', mail.id) mail.write({'state': 'exception'}) diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py index 2409fa6a09b..411170c7e0e 100644 --- a/addons/mass_mailing/__openerp__.py +++ b/addons/mass_mailing/__openerp__.py @@ -43,6 +43,7 @@ professional emails and reuse templates in a few clicks. 'wizard/mail_compose_message_view.xml', 'views/mass_mailing.xml', 'views/res_config.xml', + 'views/res_partner.xml', 'views/email_template.xml', 'security/ir.model.access.csv', ], diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index c3e661efa89..9eb1bcf55bc 100644 --- a/addons/mass_mailing/models/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -64,7 +64,7 @@ class MailMail(osv.Model): body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div') return body - def _postprocess_sent_message(self, cr, uid, mail, context=None): - if mail.state == 'sent' and mail.statistics_ids: + def _postprocess_sent_message(self, cr, uid, mail, context=None, mail_sent=True): + if mail_sent is True and mail.statistics_ids: self.pool['mail.mail.statistics'].write(cr, uid, [s.id for s in mail.statistics_ids], {'sent': fields.datetime.now()}, context=context) - return super(MailMail, self)._postprocess_sent_message(cr, uid, mail, context=context) + return super(MailMail, self)._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 7abdc6785c6..dfaad617082 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -51,11 +51,20 @@ class MassMailingContact(osv.Model): 'email': fields.char('Email', required=True), 'list_id': fields.many2one( 'mail.mass_mailing.list', string='Mailing List', - required=True, ondelete='cascade', + ondelete='cascade', ), 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive news anymore from this mailing list'), } + def name_create(self, cr, uid, name, context=None): + name, email = self.pool['res.partner']._parse_partner_name(name, context=context) + if name and not email: + email = name + if email and not name: + name = email + rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context) + return self.name_get(cr, uid, [rec_id], context)[0] + class MassMailingList(osv.Model): """Model of a contact list. """ @@ -72,19 +81,16 @@ class MassMailingList(osv.Model): elif 'active_ids' in context: res['domain'] = '%s' % [('id', 'in', context['active_ids'])] else: - res['domain'] = '%s' % [('id', 'in', context.get('active_id', 0))] + res['domain'] = False return res def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): """Compute the number of contacts linked to the mailing list. """ results = dict.fromkeys(ids, 0) for contact_list in self.browse(cr, uid, ids, context=context): - print contact_list, contact_list.model, contact_list.domain - domain = self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id] - print domain results[contact_list.id] = self.pool[contact_list.model].search( cr, uid, - domain, + self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id], count=True, context=context ) return results @@ -428,7 +434,7 @@ class MassMailing(osv.Model): 'template_id': fields.many2one( 'email.template', 'Email Template', domain=[('use_in_mass_mailing', '=', True)], - ondelete='set null', + required=True, ), 'body_html': fields.related( 'template_id', 'body_html', type='html', @@ -445,7 +451,10 @@ class MassMailing(osv.Model): ), # mailing options 'email_from': fields.char('From'), - 'email_to': fields.char('Send to Emails'), + 'email_to': fields.many2many( + 'mail.mass_mailing.contact', 'mail_mass_mailing_contact_rel', + string='Test Emails' + ), 'reply_to': fields.char('Reply To'), 'mailing_model': fields.selection(_mailing_model, string='Type', required=True), 'contact_list_ids': fields.many2many( @@ -554,6 +563,12 @@ class MassMailing(osv.Model): values['body_html'] = False return {'value': values} + def _get_model_to_list_action_id(self, cr, uid, model, context=None): + if model == 'res.partner': + return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') + else: + return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_contact_to_mailing_list') + def action_new_list(self, cr, uid, ids, context=None): wizard = self.browse(cr, uid, ids[0], context=context) action_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') @@ -584,16 +599,24 @@ class MassMailing(osv.Model): # Email Sending #------------------------------------------------------ + def _get_mail_recipients(self, cr, uid, mailing, res_ids, context=None): + if mailing.mailing_model == 'mail.mass_mailing.contact': + contacts = self.pool['mail.mass_mailing.contact'].browse(cr, uid, res_ids, context=context) + return dict((contact.id, {'email_to': '"%s" <%s>' % (contact.name, contact.email)}) for contact in contacts) + else: + return dict((res_id, {'recipient_ids': [(4, res_id)]}) for res_id in res_ids) + def send_mail(self, cr, uid, ids, context=None): Mail = self.pool['mail.mail'] for mailing in self.browse(cr, uid, ids, context=context): - domain = self.pool['mail.mass_mailing.list'].get_global_domain(cr, uid, [l.id for l in mailing.contact_list_ids], context=context)[mailing.mailing_model] + domain = self.pool['mail.mass_mailing.list'].get_global_domain( + cr, uid, [l.id for l in mailing.contact_list_ids], context=context + )[mailing.mailing_model] res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context) + recipients = self._get_mail_recipients(cr, uid, mailing, res_ids, context=context) all_mail_values = self.pool['mail.compose.message'].generate_email_for_composer_batch( cr, uid, mailing.template_id.id, res_ids, - context=context, - fields=['body_html', 'attachment_ids', 'mail_server_id'] - ) + context=context, fields=['body_html', 'attachment_ids', 'mail_server_id']) for res_id, mail_values in all_mail_values.iteritems(): mail_values.update({ 'email_from': mailing.email_from, @@ -601,40 +624,48 @@ class MassMailing(osv.Model): 'subject': mailing.name, 'body_html': mail_values.get('body'), 'auto_delete': True, - 'statistics_ids': [(0, 0, { + 'notification': True, + }) + mail_values['statistics_ids'] = [ + (0, 0, { 'model': mailing.mailing_model, 'res_id': res_id, 'mass_mailing_id': mailing.id, })] - }) m2m_attachment_ids = self.pool['mail.thread']._message_preprocess_attachments( cr, uid, mail_values.pop('attachments', []), mail_values.pop('attachment_ids', []), 'mail.message', 0, context=context) mail_values['attachment_ids'] = m2m_attachment_ids - if mailing.mailing_model == 'mail.mass_mailing.list': - contact = self.pool['mail.mass_mailing.contact'].browse(cr, uid, res_id, context=context) - mail_values['email_to'] = '"%s" <%s>' % (contact.name, contact.email) - elif mailing.mailing_model == 'res.partner': - mail_values['recipient_ids'] = [(4, res_id)] + + mail_values.update(recipients[res_id]) + Mail.create(cr, uid, mail_values, context=context) - # todo: handle email_to return True - def send_mail_to_myself(self, cr, uid, ids, context=None): + def send_mail_test(self, cr, uid, ids, context=None): Mail = self.pool['mail.mail'] for mailing in self.browse(cr, uid, ids, context=context): - mail_values = { - 'email_from': mailing.email_from, - 'reply_to': mailing.reply_to, - 'email_to': self.pool['res.users'].browse(cr, uid, uid, context=context).email, - 'subject': mailing.name, - 'body_html': mailing.template_id.body_html, - 'auto_delete': True, - } - mail_id = Mail.create(cr, uid, mail_values, context=context) - Mail.send(cr, uid, [mail_id], context=context) + # res_ids = self._set_up_test_mailing(cr, uid, mailing.mailing_model, context=context) + res_ids = [c.id for c in mailing.email_to] + all_mail_values = self.pool['mail.compose.message'].generate_email_for_composer_batch( + cr, uid, mailing.template_id.id, res_ids, + context=context, + fields=['body_html', 'attachment_ids', 'mail_server_id'] + ) + mail_ids = [] + for res_id, mail_values in all_mail_values.iteritems(): + mail_values = { + 'email_from': mailing.email_from, + 'reply_to': mailing.reply_to, + 'email_to': self.pool['mail.mass_mailing.contact'].browse(cr, uid, res_id, context=context).email, + 'subject': mailing.name, + 'body_html': mail_values.get('body'), + 'auto_delete': True, + } + mail_ids.append(Mail.create(cr, uid, mail_values, context=context)) + Mail.send(cr, uid, mail_ids, context=context) return True diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index 110439e3e55..dc86fb6c947 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -72,6 +72,17 @@ parent="mass_mailing_list" sequence="50" action="action_view_mass_mailing_contacts"/> + + + mail.mass_mailing.list.search @@ -126,8 +137,8 @@ - + + @@ -189,8 +200,8 @@
-
+ + + + + +
+
+
+ mail.mass_mailing.list.form mail.mass_mailing.list @@ -137,8 +166,7 @@ - - + @@ -221,7 +249,11 @@ - + - Imported Contacts @@ -174,7 +117,7 @@ Second Newsletter test - + From 272a7e5f80a132c4d484b599bbcf538645cb063f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 27 Mar 2014 15:11:41 +0100 Subject: [PATCH 078/186] [FIX] email_template: fixed error in default recipients calculation bzr revid: tde@openerp.com-20140327141141-qe47tlnnfb5xt9s8 --- addons/email_template/email_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index b6dfb6834bb..4eb01d5cf3d 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -410,7 +410,7 @@ class email_template(osv.osv): default_recipients = {} for res_id, recipients in default_recipients.iteritems(): results[res_id].pop('partner_to', None) - results[res_id].update(default_recipients) + results[res_id].update(recipients) for res_id, values in results.iteritems(): partner_ids = values.get('partner_ids', list()) From e65b472cb50255ed082dc3d60658962037c065d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 27 Mar 2014 15:12:41 +0100 Subject: [PATCH 079/186] [IMP] email_template: previw improvements : - usability improveemnts on the sample document, more clear - add partner_ids fields, displaying result of email template rendering that now also generated partner_ids that will be recipient_ids on a mail_mail - added attachment display bzr revid: tde@openerp.com-20140327141241-2w2lxj5hkq8nqcu5 --- .../email_template/wizard/email_template_preview.py | 3 ++- .../wizard/email_template_preview_view.xml | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/addons/email_template/wizard/email_template_preview.py b/addons/email_template/wizard/email_template_preview.py index 5fee415a75e..104437ec0d3 100644 --- a/addons/email_template/wizard/email_template_preview.py +++ b/addons/email_template/wizard/email_template_preview.py @@ -66,6 +66,7 @@ class email_template_preview(osv.osv_memory): _columns = { 'res_id': fields.selection(_get_records, 'Sample Document'), + 'partner_ids': fields.many2many('res.partner', string='Recipients'), } def on_change_res_id(self, cr, uid, ids, res_id, context=None): @@ -80,7 +81,7 @@ class email_template_preview(osv.osv_memory): # generate and get template values mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context) - vals = dict((field, mail_values.get(field, False)) for field in ('email_from', 'email_to', 'email_cc', 'reply_to', 'subject', 'body_html', 'partner_to')) + vals = dict((field, mail_values.get(field, False)) for field in ('email_from', 'email_to', 'email_cc', 'reply_to', 'subject', 'body_html', 'partner_to', 'partner_ids', 'attachment_ids')) vals['name'] = template.name return {'value': vals} diff --git a/addons/email_template/wizard/email_template_preview_view.xml b/addons/email_template/wizard/email_template_preview_view.xml index 6d61658dcdc..87c1e92fa60 100644 --- a/addons/email_template/wizard/email_template_preview_view.xml +++ b/addons/email_template/wizard/email_template_preview_view.xml @@ -8,14 +8,17 @@
-

Preview of

- Using sample document +

Preview of

+ Choose an example record: + - - + + +
From 16363697473205dfdb28a57a2d36b89b35d55958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 27 Mar 2014 15:12:59 +0100 Subject: [PATCH 080/186] [IMP] mass_mailing: create template: hide mass_mailing_id field bzr revid: tde@openerp.com-20140327141259-fs2eazs27qzgp54u --- addons/mass_mailing/wizard/create_template_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mass_mailing/wizard/create_template_view.xml b/addons/mass_mailing/wizard/create_template_view.xml index e240017e572..dd2f288feb7 100644 --- a/addons/mass_mailing/wizard/create_template_view.xml +++ b/addons/mass_mailing/wizard/create_template_view.xml @@ -10,7 +10,7 @@ - + From 900705ca5b8ade7fbecda634b019d3b546743c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 27 Mar 2014 16:46:06 +0100 Subject: [PATCH 081/186] [IMP] mass_mailing: added a new menu entry to create a new mailing list that calls a new wizard, allowing to select contacts/customers/leads and create a new mailing list. Also updated default values in action 'create mailing list' for contact, customer and leads. Also removed minimal form view on list, using only one view, with technical fields put in group_no_one. bzr revid: tde@openerp.com-20140327154606-7zrzi1ri1hfaxtay --- addons/mass_mailing/__openerp__.py | 1 + addons/mass_mailing/models/mass_mailing.py | 19 ++--- addons/mass_mailing/views/mass_mailing.xml | 81 +++++++------------ addons/mass_mailing/views/res_partner.xml | 8 +- addons/mass_mailing/wizard/__init__.py | 2 +- addons/mass_mailing/wizard/create_list.py | 25 ++++++ .../mass_mailing/wizard/create_list_view.xml | 33 ++++++++ .../mass_mailing_crm/models/mass_mailing.py | 2 +- addons/mass_mailing_crm/views/crm_lead.xml | 7 +- 9 files changed, 109 insertions(+), 69 deletions(-) create mode 100644 addons/mass_mailing/wizard/create_list.py create mode 100644 addons/mass_mailing/wizard/create_list_view.xml diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py index b637f2b17e0..161af2187a4 100644 --- a/addons/mass_mailing/__openerp__.py +++ b/addons/mass_mailing/__openerp__.py @@ -43,6 +43,7 @@ professional emails and reuse templates in a few clicks. 'data/mass_mailing_data.xml', 'wizard/mail_compose_message_view.xml', 'wizard/create_template_view.xml', + 'wizard/create_list_view.xml', 'views/mass_mailing.xml', 'views/res_config.xml', 'views/res_partner.xml', diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 89d190f51cd..5281e962426 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -81,7 +81,8 @@ class MassMailingList(osv.Model): """Override default_get to handle active_domain coming from the list view. """ res = super(MassMailingList, self).default_get(cr, uid, fields, context=context) if 'domain' in fields: - res['model'] = context.get('active_model', 'res.partner') + if not 'model' in res and context.get('active_model'): + res['model'] = context['active_model'] if 'active_domain' in context: res['domain'] = '%s' % context['active_domain'] elif 'active_ids' in context: @@ -572,12 +573,6 @@ class MassMailing(osv.Model): values['body_html'] = False return {'value': values} - def _get_model_to_list_action_id(self, cr, uid, model, context=None): - if model == 'res.partner': - return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') - else: - return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_contact_to_mailing_list') - def action_duplicate(self, cr, uid, ids, context=None): copy_id = None for mailing in self.browse(cr, uid, ids, context=context): @@ -598,10 +593,16 @@ class MassMailing(osv.Model): } return False + def _get_model_to_list_action_id(self, cr, uid, model, context=None): + if model == 'res.partner': + return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') + else: + return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_contact_to_mailing_list') + def action_new_list(self, cr, uid, ids, context=None): wizard = self.browse(cr, uid, ids[0], context=context) - action_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'mass_mailing.action_partner_to_mailing_list') - ctx = dict(context, view_manager_highlight=[action_id], default_mass_mailing_id=ids[0]) + action_id = self._get_model_to_list_action_id(cr, uid, wizard.mailing_model, context=context) + ctx = dict(context, view_manager_highlight=[action_id], default_mass_mailing_id=ids[0], default_model=wizard.mailing_model) return { 'name': _('Choose Recipients'), 'type': 'ir.actions.act_window', diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index 93fab015468..4f20c455ad7 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -68,11 +68,11 @@ {'search_default_not_opt_out': 1}
- - + + context="{ +'default_mass_mailing_id': context.get('default_mass_mailing_id'), +'default_model': context.get('default_model', 'mail.mass_mailing.contact'), +'default_name': context.get('default_name', False)}"/> @@ -108,10 +111,9 @@
- - mail.mass_mailing.list.form.minimal + + mail.mass_mailing.list.form mail.mass_mailing.list - 20
@@ -125,48 +127,15 @@
+
+

+ emails are in queue + and will be sent soon. +

+
@@ -222,20 +229,27 @@ type='action' name='%(action_mailing_email_template)d' attrs="{'invisible': [('template_id', '!=', False)]}" context="{'default_mass_mailing_id': active_id}"/> +
+
-

Here be some graphs

+

Here be some pie charts

+ + + + +

Here be some bar charts

+ +
From e0134a2bfa9253b372073b7bbb0b6e66f551f7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 31 Mar 2014 13:06:26 +0200 Subject: [PATCH 089/186] [IMP] mass_mailing: create wizard: do not inherit from mailing list anymore, just have the necessary fields to simplify bzr revid: tde@openerp.com-20140331110626-iewarhctoyoaucsg --- addons/mass_mailing/wizard/create_list.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/addons/mass_mailing/wizard/create_list.py b/addons/mass_mailing/wizard/create_list.py index 809aff62820..43ac1453f13 100644 --- a/addons/mass_mailing/wizard/create_list.py +++ b/addons/mass_mailing/wizard/create_list.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from openerp.osv import osv +from openerp.osv import osv, fields from openerp.tools.translate import _ @@ -9,11 +9,28 @@ class MailingListWizard(osv.TransientModel): allows to simplify and direct the user in the creation of its template without having to tune or hack the email.template model. """ _name = 'mail.mass_mailing.list.create' - _inherit = 'mail.mass_mailing.list' + + def _get_model_list(self, cr, uid, context=None): + return self.pool['mail.mass_mailing']._get_mailing_model(cr, uid, context=context) + + # indirections for inheritance + _model_list = lambda self, *args, **kwargs: self._get_model_list(*args, **kwargs) + + _columns = { + 'name': fields.char('Name', required=True), + 'model': fields.selection( + _model_list, type='char', required=True, + string='Applies To' + ), + } def action_new_list(self, cr, uid, ids, context=None): wizard = self.browse(cr, uid, ids[0], context=context) action_id = self.pool['mail.mass_mailing']._get_model_to_list_action_id(cr, uid, wizard.model, context=context) + if wizard.model == 'mail.mass_mailing.contact': + domain = [('list_id', '=', False)] + else: + domain = [] ctx = dict(context, search_default_not_opt_out=True, view_manager_highlight=[action_id], default_name=wizard.name, default_model=wizard.model) return { 'name': _('Choose Recipients'), @@ -22,4 +39,5 @@ class MailingListWizard(osv.TransientModel): 'view_mode': 'tree,form', 'res_model': wizard.model, 'context': ctx, + 'domain': domain, } From d363a5adba0c84daddcc48cb3ec0f26172fc0d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 31 Mar 2014 13:07:56 +0200 Subject: [PATCH 090/186] [IMP] mass_mailing: mailing list: better managemetn of contacts - choose contacts without mailing list - at creation, dynamically set the contacts to the mailing list then update the list domain bzr revid: tde@openerp.com-20140331110756-mhraczkk1i8tr62p --- addons/mass_mailing/models/mass_mailing.py | 38 +++++++++------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 2bad38718b4..432fc4515bd 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -54,6 +54,7 @@ class MassMailingContact(osv.Model): 'email': fields.char('Email', required=True), 'list_id': fields.many2one( 'mail.mass_mailing.list', string='Mailing List', + domain=[('model', '=', 'mail.mass_mailing.contact')], ondelete='cascade', ), 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive news anymore from this mailing list'), @@ -114,12 +115,6 @@ class MassMailingList(osv.Model): _get_contact_nbr, type='integer', string='Contact Number', ), - # contact-based list - 'contact_ids': fields.one2many( - 'mail.mass_mailing.contact', 'list_id', string='Contacts', - domain=[('opt_out', '=', False)], - ), - # filter-based list 'model': fields.selection( _model_list, type='char', required=True, string='Applies To' @@ -132,20 +127,13 @@ class MassMailingList(osv.Model): } def on_change_model(self, cr, uid, ids, model, context=None): - values = {} - if model == 'mail.mass_mailing.contact': - values.update(domain=False, filter_id=False, template_id=False) - else: - values.update(filter_id=False, template_id=False) - return {'value': values} + return {'value': {'filter_id': False}} def on_change_filter_id(self, cr, uid, ids, filter_id, context=None): values = {} if filter_id: ir_filter = self.pool['ir.filters'].browse(cr, uid, filter_id, context=context) values['domain'] = ir_filter.domain - else: - values['domain'] = False return {'value': values} def on_change_domain(self, cr, uid, ids, domain, model, context=None): @@ -155,6 +143,17 @@ class MassMailingList(osv.Model): domain = eval(domain) return {'value': {'contact_nbr': self.pool[model].search(cr, uid, domain, context=context, count=True)}} + def create(self, cr, uid, values, context=None): + new_id = super(MassMailingList, self).create(cr, uid, values, context=context) + if values.get('model') == 'mail.mass_mailing.contact': + domain = values.get('domain') + if domain is None or domain is False: + return new_id + contact_ids = self.pool['mail.mass_mailing.contact'].search(cr, uid, eval(domain), context=context) + self.pool['mail.mass_mailing.contact'].write(cr, uid, contact_ids, {'list_id': new_id}, context=context) + self.pool['mail.mass_mailing.list'].write(cr, uid, [new_id], {'domain': [('list_id', '=', new_id)]}, context=context) + return new_id + def action_see_records(self, cr, uid, ids, context=None): contact_list = self.browse(cr, uid, ids[0], context=context) ctx = dict(context) @@ -188,19 +187,15 @@ class MassMailingList(osv.Model): } def _get_domain(self, cr, uid, ids, context=None): + # todo: model-based method + check opt_out field exists domains = {} for contact_list in self.browse(cr, uid, ids, context=context): if contact_list.model == 'mail.mass_mailing.contact': domain = [('list_id', '=', contact_list.id)] - elif not contact_list.domain: # domain is a string like False or None -> void list + elif contact_list.domain is False or contact_list.domain is None: # domain is a string like False or None -> void list domain = [('id', '=', '0')] else: domain = eval(contact_list.domain) - # force the addition of opt_out filter - if domain: - domain = ['&', ('opt_out', '=', False)] + domain - else: - domain = [('opt_out', '=', False)] domains[contact_list.id] = domain return domains @@ -674,7 +669,7 @@ class MassMailing(osv.Model): Mail = self.pool['mail.mail'] for mailing in self.browse(cr, uid, ids, context=context): if not mailing.template_id: - raise Warning('Please specifiy a template to use.') + raise Warning('Please specify a template to use.') if not mailing.contact_nbr: raise Warning('Please select recipients.') @@ -729,7 +724,6 @@ class MassMailing(osv.Model): for mailing in self.browse(cr, uid, ids, context=context): if not mailing.template_id: raise Warning('Please specifiy a template to use.') - # res_ids = self._set_up_test_mailing(cr, uid, mailing.mailing_model, context=context) test_emails = tools.email_split(mailing.test_email_to) if not test_emails: raise Warning('Please specifiy test email adresses.') From 46541bbab94e976c5caf9c6e95fe60ad06c6d352 Mon Sep 17 00:00:00 2001 From: "Yogesh Parekh (OpenERP)" Date: Tue, 1 Apr 2014 12:32:27 +0530 Subject: [PATCH 091/186] [IMP]: Improve Reference Terms in account module bzr revid: ypa@tinyerp.com-20140401070227-w0knsr5p4gb4b06p --- addons/account/account_invoice.py | 2 +- addons/account/account_invoice_view.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 06e59232ac9..99b1157b69c 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -226,7 +226,7 @@ class account_invoice(osv.osv): }, } _columns = { - 'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}), + 'name': fields.char('Reference/Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}), 'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}), 'supplier_invoice_number': fields.char('Supplier Invoice Number', size=64, help="The reference of this invoice as provided by the supplier.", readonly=True, states={'draft':[('readonly',False)]}), 'type': fields.selection([ diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index a289e9149cb..b521f3ff48c 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -404,7 +404,7 @@ - + From d5bc161438954729227e5c530aba3759588b1948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 1 Apr 2014 15:24:54 +0200 Subject: [PATCH 092/186] [IMP] mass_mailing: now allowing to mail only a part of the mailing lists, to perform AB testing. Also allows to avoid mailing contacts more than once in a given campaign. bzr revid: tde@openerp.com-20140401132454-g65utzd1zd8crpq9 --- addons/mass_mailing/models/mass_mailing.py | 85 ++++++++++++++++++---- addons/mass_mailing/views/mass_mailing.xml | 9 +++ 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 432fc4515bd..88053127dd9 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -21,6 +21,7 @@ from datetime import datetime from dateutil import relativedelta +import random import urllib import urlparse @@ -69,9 +70,6 @@ class MassMailingContact(osv.Model): rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context) return self.name_get(cr, uid, [rec_id], context)[0] - def name_get(self, cr, uid, ids, context=None): - return [(contact.id, '%s <%s>' % (contact.name, contact.email)) for contact in self.browse(cr, uid, ids, context=context)] - class MassMailingList(osv.Model): """Model of a contact list. """ @@ -257,10 +255,11 @@ class MassMailingCampaign(osv.Model): 'mail.mass_mailing', 'mass_mailing_campaign_id', 'Mass Mailings', ), - 'statistics_ids': fields.one2many( - 'mail.mail.statistics', 'mass_mailing_campaign_id', - 'Sent Emails', - ), + 'ab_testing': fields.boolean( + 'AB Testing', + help='If checked, recipients will be mailed only once, allowing to send' + 'various mailings in a single campaign to test the effectiveness' + 'of the mailings.'), 'color': fields.integer('Color Index'), # stat fields 'total': fields.function( @@ -338,6 +337,23 @@ class MassMailingCampaign(osv.Model): 'context': dict(context, default_mass_mailing_campaign_id=ids[0]), } + #------------------------------------------------------ + # API + #------------------------------------------------------ + + def get_recipients(self, cr, uid, ids, model=None, context=None): + """Return the recipints of a mailing campaign. This is based on the statistics + build for each mailing. """ + Statistics = self.pool['mail.mail.statistics'] + res = dict.fromkeys(ids, False) + for cid in ids: + domain = [('mass_mailing_campaign_id', '=', cid)] + if model: + domain += [('model', '=', model)] + stat_ids = Statistics.search(cr, uid, domain, context=context) + res[cid] = set(stat.res_id for stat in Statistics.browse(cr, uid, stat_ids, context=context)) + return res + class MassMailing(osv.Model): """ MassMailing models a wave of emails for a mass mailign campaign. @@ -416,13 +432,18 @@ class MassMailing(osv.Model): return results def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): - res = dict.fromkeys(ids, 0) + res = dict.fromkeys(ids, False) for mailing in self.browse(cr, uid, ids, context=context): - res[mailing.id] = self.pool[mailing.mailing_model].search( + val = {'contact_nbr': 0, 'contact_ab_nbr': 0, 'contact_ab_done': 0} + val['contact_nbr'] = self.pool[mailing.mailing_model].search( cr, uid, 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], count=True, context=context ) + val['contact_ab_nbr'] = int(val['contact_nbr'] * mailing.contact_ab_pc / 100.0) + if mailing.mass_mailing_campaign_id and mailing.ab_testing: + val['contact_ab_done'] = len(self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id]) + res[mailing.id] = val return res def _get_mailing_model(self, cr, uid, context=None): @@ -457,6 +478,10 @@ class MassMailing(osv.Model): 'mail.mass_mailing.campaign', 'Mass Mailing Campaign', ondelete='set null', ), + 'ab_testing': fields.related( + 'mass_mailing_campaign_id', 'ab_testing', + type='boolean', string='AB Testing' + ), 'color': fields.related( 'mass_mailing_campaign_id', 'color', type='integer', string='Color Index', @@ -471,7 +496,22 @@ class MassMailing(osv.Model): string='Mailing Lists', domain="[('model', '=', mailing_model)]", ), - 'contact_nbr': fields.function(_get_contact_nbr, type='integer', string='Contact Number'), + 'contact_nbr': fields.function( + _get_contact_nbr, type='integer', multi='_get_contact_nbr', + string='Contact Number' + ), + 'contact_ab_pc': fields.integer( + 'AB Testing percentage', + help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.' + ), + 'contact_ab_nbr': fields.function( + _get_contact_nbr, type='integer', multi='_get_contact_nbr', + string='Contact Number in AB Testing' + ), + 'contact_ab_done': fields.function( + _get_contact_nbr, type='integer', multi='_get_contact_nbr', + string='Number of already mailed contacts' + ), # statistics data 'statistics_ids': fields.one2many( 'mail.mail.statistics', 'mass_mailing_id', @@ -535,6 +575,7 @@ class MassMailing(osv.Model): 'date': fields.datetime.now, 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx), 'mailing_model': 'res.partner', + 'contact_ab_pc': 100, } #------------------------------------------------------ @@ -654,6 +695,25 @@ class MassMailing(osv.Model): partners = self.pool['res.partner'].browse(cr, uid, res_ids, context=context) return dict((partner.id, {'partner_id': partner.id, 'name': partner.name, 'email': partner.email}) for partner in partners) + def get_recipients(self, cr, uid, mailing, context=None): + domain = self.pool['mail.mass_mailing.list'].get_global_domain( + cr, uid, [l.id for l in mailing.contact_list_ids], context=context + )[mailing.mailing_model] + res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context) + + # randomly choose a fragment + if mailing.contact_ab_pc != 100: + topick = mailing.contact_ab_nbr + if mailing.mass_mailing_campaign_id and mailing.ab_testing: + already_mailed = self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id] + else: + already_mailed = set([]) + remaining = set(res_ids).difference(already_mailed) + if topick > len(remaining): + topick = len(remaining) + res_ids = random.sample(remaining, topick) + return res_ids + def get_unsubscribe_url(self, cr, uid, mailing_id, res_id, email, msg=None, context=None): base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') url = urlparse.urljoin( @@ -674,10 +734,7 @@ class MassMailing(osv.Model): raise Warning('Please select recipients.') # get mail and recipints data - domain = self.pool['mail.mass_mailing.list'].get_global_domain( - cr, uid, [l.id for l in mailing.contact_list_ids], context=context - )[mailing.mailing_model] - res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context) + res_ids = self.get_recipients(cr, uid, mailing, context=context) template_values = self.pool['mail.compose.message'].generate_email_for_composer_batch( cr, uid, mailing.template_id.id, res_ids, context=context, fields=['body_html', 'attachment_ids', 'mail_server_id']) diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index 0a22c81a3eb..aef24670b1f 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -275,6 +275,14 @@ contacts selected + + + @@ -258,12 +273,13 @@