diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py index ac9eccb3844..ca9ea76a7bd 100644 --- a/addons/mass_mailing/__openerp__.py +++ b/addons/mass_mailing/__openerp__.py @@ -44,7 +44,6 @@ professional emails and reuse templates in a few clicks. 'data/mass_mailing_data.xml', 'wizard/mail_compose_message_view.xml', 'wizard/test_mailing.xml', - 'wizard/mailing_list_confirm.xml', 'views/mass_mailing_report.xml', 'views/mass_mailing.xml', 'views/res_config.xml', @@ -55,7 +54,9 @@ professional emails and reuse templates in a few clicks. 'js': [ 'static/src/js/mass_mailing.js', ], - 'qweb': [], + 'qweb' : [ + 'static/src/xml/*.xml', + ], 'css': [ 'static/src/css/mass_mailing.css', 'static/src/css/email_template.css' diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml index 4857c235a98..b35608e6fbc 100644 --- a/addons/mass_mailing/data/mass_mailing_demo.xml +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -14,7 +14,6 @@ ${object.id} ]]> - @@ -69,29 +68,26 @@ 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 - + @@ -103,13 +99,12 @@ Newsletter - + First Newsletter done - @@ -117,7 +112,6 @@ Second Newsletter test - diff --git a/addons/mass_mailing/models/__init__.py b/addons/mass_mailing/models/__init__.py index bc5755baddd..702da268d84 100644 --- a/addons/mass_mailing/models/__init__.py +++ b/addons/mass_mailing/models/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- import mass_mailing +import mass_mailing_stats import mail_mail import mail_thread -import email_template import res_config import mass_mailing_report diff --git a/addons/mass_mailing/models/email_template.py b/addons/mass_mailing/models/email_template.py deleted file mode 100644 index 0403c857528..00000000000 --- a/addons/mass_mailing/models/email_template.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -from openerp.tools.translate import _ -from openerp.osv import osv, fields - - -class EmailTemplate(osv.Model): - """Add the mass mailing campaign data to mail""" - _name = 'email.template' - _inherit = ['email.template'] - - _columns = { - 'use_in_mass_mailing': fields.boolean('Available for marketing and mailing'), - } - - def action_new_mailing(self, cr, uid, ids, context=None): - template = self.browse(cr, uid, ids[0], context=context) - ctx = dict(context) - ctx.update({ - 'default_mailing_model': template.model, - 'default_template_id': ids[0], - }) - return { - 'name': _('Create a Mass Mailing'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.mass_mailing', - 'views': [(False, 'form')], - 'context': ctx, - } - - -class email_template_preview(osv.TransientModel): - """ Reinitialize email template preview model to have access to all email.template - new fields. """ - _name = "email_template.preview" - _inherit = ['email.template', 'email_template.preview'] diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 24870ac8408..b7f40495e36 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -22,10 +22,7 @@ from datetime import datetime from dateutil import relativedelta import random -try: - import simplejson as json -except ImportError: - import json +import json import urllib import urlparse @@ -40,6 +37,7 @@ class MassMailingCategory(osv.Model): """Model of categories of mass mailing, i.e. marketing, newsletter, ... """ _name = 'mail.mass_mailing.category' _description = 'Mass Mailing Category' + _order = 'name' _columns = { 'name': fields.char('Name', required=True), @@ -50,19 +48,29 @@ class MassMailingContact(osv.Model): """Model of a contact. This model is different from the partner model because it holds only some basic information: name, email. The purpose is to be able to deal with large contact list to email without bloating the partner - database. """ + base.""" _name = 'mail.mass_mailing.contact' _description = 'Mass Mailing Contact' + _order = 'email' + _rec_name = 'email' _columns = { - 'name': fields.char('Name', required=True), + 'name': fields.char('Name'), 'email': fields.char('Email', required=True), + 'create_date': fields.datetime('Create Date'), 'list_id': fields.many2one( 'mail.mass_mailing.list', string='Mailing List', - domain=[('model', '=', 'mail.mass_mailing.contact')], - ondelete='cascade', + ondelete='cascade', required=True, ), - 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive news anymore from this mailing list'), + 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this list'), + } + + def _get_latest_list(self, cr, uid, context={}): + lid = self.pool.get('mail.mass_mailing.list').search(cr, uid, [], limit=1, order='id desc', context=context) + return lid and lid[0] or False + + _defaults = { + 'list_id': _get_latest_list } def name_create(self, cr, uid, name, context=None): @@ -78,150 +86,33 @@ class MassMailingContact(osv.Model): class MassMailingList(osv.Model): """Model of a contact list. """ _name = 'mail.mass_mailing.list' - _description = 'Contact List' - - 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: - 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: - res['domain'] = '%s' % [('id', 'in', context['active_ids'])] - else: - res['domain'] = False - return res + _order = 'name' + _description = 'Mailing List' 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): - results[contact_list.id] = self.pool[contact_list.model].search( - cr, uid, - self._get_domain(cr, uid, [contact_list.id], context=context)[contact_list.id], - count=True, context=context - ) - return results - - 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) + result = dict.fromkeys(ids, 0) + Contacts = self.pool.get('mail.mass_mailing.contact') + for group in Contacts.read_group(cr, uid, [('list_id', 'in', ids), ('opt_out', '!=', True)], ['list_id'], ['list_id'], context=context): + result[group['list_id'][0]] = group['list_id_count'] + return result _columns = { - 'name': fields.char('Name', required=True), + 'name': fields.char('Mailing List', required=True), 'contact_nbr': fields.function( _get_contact_nbr, type='integer', - string='Contact Number', + string='Number of Contacts', ), - 'model': fields.selection( - _model_list, type='char', required=True, - string='Applies To' - ), - 'filter_id': fields.many2one( - 'ir.filters', string='Custom Filter', - domain="[('model_id.model', '=', model)]", - ), - 'domain': fields.text('Domain'), } - def on_change_model(self, cr, uid, ids, model, context=None): - 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 - return {'value': values} - - def on_change_domain(self, cr, uid, ids, domain, model, context=None): - if domain is False: - return {'value': {'contact_nbr': 0}} - else: - 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' and (not context or not context.get('no_contact_to_list')): - 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) - ctx['search_default_not_opt_out'] = True - return { - 'name': _('See Contact List'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': contact_list.model, - 'views': [(False, 'tree'), (False, 'form')], - 'view_id': False, - 'target': 'current', - 'context': ctx, - 'domain': contact_list.domain, - } - - 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': _('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): - if 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) - 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: - 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 MassMailingStage(osv.Model): """Stage for mass mailing campaigns. """ _name = 'mail.mass_mailing.stage' _description = 'Mass Mailing Campaign Stage' - _order = 'sequence ASC' + _order = 'sequence' _columns = { - 'name': fields.char('Name', required=True), + 'name': fields.char('Name', required=True, translate=True), 'sequence': fields.integer('Sequence'), } @@ -264,9 +155,9 @@ class MassMailingCampaign(osv.Model): 'res.users', 'Responsible', required=True, ), - 'category_id': fields.many2one( - 'mail.mass_mailing.category', 'Category', - help='Category'), + 'category_ids': fields.many2many( + 'mail.mass_mailing.category', 'mail_mass_mailing_category_rel', + 'category_id', 'campaign_id', string='Categories'), 'mass_mailing_ids': fields.one2many( 'mail.mass_mailing', 'mass_mailing_campaign_id', 'Mass Mailings', @@ -326,32 +217,13 @@ class MassMailingCampaign(osv.Model): def _get_default_stage_id(self, cr, uid, context=None): stage_ids = self.pool['mail.mass_mailing.stage'].search(cr, uid, [], limit=1, context=context) - return stage_ids and stage_ids[0] + return stage_ids and stage_ids[0] or False _defaults = { 'user_id': lambda self, cr, uid, ctx=None: uid, - 'stage_id': lambda self, cr, uid, ctx=None: self._get_default_stage_id(cr, uid, context=ctx), + 'stage_id': lambda self, *args: self._get_default_stage_id(*args), } - #------------------------------------------------------ - # Actions - #------------------------------------------------------ - - def action_new_mailing(self, cr, uid, ids, context=None): - return { - 'name': _('Create a Mass Mailing for the Campaign'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.mass_mailing', - 'views': [(False, 'form')], - '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. """ @@ -448,15 +320,14 @@ class MassMailing(osv.Model): def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None): res = dict.fromkeys(ids, False) for mailing in self.browse(cr, uid, ids, context=context): - val = {'contact_nbr': 0, 'contact_ab_nbr': 0, 'contact_ab_done': 0} + 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], + cr, uid, eval(mailing.mailing_domain), 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]) + # 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 @@ -471,42 +342,32 @@ class MassMailing(osv.Model): def _get_mailing_model(self, cr, uid, context=None): return [ - ('res.partner', 'Customers'), - ('mail.mass_mailing.contact', 'Contacts') + ('res.partner', _('Customers')), + ('mail.mass_mailing.contact', _('Mailing List')) ] - def _get_state_list(self, cr, uid, context=None): - return [('draft', 'Schedule'), ('test', 'Tested'), ('done', 'Sent')] - # indirections for inheritance _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs) - _state = lambda self, *args, **kwargs: self._get_state_list(*args, **kwargs) _columns = { 'name': fields.char('Subject', required=True), + 'email_from': fields.char('From', required=True), 'date': fields.datetime('Date'), - 'state': fields.selection( - _state, string='Status', required=True, - ), - 'template_id': fields.many2one( - 'email.template', 'Email Template', - domain="[('use_in_mass_mailing', '=', True), ('model', '=', mailing_model)]", - ), 'body_html': fields.html('Body'), 'mass_mailing_campaign_id': fields.many2one( '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' + 'state': fields.selection( + [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')], + string='Status', required=True, ), 'color': fields.related( 'mass_mailing_campaign_id', 'color', type='integer', string='Color Index', ), # mailing options - 'email_from': fields.char('From'), + # TODO: simplify these 4 fields 'reply_in_thread': fields.boolean('Reply in thread'), 'reply_specified': fields.boolean('Specific Reply-To'), 'auto_reply_to_available': fields.function( @@ -514,11 +375,12 @@ class MassMailing(osv.Model): type='boolean', string='Reply in thread available' ), 'reply_to': fields.char('Reply To'), - 'mailing_model': fields.selection(_mailing_model, string='Type', required=True), + # Target Emails + 'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True), + 'mailing_domain': fields.char('Domain'), 'contact_list_ids': fields.many2many( 'mail.mass_mailing.list', 'mail_mass_mailing_list_rel', string='Mailing Lists', - domain="[('model', '=', mailing_model)]", ), 'contact_nbr': fields.function( _get_contact_nbr, type='integer', multi='_get_contact_nbr', @@ -528,14 +390,6 @@ class MassMailing(osv.Model): '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', @@ -602,7 +456,7 @@ class MassMailing(osv.Model): 'state': 'draft', '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', + 'mailing_model': 'mail.mass_mailing.contact', 'contact_ab_pc': 100, } @@ -617,7 +471,6 @@ class MassMailing(osv.Model): default.update({ 'state': 'draft', 'statistics_ids': [], - 'state': 'draft', 'name': _('%s (duplicate)') % mailing.name, }) return super(MassMailing, self).copy_data(cr, uid, id, default, context=context) @@ -626,7 +479,8 @@ class MassMailing(osv.Model): """ Override read_group to always display all states. """ if groupby and groupby[0] == "state": # Default result structure - states = self._get_state_list(cr, uid, context=context) + # states = self._get_state_list(cr, uid, context=context) + states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')] read_group_all_states = [{ '__context': {'group_by': groupby[1:]}, '__domain': domain + [('state', '=', state_value)], @@ -651,16 +505,20 @@ class MassMailing(osv.Model): # Views & Actions #------------------------------------------------------ - def on_change_mailing_model(self, cr, uid, ids, mailing_model, context=None): - values = { - 'contact_list_ids': [], - 'template_id': False, - 'contact_nbr': 0, - 'auto_reply_to_available': not mailing_model in self._get_private_models(context), - 'reply_in_thread': not mailing_model in self._get_private_models(context), - 'reply_specified': mailing_model in self._get_private_models(context) - } - return {'value': values} + def on_change_model(self, cr, uid, ids, mailing_model, list_ids, context=None): + value = {} + if mailing_model=='mail.mass_mailing.contact': + if list_ids and list_ids[0][0]==6 and list_ids[0][2]: + value['mailing_domain'] = "[('list_id', 'in', ["+','.join(map(str, list_ids[0][2]))+"])]" + else: + value['mailing_domain'] = "[('list_id', '=', False)]" + value['contact_nbr'] = self.pool[mailing_model].search( + cr, uid, eval(value['mailing_domain']), count=True, context=context + ) + else: + value['mailing_domain'] = False + value['contact_nbr'] = 0 + 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: @@ -680,30 +538,13 @@ class MassMailing(osv.Model): list_ids += command[2] if list_ids: values['contact_nbr'] = self.pool[mailing_model].search( - cr, uid, - self.pool['mail.mass_mailing.list'].get_global_domain(cr, uid, list_ids, context=context)[mailing_model], + cr, uid, [('list_id', 'in', list_ids), ('opt_out','!=',1)], count=True, context=context ) - return {'value': values} - - def on_change_template_id(self, cr, uid, ids, template_id, context=None): - values = {} - if template_id: - template = self.pool['email.template'].browse(cr, uid, template_id, context=context) - if template.email_from: - values['email_from'] = template.email_from - if template.reply_to: - values['reply_to'] = template.reply_to - values['body_html'] = template.body_html else: - values['email_from'] = self.pool['mail.message']._get_default_from(cr, uid, context=context) - values['reply_to'] = False - values['body_html'] = False + values['contact_nbr'] = 0 return {'value': values} - def on_change_contact_ab_pc(self, cr, uid, ids, contact_ab_pc, contact_nbr, context=None): - return {'value': {'contact_ab_nbr': contact_nbr * contact_ab_pc / 100.0}} - def action_duplicate(self, cr, uid, ids, context=None): copy_id = None for mid in ids: @@ -719,27 +560,14 @@ 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): - mailing = self.browse(cr, uid, ids[0], context=context) - action_id = self._get_model_to_list_action_id(cr, uid, mailing.mailing_model, context=context) - ctx = dict(context, - search_default_not_opt_out=True, - view_manager_highlight=[action_id], - default_name=mailing.name, - default_mass_mailing_id=ids[0], - default_model=mailing.mailing_model) + def action_test_mailing(self, cr, uid, ids, context=None): + ctx = dict(context, default_mass_mailing_id=ids[0]) return { - 'name': _('Choose Recipients'), + 'name': _('Test Mailing'), 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': mailing.mailing_model, + 'view_mode': 'form', + 'res_model': 'mail.mass_mailing.test', + 'target': 'new', 'context': ctx, } @@ -757,19 +585,11 @@ class MassMailing(osv.Model): 'context': context, } - def action_test_mailing(self, cr, uid, ids, context=None): - ctx = dict(context, default_mass_mailing_id=ids[0]) - return { - 'name': _('Test Mailing'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'mail.mass_mailing.test', - 'target': 'new', - 'context': ctx, - } - def action_edit_html(self, cr, uid, ids, context=None): - url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d' % ids[0] + # fixme: assert is not correct + assert len(ids)==1, "One and only one ID allowed for this action" + 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) return { 'name': _('Open with Visual Editor'), 'type': 'ir.actions.act_url', @@ -791,13 +611,14 @@ class MassMailing(osv.Model): 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] + domain = eval(mailing.mailing_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: + 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] @@ -846,80 +667,3 @@ class MassMailing(osv.Model): return True -class MailMailStats(osv.Model): - """ MailMailStats models the statistics collected about emails. Those statistics - are stored in a separated model and table to avoid bloating the mail_mail table - with statistics values. This also allows to delete emails send with mass mailing - without loosing the statistics about them. """ - - _name = 'mail.mail.statistics' - _description = 'Email Statistics' - _rec_name = 'message_id' - _order = 'message_id' - - _columns = { - 'mail_mail_id': fields.integer( - 'Mail ID', - help='ID of the related mail_mail. This field is an integer field because' - 'the related mail_mail can be deleted separately from its statistics.' - ), - 'message_id': fields.char('Message-ID'), - 'model': fields.char('Document model'), - 'res_id': fields.integer('Document ID'), - # campaign / wave data - 'mass_mailing_id': fields.many2one( - 'mail.mass_mailing', 'Mass Mailing', - ondelete='set null', - ), - 'mass_mailing_campaign_id': fields.related( - 'mass_mailing_id', 'mass_mailing_campaign_id', - type='many2one', ondelete='set null', - relation='mail.mass_mailing.campaign', - string='Mass Mailing Campaign', - store=True, readonly=True, - ), - 'template_id': fields.related( - 'mass_mailing_id', 'template_id', - type='many2one', ondelete='set null', - relation='email.template', - string='Email Template', - store=True, readonly=True, - ), - # Bounce and tracking - 'scheduled': fields.datetime('Scheduled', help='Date when the email has been created'), - 'sent': fields.datetime('Sent', help='Date when the email has been sent'), - 'exception': fields.datetime('Exception', help='Date of technical error leading to the email not being sent'), - 'opened': fields.datetime('Opened', help='Date when the email has been opened the first time'), - 'replied': fields.datetime('Replied', help='Date when this email has been replied for the first time.'), - 'bounced': fields.datetime('Bounced', help='Date when this email has bounced.'), - } - - _defaults = { - 'scheduled': fields.datetime.now, - } - - def _get_ids(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, domain=None, context=None): - if not ids and mail_mail_ids: - base_domain = [('mail_mail_id', 'in', mail_mail_ids)] - elif not ids and mail_message_ids: - base_domain = [('message_id', 'in', mail_message_ids)] - else: - base_domain = [('id', 'in', ids or [])] - if domain: - base_domain = ['&'] + domain + base_domain - return self.search(cr, uid, base_domain, context=context) - - def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): - stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('opened', '=', False)], context) - self.write(cr, uid, stat_ids, {'opened': fields.datetime.now()}, context=context) - return stat_ids - - def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): - stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('replied', '=', False)], context) - self.write(cr, uid, stat_ids, {'replied': fields.datetime.now()}, context=context) - return stat_ids - - def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): - stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('bounced', '=', False)], context) - self.write(cr, uid, stat_ids, {'bounced': fields.datetime.now()}, context=context) - return stat_ids diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mass_mailing_stats.py new file mode 100644 index 00000000000..319a6fde14d --- /dev/null +++ b/addons/mass_mailing/models/mass_mailing_stats.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013-today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see +# +############################################################################## + +from datetime import datetime +from dateutil import relativedelta +import random +try: + import simplejson as json +except ImportError: + import json +import urllib +import urlparse + +from openerp import tools +from openerp.exceptions import Warning +from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ +from openerp.osv import osv, fields + + +class MailMailStats(osv.Model): + """ MailMailStats models the statistics collected about emails. Those statistics + are stored in a separated model and table to avoid bloating the mail_mail table + with statistics values. This also allows to delete emails send with mass mailing + without loosing the statistics about them. """ + + _name = 'mail.mail.statistics' + _description = 'Email Statistics' + _rec_name = 'message_id' + _order = 'message_id' + + _columns = { + 'mail_mail_id': fields.integer( + 'Mail ID', + help='ID of the related mail_mail. This field is an integer field because' + 'the related mail_mail can be deleted separately from its statistics.' + ), + 'message_id': fields.char('Message-ID'), + 'model': fields.char('Document model'), + 'res_id': fields.integer('Document ID'), + # campaign / wave data + 'mass_mailing_id': fields.many2one( + 'mail.mass_mailing', 'Mass Mailing', + ondelete='set null', + ), + 'mass_mailing_campaign_id': fields.related( + 'mass_mailing_id', 'mass_mailing_campaign_id', + type='many2one', ondelete='set null', + relation='mail.mass_mailing.campaign', + string='Mass Mailing Campaign', + store=True, readonly=True, + ), + # Bounce and tracking + 'scheduled': fields.datetime('Scheduled', help='Date when the email has been created'), + 'sent': fields.datetime('Sent', help='Date when the email has been sent'), + 'exception': fields.datetime('Exception', help='Date of technical error leading to the email not being sent'), + 'opened': fields.datetime('Opened', help='Date when the email has been opened the first time'), + 'replied': fields.datetime('Replied', help='Date when this email has been replied for the first time.'), + 'bounced': fields.datetime('Bounced', help='Date when this email has bounced.'), + } + + _defaults = { + 'scheduled': fields.datetime.now, + } + + def _get_ids(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, domain=None, context=None): + if not ids and mail_mail_ids: + base_domain = [('mail_mail_id', 'in', mail_mail_ids)] + elif not ids and mail_message_ids: + base_domain = [('message_id', 'in', mail_message_ids)] + else: + base_domain = [('id', 'in', ids or [])] + if domain: + base_domain = ['&'] + domain + base_domain + return self.search(cr, uid, base_domain, context=context) + + def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): + stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('opened', '=', False)], context) + self.write(cr, uid, stat_ids, {'opened': fields.datetime.now()}, context=context) + return stat_ids + + def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): + stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('replied', '=', False)], context) + self.write(cr, uid, stat_ids, {'replied': fields.datetime.now()}, context=context) + return stat_ids + + def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None): + stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('bounced', '=', False)], context) + self.write(cr, uid, stat_ids, {'bounced': fields.datetime.now()}, context=context) + return stat_ids + diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js index c0dc621984f..cf4927e908e 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing.js +++ b/addons/mass_mailing/static/src/js/mass_mailing.js @@ -1,4 +1,5 @@ -openerp.mass_mailing = function(openerp) { +openerp.mass_mailing = function (instance) { + var _t = instance.web._t; openerp.web_kanban.KanbanRecord.include({ on_card_clicked: function (event) { @@ -10,4 +11,65 @@ openerp.mass_mailing = function(openerp) { }, }); + instance.web.form.CharDomainButton = instance.web.form.AbstractField.extend({ + template: 'CharDomainButton', + init: function(field_manager, node) { + this._super.apply(this, arguments); + }, + start: function() { + var self=this; + this._super.apply(this, arguments); + $('button', this.$el).on('click', self.on_click); + this.set_button(); + }, + set_button: function() { + var self = this; + // debugger + if (this.get('value')) { + // TODO: rpc to copute X + var domain = instance.web.pyeval.eval('domain', this.get('value')); + var relation = this.getParent().fields.mailing_model.get('value')[0]; + var ds = new instance.web.DataSetStatic(self, relation, self.build_context()); + ds.call('search_count', [domain]).then(function (results) { + $('.oe_domain_count', self.$el).text(results + ' records selected'); + $('button span', self.$el).text(' Change selection'); + }); + } else { + $('.oe_domain_count', this.$el).text('0 record selected'); + $('button span', this.$el).text(' Select records'); + }; + }, + on_click: function(ev) { + var self = this; + 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.select_element( + model, {title: 'Select records...'}, + [], this.build_context()); + this.pop.on("elements_selected", self, function() { + var self2 = this; + var search_data = this.pop.searchview.build_search_data() + instance.web.pyeval.eval_domains_and_contexts({ + domains: search_data.domains, + contexts: search_data.contexts, + group_by_seq: search_data.groupbys || [] + }).then(function (results) { + // if selected IDS change domain + var domain = self2.pop.dataset.domain.concat(results.domain || []); + self.set_value(JSON.stringify(domain)) + }); + }); + 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( + {'char_domain': 'instance.web.form.CharDomainButton'} + ); + }; diff --git a/addons/mass_mailing/static/src/xml/mass_mailing.xml b/addons/mass_mailing/static/src/xml/mass_mailing.xml new file mode 100644 index 00000000000..2b20e450113 --- /dev/null +++ b/addons/mass_mailing/static/src/xml/mass_mailing.xml @@ -0,0 +1,13 @@ + + diff --git a/addons/mass_mailing/views/email_template.xml b/addons/mass_mailing/views/email_template.xml index 438852609bb..cde5685abaf 100644 --- a/addons/mass_mailing/views/email_template.xml +++ b/addons/mass_mailing/views/email_template.xml @@ -3,17 +3,6 @@ - - email.template.form.mass.mailing - email.template - - - - - - - - email.template.form.minimal email.template @@ -29,7 +18,6 @@ domain="[('model', 'in', ['res.partner', 'mail.mass_mailing.contact'])]"/> -
@@ -37,8 +25,8 @@ type="action" target="new" context="{'template_id':active_id}"/>
- +
@@ -53,19 +41,6 @@
- - email.template.search.mass.mailing - email.template - - - - - - - - - email.template.kanban email.template @@ -78,9 +53,6 @@
i