diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py index ac9eccb3844..2ab0b42e3a7 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', diff --git a/addons/mass_mailing/models/__init__.py b/addons/mass_mailing/models/__init__.py index bc5755baddd..f1809e54a4f 100644 --- a/addons/mass_mailing/models/__init__.py +++ b/addons/mass_mailing/models/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import mass_mailing +import mass_mailing_stats import mail_mail import mail_thread import email_template diff --git a/addons/mass_mailing/models/email_template.py b/addons/mass_mailing/models/email_template.py index 0403c857528..8b145ba0069 100644 --- a/addons/mass_mailing/models/email_template.py +++ b/addons/mass_mailing/models/email_template.py @@ -8,11 +8,9 @@ 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) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 24870ac8408..5dbbc584785 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,191 +37,57 @@ 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), } - 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), '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'), + 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this 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] + 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 + } 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 - - 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) - + _order = 'name' + _description = 'Mailing List' _columns = { - 'name': fields.char('Name', required=True), - 'contact_nbr': fields.function( - _get_contact_nbr, type='integer', - string='Contact Number', - ), - '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'), + 'name': fields.char('Mailing List', required=True), } - 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'), } - _defaults = { 'sequence': 0, } @@ -234,29 +97,6 @@ class MassMailingCampaign(osv.Model): """Model of mass mailing campaigns. """ _name = "mail.mass_mailing.campaign" _description = 'Mass Mailing Campaign' - - def _get_statistics(self, cr, uid, ids, name, arg, context=None): - """ Compute statistics of the mass mailing campaign """ - Statistics = self.pool['mail.mail.statistics'] - results = dict.fromkeys(ids, False) - for cid in ids: - stat_ids = Statistics.search(cr, uid, [('mass_mailing_campaign_id', '=', cid)], context=context) - stats = Statistics.browse(cr, uid, stat_ids, context=context) - results[cid] = { - 'total': len(stats), - 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]), - 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]), - 'sent': len([s for s in stats if not s.sent is False]), - 'opened': len([s for s in stats if not s.opened is False]), - 'replied': len([s for s in stats if not s.replied is False]), - 'bounced': len([s for s in stats if not s.bounced is False]), - } - results[cid]['delivered'] = results[cid]['sent'] - results[cid]['bounced'] - results[cid]['received_ratio'] = 100.0 * results[cid]['delivered'] / (results[cid]['sent'] or 1) - results[cid]['opened_ratio'] = 100.0 * results[cid]['opened'] / (results[cid]['sent'] or 1) - results[cid]['replied_ratio'] = 100.0 * results[cid]['replied'] / (results[cid]['sent'] or 1) - return results - _columns = { 'name': fields.char('Name', required=True), 'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True), @@ -264,201 +104,31 @@ 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', 'Categories'), 'mass_mailing_ids': fields.one2many( 'mail.mass_mailing', 'mass_mailing_campaign_id', 'Mass Mailings', ), - '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( - _get_statistics, string='Total', - type='integer', multi='_get_statistics' - ), - 'scheduled': fields.function( - _get_statistics, string='Scheduled', - type='integer', multi='_get_statistics' - ), - 'failed': fields.function( - _get_statistics, string='Failed', - type='integer', multi='_get_statistics' - ), - 'sent': fields.function( - _get_statistics, string='Sent Emails', - type='integer', multi='_get_statistics' - ), - 'delivered': fields.function( - _get_statistics, string='Delivered', - type='integer', multi='_get_statistics', - ), - 'opened': fields.function( - _get_statistics, string='Opened', - type='integer', multi='_get_statistics', - ), - 'replied': fields.function( - _get_statistics, string='Replied', - type='integer', multi='_get_statistics' - ), - 'bounced': fields.function( - _get_statistics, string='Bounced', - type='integer', multi='_get_statistics' - ), - 'received_ratio': fields.function( - _get_statistics, string='Received Ratio', - type='integer', multi='_get_statistics', - ), - 'opened_ratio': fields.function( - _get_statistics, string='Opened Ratio', - type='integer', multi='_get_statistics', - ), - 'replied_ratio': fields.function( - _get_statistics, string='Replied Ratio', - type='integer', multi='_get_statistics', - ), } 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. """ - 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. A mass mailing is an occurence of sending emails. """ - _name = 'mail.mass_mailing' _description = 'Mass Mailing' - # number of periods for tracking mail_mail statistics - _period_number = 6 - _order = 'date DESC' - - def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None): - """ Generic method to generate data for bar chart values using SparklineBarWidget. - This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). - - :param obj: the target model (i.e. crm_lead) - :param domain: the domain applied to the read_group - :param list read_fields: the list of fields to read in the read_group - :param str value_field: the field used to compute the value of the bar slice - :param str groupby_field: the fields used to group - - :return list section_result: a list of dicts: [ - { 'value': (int) bar_column_value, - 'tootip': (str) bar_column_tooltip, - } - ] - """ - date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date() - section_result = [{'value': 0, - 'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'), - } for i in range(0, self._period_number)] - group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) - field_col_info = obj._all_columns.get(groupby_field.split(':')[0]) - pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT - for group in group_obj: - group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date() - timedelta = relativedelta.relativedelta(group_begin_date, date_begin) - section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)} - return section_result - - def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None): - """ Get the daily statistics of the mass mailing. This is done by a grouping - on opened and replied fields. Using custom format in context, we obtain - results for the next 6 days following the mass mailing date. """ - obj = self.pool['mail.mail.statistics'] - res = {} - for id in ids: - res[id] = {} - date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT) - date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1) - date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) - date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) - domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)] - res[id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened:day', context=context)) - domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)] - res[id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied:day', context=context)) - return res - - def _get_statistics(self, cr, uid, ids, name, arg, context=None): - """ Compute statistics of the mass mailing campaign """ - Statistics = self.pool['mail.mail.statistics'] - results = dict.fromkeys(ids, False) - for mid in ids: - stat_ids = Statistics.search(cr, uid, [('mass_mailing_id', '=', mid)], context=context) - stats = Statistics.browse(cr, uid, stat_ids, context=context) - results[mid] = { - 'total': len(stats), - 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]), - 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]), - 'sent': len([s for s in stats if not s.sent is False]), - 'opened': len([s for s in stats if not s.opened is False]), - 'replied': len([s for s in stats if not s.replied is False]), - 'bounced': len([s for s in stats if not s.bounced is False]), - } - results[mid]['delivered'] = results[mid]['sent'] - results[mid]['bounced'] - results[mid]['received_ratio'] = 100.0 * results[mid]['delivered'] / (results[mid]['sent'] or 1) - results[mid]['opened_ratio'] = 100.0 * results[mid]['opened'] / (results[mid]['sent'] or 1) - results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['sent'] or 1) - return results - - 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'] = 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 + _order = 'id DESC' def _get_private_models(self, context=None): return ['res.partner', 'mail.mass_mailing.contact'] @@ -475,38 +145,26 @@ class MassMailing(osv.Model): ('mail.mass_mailing.contact', 'Contacts') ] - 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'), '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', 'Schedule'), ('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,88 +172,18 @@ 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(_get_mailing_model, string='Model', required=True), + 'mailing_domain': fields.char('Domain', required=True), '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', - 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', - 'Emails Statistics', - ), - 'total': fields.function( - _get_statistics, string='Total', - type='integer', multi='_get_statistics', - ), - 'scheduled': fields.function( - _get_statistics, string='Scheduled', - type='integer', multi='_get_statistics', - ), - 'failed': fields.function( - _get_statistics, string='Failed', - type='integer', multi='_get_statistics', - ), - 'sent': fields.function( - _get_statistics, string='Sent', - type='integer', multi='_get_statistics', - ), - 'delivered': fields.function( - _get_statistics, string='Delivered', - type='integer', multi='_get_statistics', - ), - 'opened': fields.function( - _get_statistics, string='Opened', - type='integer', multi='_get_statistics', - ), - 'replied': fields.function( - _get_statistics, string='Replied', - type='integer', multi='_get_statistics', - ), - 'bounced': fields.function( - _get_statistics, string='Bounced', - type='integer', multi='_get_statistics', - ), - 'received_ratio': fields.function( - _get_statistics, string='Received Ratio', - type='integer', multi='_get_statistics', - ), - 'opened_ratio': fields.function( - _get_statistics, string='Opened Ratio', - type='integer', multi='_get_statistics', - ), - 'replied_ratio': fields.function( - _get_statistics, string='Replied Ratio', - type='integer', multi='_get_statistics', - ), - # dayly ratio - 'opened_dayly': fields.function( - _get_daily_statistics, string='Opened', - type='char', multi='_get_daily_statistics', - oldname='opened_monthly', - ), - 'replied_dayly': fields.function( - _get_daily_statistics, string='Replied', - type='char', multi='_get_daily_statistics', - oldname='replied_monthly', - ), } _defaults = { @@ -611,42 +199,15 @@ class MassMailing(osv.Model): #------------------------------------------------------ def copy_data(self, cr, uid, id, default=None, context=None): - if default is None: - default = {} + default = default or {} mailing = self.browse(cr, uid, id, context=context) 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) - def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True): - """ 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) - read_group_all_states = [{ - '__context': {'group_by': groupby[1:]}, - '__domain': domain + [('state', '=', state_value)], - 'state': state_value, - 'state_count': 0, - } for state_value, state_name in states] - # Get standard results - read_group_res = super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) - # Update standard results with default results - result = [] - for state_value, state_name in states: - res = filter(lambda x: x['state'] == state_value, read_group_res) - if not res: - res = filter(lambda x: x['state'] == state_value, read_group_all_states) - res[0]['state'] = [state_value, state_name] - result.append(res[0]) - return result - else: - return super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) - #------------------------------------------------------ # Views & Actions #------------------------------------------------------ @@ -846,80 +407,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/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index 8bca6473828..7c0280f2c1f 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -12,17 +12,17 @@ - + mail.mass_mailing.contact.search mail.mass_mailing.contact - + - mail.mass_mailing.contact 10 - + @@ -46,58 +46,26 @@ - - mail.mass_mailing.contact.form - mail.mass_mailing.contact - -
- - - - - - - - -
-
-
- - Mass Mailing Contacts + Mailing List Subscribers mail.mass_mailing.contact form - tree,form + tree {'search_default_not_opt_out': 1} - - - - - + mail.mass_mailing.list.search mail.mass_mailing.list - - - - + + + @@ -106,39 +74,40 @@ mail.mass_mailing.list 10 - + - + + Recipients + mail.mass_mailing.contact + form + tree + {'search_default_list_id': active_id} + +

