mass_mailing.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. # -*- coding: utf-8 -*-
  2. from datetime import datetime
  3. from dateutil import relativedelta
  4. import json
  5. import random
  6. from openerp import tools
  7. from openerp.exceptions import Warning
  8. from openerp.tools.safe_eval import safe_eval as eval
  9. from openerp.tools.translate import _
  10. from openerp.tools import ustr
  11. from openerp.osv import osv, fields
  12. class MassMailingCategory(osv.Model):
  13. """Model of categories of mass mailing, i.e. marketing, newsletter, ... """
  14. _name = 'mail.mass_mailing.category'
  15. _description = 'Mass Mailing Category'
  16. _order = 'name'
  17. _columns = {
  18. 'name': fields.char('Name', required=True),
  19. }
  20. class MassMailingList(osv.Model):
  21. """Model of a contact list. """
  22. _name = 'mail.mass_mailing.list'
  23. _order = 'name'
  24. _description = 'Mailing List'
  25. def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
  26. result = dict.fromkeys(ids, 0)
  27. Contacts = self.pool.get('mail.mass_mailing.contact')
  28. for group in Contacts.read_group(cr, uid, [('list_id', 'in', ids), ('opt_out', '!=', True)], ['list_id'], ['list_id'], context=context):
  29. result[group['list_id'][0]] = group['list_id_count']
  30. return result
  31. _columns = {
  32. 'name': fields.char('Mailing List', required=True),
  33. 'contact_nbr': fields.function(
  34. _get_contact_nbr, type='integer',
  35. string='Number of Contacts',
  36. ),
  37. }
  38. class MassMailingContact(osv.Model):
  39. """Model of a contact. This model is different from the partner model
  40. because it holds only some basic information: name, email. The purpose is to
  41. be able to deal with large contact list to email without bloating the partner
  42. base."""
  43. _name = 'mail.mass_mailing.contact'
  44. _inherit = 'mail.thread'
  45. _description = 'Mass Mailing Contact'
  46. _order = 'email'
  47. _rec_name = 'email'
  48. _columns = {
  49. 'name': fields.char('Name'),
  50. 'email': fields.char('Email', required=True),
  51. 'create_date': fields.datetime('Create Date'),
  52. 'list_id': fields.many2one(
  53. 'mail.mass_mailing.list', string='Mailing List',
  54. ondelete='cascade', required=True,
  55. ),
  56. 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this list'),
  57. }
  58. def _get_latest_list(self, cr, uid, context={}):
  59. lid = self.pool.get('mail.mass_mailing.list').search(cr, uid, [], limit=1, order='id desc', context=context)
  60. return lid and lid[0] or False
  61. _defaults = {
  62. 'list_id': _get_latest_list
  63. }
  64. def get_name_email(self, name, context):
  65. name, email = self.pool['res.partner']._parse_partner_name(name, context=context)
  66. if name and not email:
  67. email = name
  68. if email and not name:
  69. name = email
  70. return name, email
  71. def name_create(self, cr, uid, name, context=None):
  72. name, email = self.get_name_email(name, context=context)
  73. rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
  74. return self.name_get(cr, uid, [rec_id], context)[0]
  75. def add_to_list(self, cr, uid, name, list_id, context=None):
  76. name, email = self.get_name_email(name, context=context)
  77. rec_id = self.create(cr, uid, {'name': name, 'email': email, 'list_id': list_id}, context=context)
  78. return self.name_get(cr, uid, [rec_id], context)[0]
  79. def message_get_default_recipients(self, cr, uid, ids, context=None):
  80. res = {}
  81. for record in self.browse(cr, uid, ids, context=context):
  82. res[record.id] = {'partner_ids': [], 'email_to': record.email, 'email_cc': False}
  83. return res
  84. class MassMailingStage(osv.Model):
  85. """Stage for mass mailing campaigns. """
  86. _name = 'mail.mass_mailing.stage'
  87. _description = 'Mass Mailing Campaign Stage'
  88. _order = 'sequence'
  89. _columns = {
  90. 'name': fields.char('Name', required=True, translate=True),
  91. 'sequence': fields.integer('Sequence'),
  92. }
  93. _defaults = {
  94. 'sequence': 0,
  95. }
  96. class MassMailingCampaign(osv.Model):
  97. """Model of mass mailing campaigns. """
  98. _name = "mail.mass_mailing.campaign"
  99. _description = 'Mass Mailing Campaign'
  100. def _get_statistics(self, cr, uid, ids, name, arg, context=None):
  101. """ Compute statistics of the mass mailing campaign """
  102. results = {}
  103. cr.execute("""
  104. SELECT
  105. c.id as campaign_id,
  106. COUNT(s.id) AS total,
  107. COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
  108. 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,
  109. 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,
  110. COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
  111. COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
  112. COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
  113. COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
  114. FROM
  115. mail_mail_statistics s
  116. RIGHT JOIN
  117. mail_mass_mailing_campaign c
  118. ON (c.id = s.mass_mailing_campaign_id)
  119. WHERE
  120. c.id IN %s
  121. GROUP BY
  122. c.id
  123. """, (tuple(ids), ))
  124. for row in cr.dictfetchall():
  125. results[row.pop('campaign_id')] = row
  126. total = row['total'] or 1
  127. row['delivered'] = row['sent'] - row['bounced']
  128. row['received_ratio'] = 100.0 * row['delivered'] / total
  129. row['opened_ratio'] = 100.0 * row['opened'] / total
  130. row['replied_ratio'] = 100.0 * row['replied'] / total
  131. return results
  132. _columns = {
  133. 'name': fields.char('Name', required=True),
  134. 'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True),
  135. 'user_id': fields.many2one(
  136. 'res.users', 'Responsible',
  137. required=True,
  138. ),
  139. 'category_ids': fields.many2many(
  140. 'mail.mass_mailing.category', 'mail_mass_mailing_category_rel',
  141. 'category_id', 'campaign_id', string='Categories'),
  142. 'mass_mailing_ids': fields.one2many(
  143. 'mail.mass_mailing', 'mass_mailing_campaign_id',
  144. 'Mass Mailings',
  145. ),
  146. 'unique_ab_testing': fields.boolean(
  147. 'AB Testing',
  148. help='If checked, recipients will be mailed only once, allowing to send'
  149. 'various mailings in a single campaign to test the effectiveness'
  150. 'of the mailings.'),
  151. 'color': fields.integer('Color Index'),
  152. # stat fields
  153. 'total': fields.function(
  154. _get_statistics, string='Total',
  155. type='integer', multi='_get_statistics'
  156. ),
  157. 'scheduled': fields.function(
  158. _get_statistics, string='Scheduled',
  159. type='integer', multi='_get_statistics'
  160. ),
  161. 'failed': fields.function(
  162. _get_statistics, string='Failed',
  163. type='integer', multi='_get_statistics'
  164. ),
  165. 'sent': fields.function(
  166. _get_statistics, string='Sent Emails',
  167. type='integer', multi='_get_statistics'
  168. ),
  169. 'delivered': fields.function(
  170. _get_statistics, string='Delivered',
  171. type='integer', multi='_get_statistics',
  172. ),
  173. 'opened': fields.function(
  174. _get_statistics, string='Opened',
  175. type='integer', multi='_get_statistics',
  176. ),
  177. 'replied': fields.function(
  178. _get_statistics, string='Replied',
  179. type='integer', multi='_get_statistics'
  180. ),
  181. 'bounced': fields.function(
  182. _get_statistics, string='Bounced',
  183. type='integer', multi='_get_statistics'
  184. ),
  185. 'received_ratio': fields.function(
  186. _get_statistics, string='Received Ratio',
  187. type='integer', multi='_get_statistics',
  188. ),
  189. 'opened_ratio': fields.function(
  190. _get_statistics, string='Opened Ratio',
  191. type='integer', multi='_get_statistics',
  192. ),
  193. 'replied_ratio': fields.function(
  194. _get_statistics, string='Replied Ratio',
  195. type='integer', multi='_get_statistics',
  196. ),
  197. }
  198. def _get_default_stage_id(self, cr, uid, context=None):
  199. stage_ids = self.pool['mail.mass_mailing.stage'].search(cr, uid, [], limit=1, context=context)
  200. return stage_ids and stage_ids[0] or False
  201. _defaults = {
  202. 'user_id': lambda self, cr, uid, ctx=None: uid,
  203. 'stage_id': lambda self, *args: self._get_default_stage_id(*args),
  204. }
  205. def get_recipients(self, cr, uid, ids, model=None, context=None):
  206. """Return the recipients of a mailing campaign. This is based on the statistics
  207. build for each mailing. """
  208. Statistics = self.pool['mail.mail.statistics']
  209. res = dict.fromkeys(ids, False)
  210. for cid in ids:
  211. domain = [('mass_mailing_campaign_id', '=', cid)]
  212. if model:
  213. domain += [('model', '=', model)]
  214. stat_ids = Statistics.search(cr, uid, domain, context=context)
  215. res[cid] = set(stat.res_id for stat in Statistics.browse(cr, uid, stat_ids, context=context))
  216. return res
  217. class MassMailing(osv.Model):
  218. """ MassMailing models a wave of emails for a mass mailign campaign.
  219. A mass mailing is an occurence of sending emails. """
  220. _name = 'mail.mass_mailing'
  221. _description = 'Mass Mailing'
  222. # number of periods for tracking mail_mail statistics
  223. _period_number = 6
  224. _order = 'sent_date DESC'
  225. def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, date_begin, context=None):
  226. """ Generic method to generate data for bar chart values using SparklineBarWidget.
  227. This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
  228. :param obj: the target model (i.e. crm_lead)
  229. :param domain: the domain applied to the read_group
  230. :param list read_fields: the list of fields to read in the read_group
  231. :param str value_field: the field used to compute the value of the bar slice
  232. :param str groupby_field: the fields used to group
  233. :return list section_result: a list of dicts: [
  234. { 'value': (int) bar_column_value,
  235. 'tootip': (str) bar_column_tooltip,
  236. }
  237. ]
  238. """
  239. date_begin = date_begin.date()
  240. section_result = [{'value': 0,
  241. 'tooltip': ustr((date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y')),
  242. } for i in range(0, self._period_number)]
  243. group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
  244. field = obj._fields.get(groupby_field.split(':')[0])
  245. pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field.type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
  246. for group in group_obj:
  247. group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
  248. timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
  249. section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
  250. return section_result
  251. def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
  252. """ Get the daily statistics of the mass mailing. This is done by a grouping
  253. on opened and replied fields. Using custom format in context, we obtain
  254. results for the next 6 days following the mass mailing date. """
  255. obj = self.pool['mail.mail.statistics']
  256. res = {}
  257. for mailing in self.browse(cr, uid, ids, context=context):
  258. res[mailing.id] = {}
  259. date = mailing.sent_date if mailing.sent_date else mailing.create_date
  260. date_begin = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
  261. date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
  262. date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
  263. date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
  264. domain = [('mass_mailing_id', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
  265. res[mailing.id]['opened_daily'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
  266. domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
  267. res[mailing.id]['replied_daily'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context))
  268. return res
  269. def _get_statistics(self, cr, uid, ids, name, arg, context=None):
  270. """ Compute statistics of the mass mailing """
  271. results = {}
  272. cr.execute("""
  273. SELECT
  274. m.id as mailing_id,
  275. COUNT(s.id) AS total,
  276. COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
  277. 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,
  278. 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,
  279. COUNT(CASE WHEN s.sent is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
  280. COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
  281. COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied,
  282. COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
  283. FROM
  284. mail_mail_statistics s
  285. RIGHT JOIN
  286. mail_mass_mailing m
  287. ON (m.id = s.mass_mailing_id)
  288. WHERE
  289. m.id IN %s
  290. GROUP BY
  291. m.id
  292. """, (tuple(ids), ))
  293. for row in cr.dictfetchall():
  294. results[row.pop('mailing_id')] = row
  295. total = row['total'] or 1
  296. row['received_ratio'] = 100.0 * row['delivered'] / total
  297. row['opened_ratio'] = 100.0 * row['opened'] / total
  298. row['replied_ratio'] = 100.0 * row['replied'] / total
  299. return results
  300. def _get_mailing_model(self, cr, uid, context=None):
  301. res = []
  302. for model_name in self.pool:
  303. model = self.pool[model_name]
  304. if hasattr(model, '_mail_mass_mailing') and getattr(model, '_mail_mass_mailing'):
  305. res.append((model._name, getattr(model, '_mail_mass_mailing')))
  306. res.append(('mail.mass_mailing.contact', _('Mailing List')))
  307. return res
  308. # indirections for inheritance
  309. _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs)
  310. _columns = {
  311. 'name': fields.char('Subject', required=True),
  312. 'email_from': fields.char('From', required=True),
  313. 'create_date': fields.datetime('Creation Date'),
  314. 'sent_date': fields.datetime('Sent Date', oldname='date', copy=False),
  315. 'body_html': fields.html('Body'),
  316. 'attachment_ids': fields.many2many(
  317. 'ir.attachment', 'mass_mailing_ir_attachments_rel',
  318. 'mass_mailing_id', 'attachment_id', 'Attachments'
  319. ),
  320. 'mass_mailing_campaign_id': fields.many2one(
  321. 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
  322. ondelete='set null',
  323. ),
  324. 'state': fields.selection(
  325. [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')],
  326. string='Status', required=True, copy=False,
  327. ),
  328. 'color': fields.related(
  329. 'mass_mailing_campaign_id', 'color',
  330. type='integer', string='Color Index',
  331. ),
  332. # mailing options
  333. 'reply_to_mode': fields.selection(
  334. [('thread', 'In Document'), ('email', 'Specified Email Address')],
  335. string='Reply-To Mode', required=True,
  336. ),
  337. 'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'),
  338. # recipients
  339. 'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
  340. 'mailing_domain': fields.char('Domain', oldname='domain'),
  341. 'contact_list_ids': fields.many2many(
  342. 'mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
  343. string='Mailing Lists',
  344. ),
  345. 'contact_ab_pc': fields.integer(
  346. 'AB Testing percentage',
  347. help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
  348. ),
  349. # statistics data
  350. 'statistics_ids': fields.one2many(
  351. 'mail.mail.statistics', 'mass_mailing_id',
  352. 'Emails Statistics',
  353. ),
  354. 'total': fields.function(
  355. _get_statistics, string='Total',
  356. type='integer', multi='_get_statistics',
  357. ),
  358. 'scheduled': fields.function(
  359. _get_statistics, string='Scheduled',
  360. type='integer', multi='_get_statistics',
  361. ),
  362. 'failed': fields.function(
  363. _get_statistics, string='Failed',
  364. type='integer', multi='_get_statistics',
  365. ),
  366. 'sent': fields.function(
  367. _get_statistics, string='Sent',
  368. type='integer', multi='_get_statistics',
  369. ),
  370. 'delivered': fields.function(
  371. _get_statistics, string='Delivered',
  372. type='integer', multi='_get_statistics',
  373. ),
  374. 'opened': fields.function(
  375. _get_statistics, string='Opened',
  376. type='integer', multi='_get_statistics',
  377. ),
  378. 'replied': fields.function(
  379. _get_statistics, string='Replied',
  380. type='integer', multi='_get_statistics',
  381. ),
  382. 'bounced': fields.function(
  383. _get_statistics, string='Bounced',
  384. type='integer', multi='_get_statistics',
  385. ),
  386. 'received_ratio': fields.function(
  387. _get_statistics, string='Received Ratio',
  388. type='integer', multi='_get_statistics',
  389. ),
  390. 'opened_ratio': fields.function(
  391. _get_statistics, string='Opened Ratio',
  392. type='integer', multi='_get_statistics',
  393. ),
  394. 'replied_ratio': fields.function(
  395. _get_statistics, string='Replied Ratio',
  396. type='integer', multi='_get_statistics',
  397. ),
  398. # daily ratio
  399. 'opened_daily': fields.function(
  400. _get_daily_statistics, string='Opened',
  401. type='char', multi='_get_daily_statistics',
  402. ),
  403. 'replied_daily': fields.function(
  404. _get_daily_statistics, string='Replied',
  405. type='char', multi='_get_daily_statistics',
  406. )
  407. }
  408. def default_get(self, cr, uid, fields, context=None):
  409. res = super(MassMailing, self).default_get(cr, uid, fields, context=context)
  410. if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model'):
  411. if res['mailing_model'] in ['res.partner', 'mail.mass_mailing.contact']:
  412. res['reply_to_mode'] = 'email'
  413. else:
  414. res['reply_to_mode'] = 'thread'
  415. return res
  416. _defaults = {
  417. 'state': 'draft',
  418. 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
  419. 'reply_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
  420. 'mailing_model': 'mail.mass_mailing.contact',
  421. 'contact_ab_pc': 100,
  422. 'mailing_domain': [],
  423. }
  424. #------------------------------------------------------
  425. # Technical stuff
  426. #------------------------------------------------------
  427. def copy_data(self, cr, uid, id, default=None, context=None):
  428. mailing = self.browse(cr, uid, id, context=context)
  429. default = dict(default or {},
  430. name=_('%s (copy)') % mailing.name)
  431. return super(MassMailing, self).copy_data(cr, uid, id, default, context=context)
  432. def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
  433. """ Override read_group to always display all states. """
  434. if groupby and groupby[0] == "state":
  435. # Default result structure
  436. # states = self._get_state_list(cr, uid, context=context)
  437. states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')]
  438. read_group_all_states = [{
  439. '__context': {'group_by': groupby[1:]},
  440. '__domain': domain + [('state', '=', state_value)],
  441. 'state': state_value,
  442. 'state_count': 0,
  443. } for state_value, state_name in states]
  444. # Get standard results
  445. read_group_res = super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
  446. # Update standard results with default results
  447. result = []
  448. for state_value, state_name in states:
  449. res = filter(lambda x: x['state'] == state_value, read_group_res)
  450. if not res:
  451. res = filter(lambda x: x['state'] == state_value, read_group_all_states)
  452. res[0]['state'] = [state_value, state_name]
  453. result.append(res[0])
  454. return result
  455. else:
  456. return super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
  457. #------------------------------------------------------
  458. # Views & Actions
  459. #------------------------------------------------------
  460. def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None):
  461. value = {}
  462. if mailing_model == 'mail.mass_mailing.contact':
  463. mailing_list_ids = set()
  464. for item in list_ids:
  465. if isinstance(item, (int, long)):
  466. mailing_list_ids.add(item)
  467. elif len(item) == 3:
  468. mailing_list_ids |= set(item[2])
  469. if mailing_list_ids:
  470. value['mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(mailing_list_ids)
  471. else:
  472. value['mailing_domain'] = "[('list_id', '=', False)]"
  473. elif mailing_model in ['res.partner']:
  474. value['mailing_domain'] = "[('opt_out', '=', False)]"
  475. else:
  476. value['mailing_domain'] = []
  477. return {'value': value}
  478. def action_duplicate(self, cr, uid, ids, context=None):
  479. copy_id = None
  480. for mid in ids:
  481. copy_id = self.copy(cr, uid, mid, context=context)
  482. if copy_id:
  483. return {
  484. 'type': 'ir.actions.act_window',
  485. 'view_type': 'form',
  486. 'view_mode': 'form',
  487. 'res_model': 'mail.mass_mailing',
  488. 'res_id': copy_id,
  489. 'context': context,
  490. }
  491. return False
  492. def action_test_mailing(self, cr, uid, ids, context=None):
  493. ctx = dict(context, default_mass_mailing_id=ids[0])
  494. return {
  495. 'name': _('Test Mailing'),
  496. 'type': 'ir.actions.act_window',
  497. 'view_mode': 'form',
  498. 'res_model': 'mail.mass_mailing.test',
  499. 'target': 'new',
  500. 'context': ctx,
  501. }
  502. def action_edit_html(self, cr, uid, ids, context=None):
  503. if not len(ids) == 1:
  504. raise ValueError('One and only one ID allowed for this action')
  505. mail = self.browse(cr, uid, ids[0], context=context)
  506. 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'])
  507. return {
  508. 'name': _('Open with Visual Editor'),
  509. 'type': 'ir.actions.act_url',
  510. 'url': url,
  511. 'target': 'self',
  512. }
  513. #------------------------------------------------------
  514. # Email Sending
  515. #------------------------------------------------------
  516. def get_recipients(self, cr, uid, mailing, context=None):
  517. if mailing.mailing_domain:
  518. domain = eval(mailing.mailing_domain)
  519. res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
  520. else:
  521. res_ids = []
  522. domain = [('id', 'in', res_ids)]
  523. # randomly choose a fragment
  524. if mailing.contact_ab_pc < 100:
  525. contact_nbr = self.pool[mailing.mailing_model].search(cr, uid, domain, count=True, context=context)
  526. topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
  527. if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
  528. 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]
  529. else:
  530. already_mailed = set([])
  531. remaining = set(res_ids).difference(already_mailed)
  532. if topick > len(remaining):
  533. topick = len(remaining)
  534. res_ids = random.sample(remaining, topick)
  535. return res_ids
  536. def send_mail(self, cr, uid, ids, context=None):
  537. author_id = self.pool['res.users'].browse(cr, uid, uid, context=context).partner_id.id
  538. for mailing in self.browse(cr, uid, ids, context=context):
  539. # instantiate an email composer + send emails
  540. res_ids = self.get_recipients(cr, uid, mailing, context=context)
  541. if not res_ids:
  542. raise Warning('Please select recipients.')
  543. comp_ctx = dict(context, active_ids=res_ids)
  544. composer_values = {
  545. 'author_id': author_id,
  546. 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
  547. 'body': mailing.body_html,
  548. 'subject': mailing.name,
  549. 'model': mailing.mailing_model,
  550. 'email_from': mailing.email_from,
  551. 'record_name': False,
  552. 'composition_mode': 'mass_mail',
  553. 'mass_mailing_id': mailing.id,
  554. 'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
  555. 'no_auto_thread': mailing.reply_to_mode != 'thread',
  556. }
  557. if mailing.reply_to_mode == 'email':
  558. composer_values['reply_to'] = mailing.reply_to
  559. composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx)
  560. self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx)
  561. self.write(cr, uid, [mailing.id], {'sent_date': fields.datetime.now(), 'state': 'done'}, context=context)
  562. return True