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
'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'
-
- 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)
- mlc = self.pool.get('mail.mass_mailing.contact').
- result = dict(lambda x: (x,0), ids)
- for m in mlc.read_group(cr, uid, [('list_id','in',ids)], ['list_id'], ['list_id'], context=context):
- result[m['list_id']] = m['__count']
- return results
-
_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),
- 'contact_nbr': fields.function(
- _get_contact_nbr, type='integer',
- string='Contact Number',
- ),
}
- 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
-
- # TODO: remove this?
- 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,
- }
-
- # TODO: remove this?
- 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,
- }
-
class MassMailingStage(osv.Model):
"""Stage for mass mailing campaigns. """
"""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),
'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):
_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
-
- # 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_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
-
- # To improve
- 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
-
def _get_private_models(self, context=None):
return ['res.partner', 'mail.mass_mailing.contact']
('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)]",
- ),
-
- 'state': fields.selection(
- [('draft', 'Schedule'), ('test', 'Tested'), ('done', 'Sent')], 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'
-
-
- # TODO: to remove
- 'ab_testing': fields.related(
- 'mass_mailing_campaign_id', 'ab_testing',
- type='boolean', string='AB Testing'
- ),
- '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'
++ '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(
_get_auto_reply_to_available,
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'
- ),
- # 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',
+ '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 = {
#------------------------------------------------------
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',
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
-# Merge this on emails
-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