123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- # -*- coding: utf-8 -*-
- from datetime import datetime
- from dateutil import relativedelta
- import json
- import random
- 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.tools import ustr
- from openerp.osv import osv, fields
- 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 MassMailingList(osv.Model):
- """Model of a contact list. """
- _name = 'mail.mass_mailing.list'
- _order = 'name'
- _description = 'Mailing List'
- def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
- 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('Mailing List', required=True),
- 'contact_nbr': fields.function(
- _get_contact_nbr, type='integer',
- string='Number of Contacts',
- ),
- }
- 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
- base."""
- _name = 'mail.mass_mailing.contact'
- _inherit = 'mail.thread'
- _description = 'Mass Mailing Contact'
- _order = 'email'
- _rec_name = 'email'
- _columns = {
- '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',
- ondelete='cascade', required=True,
- ),
- '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 get_name_email(self, name, context):
- 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
- return name, email
- def name_create(self, cr, uid, name, context=None):
- name, email = self.get_name_email(name, context=context)
- rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
- return self.name_get(cr, uid, [rec_id], context)[0]
- def add_to_list(self, cr, uid, name, list_id, context=None):
- name, email = self.get_name_email(name, context=context)
- rec_id = self.create(cr, uid, {'name': name, 'email': email, 'list_id': list_id}, context=context)
- return self.name_get(cr, uid, [rec_id], context)[0]
- def message_get_default_recipients(self, cr, uid, ids, context=None):
- res = {}
- for record in self.browse(cr, uid, ids, context=context):
- res[record.id] = {'partner_ids': [], 'email_to': record.email, 'email_cc': False}
- return res
- class MassMailingStage(osv.Model):
- """Stage for mass mailing campaigns. """
- _name = 'mail.mass_mailing.stage'
- _description = 'Mass Mailing Campaign Stage'
- _order = 'sequence'
- _columns = {
- 'name': fields.char('Name', required=True, translate=True),
- 'sequence': fields.integer('Sequence'),
- }
- _defaults = {
- 'sequence': 0,
- }
- 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 """
- results = {}
- cr.execute("""
- SELECT
- c.id as campaign_id,
- COUNT(s.id) AS total,
- COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
- COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
- COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
- COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
- COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
- FROM
- mail_mail_statistics s
- RIGHT JOIN
- mail_mass_mailing_campaign c
- ON (c.id = s.mass_mailing_campaign_id)
- WHERE
- c.id IN %s
- GROUP BY
- c.id
- """, (tuple(ids), ))
- for row in cr.dictfetchall():
- results[row.pop('campaign_id')] = row
- total = row['total'] or 1
- row['delivered'] = row['sent'] - row['bounced']
- row['received_ratio'] = 100.0 * row['delivered'] / total
- row['opened_ratio'] = 100.0 * row['opened'] / total
- row['replied_ratio'] = 100.0 * row['replied'] / total
- return results
- _columns = {
- 'name': fields.char('Name', required=True),
- 'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True),
- 'user_id': fields.many2one(
- 'res.users', 'Responsible',
- required=True,
- ),
- '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',
- ),
- 'unique_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] or False
- _defaults = {
- 'user_id': lambda self, cr, uid, ctx=None: uid,
- 'stage_id': lambda self, *args: self._get_default_stage_id(*args),
- }
- def get_recipients(self, cr, uid, ids, model=None, context=None):
- """Return the recipients 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 = 'sent_date DESC'
- def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, date_begin, context=None):
- """ Generic method to generate data for bar chart values using SparklineBarWidget.
- 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 = date_begin.date()
- section_result = [{'value': 0,
- 'tooltip': ustr((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 = obj._fields.get(groupby_field.split(':')[0])
- pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field.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 mailing in self.browse(cr, uid, ids, context=context):
- res[mailing.id] = {}
- date = mailing.sent_date if mailing.sent_date else mailing.create_date
- date_begin = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
- date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
- date_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', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
- res[mailing.id]['opened_daily'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
- domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
- res[mailing.id]['replied_daily'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context))
- return res
- def _get_statistics(self, cr, uid, ids, name, arg, context=None):
- """ Compute statistics of the mass mailing """
- results = {}
- cr.execute("""
- SELECT
- m.id as mailing_id,
- COUNT(s.id) AS total,
- COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
- COUNT(CASE WHEN s.sent is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
- COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
- COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied,
- COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
- FROM
- mail_mail_statistics s
- RIGHT JOIN
- mail_mass_mailing m
- ON (m.id = s.mass_mailing_id)
- WHERE
- m.id IN %s
- GROUP BY
- m.id
- """, (tuple(ids), ))
- for row in cr.dictfetchall():
- results[row.pop('mailing_id')] = row
- total = row['total'] or 1
- row['received_ratio'] = 100.0 * row['delivered'] / total
- row['opened_ratio'] = 100.0 * row['opened'] / total
- row['replied_ratio'] = 100.0 * row['replied'] / total
- return results
- def _get_mailing_model(self, cr, uid, context=None):
- res = []
- for model_name in self.pool:
- model = self.pool[model_name]
- if hasattr(model, '_mail_mass_mailing') and getattr(model, '_mail_mass_mailing'):
- res.append((model._name, getattr(model, '_mail_mass_mailing')))
- res.append(('mail.mass_mailing.contact', _('Mailing List')))
- return res
- # indirections for inheritance
- _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs)
- _columns = {
- 'name': fields.char('Subject', required=True),
- 'email_from': fields.char('From', required=True),
- 'create_date': fields.datetime('Creation Date'),
- 'sent_date': fields.datetime('Sent Date', oldname='date', copy=False),
- 'body_html': fields.html('Body'),
- 'attachment_ids': fields.many2many(
- 'ir.attachment', 'mass_mailing_ir_attachments_rel',
- 'mass_mailing_id', 'attachment_id', 'Attachments'
- ),
- 'mass_mailing_campaign_id': fields.many2one(
- 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
- ondelete='set null',
- ),
- 'state': fields.selection(
- [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')],
- string='Status', required=True, copy=False,
- ),
- 'color': fields.related(
- 'mass_mailing_campaign_id', 'color',
- type='integer', string='Color Index',
- ),
- # mailing options
- 'reply_to_mode': fields.selection(
- [('thread', 'In Document'), ('email', 'Specified Email Address')],
- string='Reply-To Mode', required=True,
- ),
- 'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'),
- # recipients
- 'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
- 'mailing_domain': fields.char('Domain', oldname='domain'),
- 'contact_list_ids': fields.many2many(
- 'mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
- string='Mailing Lists',
- ),
- 'contact_ab_pc': fields.integer(
- 'AB Testing percentage',
- help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
- ),
- # 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',
- ),
- # daily ratio
- 'opened_daily': fields.function(
- _get_daily_statistics, string='Opened',
- type='char', multi='_get_daily_statistics',
- ),
- 'replied_daily': fields.function(
- _get_daily_statistics, string='Replied',
- type='char', multi='_get_daily_statistics',
- )
- }
- def default_get(self, cr, uid, fields, context=None):
- res = super(MassMailing, self).default_get(cr, uid, fields, context=context)
- if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model'):
- if res['mailing_model'] in ['res.partner', 'mail.mass_mailing.contact']:
- res['reply_to_mode'] = 'email'
- else:
- res['reply_to_mode'] = 'thread'
- return res
- _defaults = {
- 'state': 'draft',
- 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
- 'reply_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
- 'mailing_model': 'mail.mass_mailing.contact',
- 'contact_ab_pc': 100,
- 'mailing_domain': [],
- }
- #------------------------------------------------------
- # Technical stuff
- #------------------------------------------------------
- def copy_data(self, cr, uid, id, default=None, context=None):
- mailing = self.browse(cr, uid, id, context=context)
- default = dict(default or {},
- name=_('%s (copy)') % 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)
- states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')]
- 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
- #------------------------------------------------------
- def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None):
- value = {}
- if mailing_model == 'mail.mass_mailing.contact':
- mailing_list_ids = set()
- for item in list_ids:
- if isinstance(item, (int, long)):
- mailing_list_ids.add(item)
- elif len(item) == 3:
- mailing_list_ids |= set(item[2])
- if mailing_list_ids:
- value['mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(mailing_list_ids)
- else:
- value['mailing_domain'] = "[('list_id', '=', False)]"
- elif mailing_model in ['res.partner']:
- value['mailing_domain'] = "[('opt_out', '=', False)]"
- else:
- value['mailing_domain'] = []
- return {'value': value}
- def action_duplicate(self, cr, uid, ids, context=None):
- copy_id = None
- for mid in ids:
- copy_id = self.copy(cr, uid, mid, context=context)
- if copy_id:
- return {
- 'type': 'ir.actions.act_window',
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'mail.mass_mailing',
- 'res_id': copy_id,
- 'context': context,
- }
- return False
- 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):
- if not len(ids) == 1:
- raise ValueError('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&template_model=%s&return_action=%d&enable_editor=1' % (ids[0], mail.mailing_model, context['params']['action'])
- return {
- 'name': _('Open with Visual Editor'),
- 'type': 'ir.actions.act_url',
- 'url': url,
- 'target': 'self',
- }
- #------------------------------------------------------
- # Email Sending
- #------------------------------------------------------
- def get_recipients(self, cr, uid, mailing, context=None):
- if mailing.mailing_domain:
- domain = eval(mailing.mailing_domain)
- res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
- else:
- res_ids = []
- domain = [('id', 'in', res_ids)]
- # randomly choose a fragment
- if mailing.contact_ab_pc < 100:
- contact_nbr = self.pool[mailing.mailing_model].search(cr, uid, domain, count=True, context=context)
- topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
- if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
- already_mailed = self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id]
- else:
- already_mailed = set([])
- remaining = set(res_ids).difference(already_mailed)
- if topick > len(remaining):
- topick = len(remaining)
- res_ids = random.sample(remaining, topick)
- return res_ids
- def send_mail(self, cr, uid, ids, context=None):
- author_id = self.pool['res.users'].browse(cr, uid, uid, context=context).partner_id.id
- for mailing in self.browse(cr, uid, ids, context=context):
- # instantiate an email composer + send emails
- res_ids = self.get_recipients(cr, uid, mailing, context=context)
- if not res_ids:
- raise Warning('Please select recipients.')
- comp_ctx = dict(context, active_ids=res_ids)
- composer_values = {
- 'author_id': author_id,
- 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
- 'body': mailing.body_html,
- 'subject': mailing.name,
- 'model': mailing.mailing_model,
- 'email_from': mailing.email_from,
- 'record_name': False,
- 'composition_mode': 'mass_mail',
- 'mass_mailing_id': mailing.id,
- 'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
- 'no_auto_thread': mailing.reply_to_mode != 'thread',
- }
- if mailing.reply_to_mode == 'email':
- composer_values['reply_to'] = mailing.reply_to
- composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx)
- self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx)
- self.write(cr, uid, [mailing.id], {'sent_date': fields.datetime.now(), 'state': 'done'}, context=context)
- return True
|