+ Click to create a recipient. +

+
+
+ mail.mass_mailing.list.form mail.mass_mailing.list
-
-
+ +
+
- -
@@ -150,15 +119,17 @@ mail.mass_mailing.list form tree,form - -

- Click here to create a new mailing list. -

- Mailing lists allows you to to manage customers and contacts easily and to send to mailings in a single click. -

+ +

+ Click here to create a new mailing list. +

+ Mailing lists allows you to to manage customers and + contacts easily and to send to mailings in a single click. +

+
- @@ -170,15 +141,12 @@ - - @@ -197,7 +165,6 @@ - @@ -208,20 +175,21 @@
+
-

emails are in queue and will be sent soon.

+ @@ -512,7 +480,6 @@ - diff --git a/addons/mass_mailing/wizard/__init__.py b/addons/mass_mailing/wizard/__init__.py index 6a8e9b98acb..aa8074b7488 100644 --- a/addons/mass_mailing/wizard/__init__.py +++ b/addons/mass_mailing/wizard/__init__.py @@ -2,4 +2,3 @@ import test_mailing import mail_compose_message -import mailing_list_confirm diff --git a/addons/mass_mailing/wizard/mailing_list_confirm.py b/addons/mass_mailing/wizard/mailing_list_confirm.py deleted file mode 100644 index de9fa103cb2..00000000000 --- a/addons/mass_mailing/wizard/mailing_list_confirm.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- - -from openerp.osv import osv, fields -from openerp.tools.translate import _ - - -class MailingListConfirm(osv.TransientModel): - """A wizard that acts as a confirmation when creating a new mailing list coming - from a list view. This allows to have a lightweight flow to create mailing - lists without having to go through multiple screens.""" - - _inherit = 'mail.mass_mailing.list' - _name = 'mail.mass_mailing.list.confirm' - - _columns = { - 'mass_mailing_id': fields.many2one('mail.mass_mailing', 'Mailing'), - } - - def create(self, cr, uid, values, context=None): - if context is None: - context = {} - context.update(no_contact_to_list=True) - return super(MailingListConfirm, self).create(cr, uid, values, context=context) - - def action_confirm(self, cr, uid, ids, context=None): - wizard = self.browse(cr, uid, ids[0], context=context) - mailing_list_id = self.pool['mail.mass_mailing.list'].create( - cr, uid, {'name': wizard.name, 'model': wizard.model}, context=context) - res = self.pool['mail.mass_mailing.list'].action_add_to_mailing(cr, uid, [mailing_list_id], context=context) - if not res: - return { - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.mass_mailing.list', - 'res_id': mailing_list_id, - 'context': context, - } - return res - - 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'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': wizard.model, - 'context': ctx, - 'domain': domain, - } diff --git a/addons/mass_mailing/wizard/mailing_list_confirm.xml b/addons/mass_mailing/wizard/mailing_list_confirm.xml deleted file mode 100644 index 1713fe0fe35..00000000000 --- a/addons/mass_mailing/wizard/mailing_list_confirm.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - mail.mass_mailing.list.confirm.form - mail.mass_mailing.list.confirm - - - - - -
- Adding contacts - to the mailing . -
- -
-
-
- -
-
- - - mail.mass_mailing.list.confirm.form - mail.mass_mailing.list.confirm - 32 - -
-
-
- - - - - - -
-
-
- - - Create a Mailing List - mail.mass_mailing.list.confirm - form - form - new - - - - Create a Mailing List - mail.mass_mailing.list.confirm - form - form - - new - - -
-