Browse Source

[ADD] mass_mailing_sending_queue addon

pull/85/head
Antonio Espinosa 8 years ago
parent
commit
29fae520a4
  1. 93
      mass_mailing_sending_queue/README.rst
  2. 4
      mass_mailing_sending_queue/__init__.py
  3. 24
      mass_mailing_sending_queue/__openerp__.py
  4. 15
      mass_mailing_sending_queue/data/ir_config_parameter.xml
  5. 19
      mass_mailing_sending_queue/data/ir_cron.xml
  6. 156
      mass_mailing_sending_queue/i18n/es.po
  7. 8
      mass_mailing_sending_queue/models/__init__.py
  8. 20
      mass_mailing_sending_queue/models/mail_mail.py
  9. 20
      mass_mailing_sending_queue/models/mail_mail_statistics.py
  10. 103
      mass_mailing_sending_queue/models/mail_mass_mailing.py
  11. 190
      mass_mailing_sending_queue/models/mail_mass_mailing_sending.py
  12. 3
      mass_mailing_sending_queue/security/ir.model.access.csv
  13. BIN
      mass_mailing_sending_queue/static/description/icon.png
  14. 5
      mass_mailing_sending_queue/tests/__init__.py
  15. 179
      mass_mailing_sending_queue/tests/test_mass_mailing_sending.py
  16. 81
      mass_mailing_sending_queue/views/mass_mailing_sending_view.xml
  17. 48
      mass_mailing_sending_queue/views/mass_mailing_view.xml

93
mass_mailing_sending_queue/README.rst

@ -0,0 +1,93 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
==========================
Mass mailing sending queue
==========================
This module adds a queue for generating mail records when mass mailing
'Send to All' button is clicked. This is an additional queue, apart from
the existing one (implemented in addons/mail) for doing the actual sending.
Configuration
=============
There is a system parameter, 'mail.mass_mailing.sending.batch_size'
(default value is 500), to control how many emails are created in each
cron iteration (method 'mail.mass_mailing.sending.cron()').
Usage
=====
Without this module, when 'Send to All' button is clicked at mass mailing form,
all 'mail.mail' and 'mail.mail.statistics' objects are created. This process
might take a long time if the recipient list is 10k+ and the famous
"Take a minute to get a coffee, because it's loading..." text might appear.
With this new queue, mass mailing will appear in 'Sending' state to the user
until all emails are sent or failed. After 'Send to All' button is clicked,
the user will quickly land to the mass mailing form.
In 'Mass mailing' form, a new tab "Sending tasks" has been added where the
user can check the Sent mails history.
In 'Settings > Technical > Email > Mass mailing sending' allowed users can
track all running mass mailing sending objects and see:
* Pending recipients: Number of recipients for which the email is not yet created.
* Start date: Date when user press 'Send to All' button.
* Mails to be sent: number of emails waiting to be sent.
* Sent mails: number of emails successfully sent.
* Failed mails: number of unsent emails due to error.
NOTE: User will not be able to send the same mass mailing again if another
one is ongoing. An UserError exception is raised in this case.
NOTE: If number of recipients are less than 'batch_size / 2', then all
emails are created when 'Send to All' button is clicked (standard way).
Although a sending object is created anyway in order to be consistent.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/205/8.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/social/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Antonio Espinosa <antonio.espinosa@tecnativa.com>
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

4
mass_mailing_sending_queue/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

24
mass_mailing_sending_queue/__openerp__.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Mass mailing sending queue",
"summary": "A new queue for sending mass mailing",
"version": "8.0.1.0.0",
"category": "Marketing",
"website": "https://odoo-community.org/",
"author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
"mass_mailing",
],
"data": [
"data/ir_config_parameter.xml",
"data/ir_cron.xml",
"security/ir.model.access.csv",
"views/mass_mailing_sending_view.xml",
"views/mass_mailing_view.xml",
],
}

15
mass_mailing_sending_queue/data/ir_config_parameter.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data noupdate="1">
<record id="sending_batch_size" model="ir.config_parameter" forcecreate="True">
<field name="key">mail.mass_mailing.sending.batch_size</field>
<field name="value">500</field>
<field name="group_ids" eval="[(6, False, [ref('base.group_system')])]" />
</record>
</data>
</openerp>

19
mass_mailing_sending_queue/data/ir_cron.xml

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data noupdate="1">
<record id="cronjob" model="ir.cron">
<field name="name">Mass mailing sending queue</field>
<field name="interval_type">minutes</field>
<field name="interval_number">1</field>
<field name="numbercall">-1</field>
<field name="model">mail.mass_mailing.sending</field>
<field name="function">cron</field>
<field name="nextcall">2016-01-01</field>
</record>
</data>
</openerp>

156
mass_mailing_sending_queue/i18n/es.po

@ -0,0 +1,156 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mass_mailing_sending_queue
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-08 13:56+0000\n"
"PO-Revision-Date: 2016-09-08 13:56+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,create_uid:0
msgid "Created by"
msgstr "Creado por"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,create_date:0
msgid "Created on"
msgstr "Creado en"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,display_name:0
msgid "Display Name"
msgstr "Nombre mostrado"
#. module: mass_mailing_sending_queue
#: model:ir.model,name:mass_mailing_sending_queue.model_mail_mail_statistics
msgid "Email Statistics"
msgstr "Estadísticas de correo electrónico"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,failed:0
msgid "Emails failed"
msgstr "Correos electrónicos fallidos"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,sending:0
msgid "Emails sending"
msgstr "Emails enviándose"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,sent:0
msgid "Emails sent"
msgstr "Emails enviados"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
#: selection:mail.mass_mailing.sending,state:0
msgid "Enqueued"
msgstr "En cola"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
msgid "Group By"
msgstr "Agrupar por"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,id:0
msgid "ID"
msgstr "ID"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,__last_update:0
msgid "Last Modified on"
msgstr "Última modficación en"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,write_uid:0
msgid "Last Updated by"
msgstr "Última modficación por"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,write_date:0
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: mass_mailing_sending_queue
#: model:ir.model,name:mass_mailing_sending_queue.model_mail_mass_mailing
msgid "Mass Mailing"
msgstr "Envío masivo"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
#: field:mail.mass_mailing.sending,mass_mailing_id:0
msgid "Mass mailing"
msgstr "Envío masivo"
#. module: mass_mailing_sending_queue
#: model:ir.ui.menu,name:mass_mailing_sending_queue.menu_mass_mailing_sending
#: field:mail.mail.statistics,mass_mailing_sending_id:0
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_form
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
msgid "Mass mailing sending"
msgstr "Envío de correo masivo"
#. module: mass_mailing_sending_queue
#: model:ir.actions.act_window,name:mass_mailing_sending_queue.action_view_mail_mass_mailing_sending
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_tree
msgid "Mass mailing sendings"
msgstr "Envíos de correo masivo"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing,pending:0
msgid "Pending"
msgstr "Pendiente"
#. module: mass_mailing_sending_queue
#: code:addons/mass_mailing_sending_queue/models/mail_mass_mailing.py:23
#: code:addons/mass_mailing_sending_queue/models/mail_mass_mailing_sending.py:67
#, python-format
msgid "Please select recipients."
msgstr "Por favor, seleccione algún destinatario"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,pending:0
msgid "Recipients pending"
msgstr "Destinatarios pendientes"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
msgid "Running"
msgstr "Ejecutándose"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
#: selection:mail.mass_mailing.sending,state:0
msgid "Sending"
msgstr "Enviando"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing.sending:mass_mailing_sending_queue.view_mail_mass_mailing_sending_search
#: selection:mail.mass_mailing.sending,state:0
msgid "Sent"
msgstr "Enviado"
#. module: mass_mailing_sending_queue
#: field:mail.mass_mailing.sending,state:0
msgid "State"
msgstr "Estado"
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing:mass_mailing_sending_queue.view_mail_mass_mailing_form
msgid "recipients pending for creating email."
msgstr "destinatarios pendientes de crear el correo electrónico."
#. module: mass_mailing_sending_queue
#: view:mail.mass_mailing:mass_mailing_sending_queue.view_mail_mass_mailing_form
msgid "{'invisible': [('scheduled', '=', 0), ('pending', '=', 0)]}"
msgstr "{'invisible': [('scheduled', '=', 0), ('pending', '=', 0)]}"

8
mass_mailing_sending_queue/models/__init__.py

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import mail_mail_statistics
from . import mail_mass_mailing
from . import mail_mass_mailing_sending
from . import mail_mail

20
mass_mailing_sending_queue/models/mail_mail.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, api
class MailMail(models.Model):
_inherit = 'mail.mail'
@api.model
def _postprocess_sent_message(self, mail, mail_sent=True):
# Read before super, because mail will be removed if sent successfully
stats = mail.statistics_ids
res = super(MailMail, self)._postprocess_sent_message(
mail, mail_sent=mail_sent)
for stat in stats.filtered(
lambda r: r.mass_mailing_sending_id.state == 'sending'):
stat.mass_mailing_sending_id._process_sending()
return res

20
mass_mailing_sending_queue/models/mail_mail_statistics.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
class MailMailStatistics(models.Model):
_inherit = 'mail.mail.statistics'
mass_mailing_sending_id = fields.Many2one(
comodel_name='mail.mass_mailing.sending',
string="Mass mailing sending", readonly=True)
@api.model
def create(self, vals):
res = super(MailMailStatistics, self).create(vals)
res.mass_mailing_sending_id = \
self.env.context.get('mass_mailing_sending_id', False)
return res

103
mass_mailing_sending_queue/models/mail_mass_mailing.py

@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import api, fields, models, _
from openerp.exceptions import Warning as UserError
class MailMassMailing(models.Model):
_inherit = 'mail.mass_mailing'
state = fields.Selection(selection_add=[
('sending', "Sending"),
])
pending_count = fields.Integer(
string="Pending", compute='_compute_pending_count')
mass_mailing_sending_ids = fields.One2many(
comodel_name='mail.mass_mailing.sending', readonly=True,
inverse_name='mass_mailing_id', string="Sending tasks")
@api.model
def read_group(self, domain, fields, groupby, **kwargs):
# Add 'sending' state group, even if no results.
# This is needed for kanban view, columns are showed always
res = super(MailMassMailing, self).read_group(
domain, fields, groupby, **kwargs)
if groupby and groupby[0] == "state":
group_domain = domain + [('state', '=', 'sending')]
count = self.search_count(group_domain)
res.append({
'__context': {'group_by': groupby[1:]},
'__domain': group_domain,
'state': ('sending', _("Sending")),
'state_count': count,
})
return res
def _sendings_get(self):
self.ensure_one()
return self.env['mail.mass_mailing.sending'].search([
('mass_mailing_id', '=', self.id),
('state', 'in', ('enqueued', 'sending')),
])
@api.multi
def send_mail(self):
if not self.env.context.get('sending_queue_enabled', False):
return super(MailMassMailing, self).send_mail()
for mailing in self:
m_sending = self.env['mail.mass_mailing.sending']
sendings = mailing._sendings_get()
if sendings:
raise UserError(_(
"There is another sending task running. "
"Please, be patient. You can see all the sending tasks in "
"'Sending tasks' tab"
))
res_ids = mailing.get_recipients(mailing)
batch_size = m_sending.batch_size_get()
if not res_ids:
raise UserError(_("Please select recipients."))
sending = m_sending.create({
'state': 'draft',
'mass_mailing_id': mailing.id,
})
sending_state = 'enqueued'
if len(res_ids) < (batch_size / 2):
mailing.with_context(
mass_mailing_sending_id=sending.id,
sending_queue_enabled=False).send_mail()
sending_state = 'sending'
sending.state = sending_state
mailing.write({
'sent_date': fields.Datetime.now(),
'state': 'sending',
})
return True
@api.model
def get_recipients(self, mailing):
sending = False
sending_id = self.env.context.get('mass_mailing_sending_id', False)
if sending_id:
sending = self.env['mail.mass_mailing.sending'].browse(sending_id)
try:
res_ids = super(MailMassMailing, self).get_recipients(mailing)
except UserError as e:
if sending:
sending._send_error(e)
else:
raise
return []
if sending:
res_ids = sending.get_recipient_batch(res_ids)
return res_ids
@api.multi
def _compute_pending_count(self):
for mailing in self:
sendings = mailing._sendings_get()
mailing.pending_count = (
sum(sendings.mapped('pending_count')) +
sum(sendings.mapped('sending_count')))

190
mass_mailing_sending_queue/models/mail_mass_mailing_sending.py

@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from openerp import api, fields, models, tools
_logger = logging.getLogger(__name__)
BATCH_SIZE_DEFAULT = 500
class MailMassMailingSending(models.Model):
_name = 'mail.mass_mailing.sending'
state = fields.Selection([
('draft', "Draft"),
('enqueued', "Enqueued"),
('sending', "Sending"),
('sent', "Sent"),
('error', "Error"),
], string="State", required=True, copy=False, default='enqueued')
mass_mailing_id = fields.Many2one(
string="Mass mailing", comodel_name='mail.mass_mailing',
readonly=True, ondelete='cascade')
pending_count = fields.Integer(
string="Pending recipients", compute='_compute_pending_count')
sending_count = fields.Integer(
string="Mails to be sent", compute='_compute_sending_count')
sent_count = fields.Integer(
string="Sent mails", compute='_compute_sent_count')
failed_count = fields.Integer(
string="Failed mails", compute='_compute_failed_count')
error = fields.Char(string="Error message")
date_start = fields.Datetime(
string="Date start", default=fields.Datetime.now())
date_end = fields.Datetime(string="Date end")
@api.model
def batch_size_get(self):
m_param = self.env['ir.config_parameter']
batch_size = BATCH_SIZE_DEFAULT
batch_size_str = m_param.get_param(
'mail.mass_mailing.sending.batch_size')
if batch_size_str and batch_size_str.isdigit():
batch_size = int(batch_size_str)
return batch_size
@api.multi
def pending_emails(self):
return self.env['mail.mail.statistics'].search([
('mass_mailing_sending_id', 'in', self.ids),
('scheduled', '!=', False),
('sent', '=', False),
('exception', '=', False),
])
@api.multi
def get_recipient_batch(self, res_ids):
batch_size = self.batch_size_get()
already_enqueued = self.env['mail.mail.statistics'].search([
('mass_mailing_sending_id', 'in', self.ids),
])
set_ids = set(res_ids)
new_ids = list(
set_ids - set(already_enqueued.mapped('res_id')))
if not self.env.context.get('sending_avoid_batch', False):
new_ids = new_ids[:batch_size]
if set(new_ids) != set_ids:
return new_ids
return res_ids
@api.multi
def pending_recipients(self):
self.ensure_one()
m_mailing = self.env['mail.mass_mailing'].with_context(
mass_mailing_sending_id=self.id, sending_avoid_batch=True)
return m_mailing.get_recipients(self.mass_mailing_id)
@api.multi
def send_mail(self):
for sending in self:
try:
sending.with_context(mass_mailing_sending_id=sending.id).\
mass_mailing_id.send_mail()
except Exception as e:
sending._send_error(e)
return True
@api.multi
def _send_error(self, exception):
self.ensure_one()
self.write({
'error': tools.ustr(exception),
'state': 'error',
'date_end': fields.Datetime.now(),
})
self.mass_mailing_id.state = 'done'
@api.multi
def _process_enqueued(self):
# Create mail_mail objects not created
self.ensure_one()
if self.pending_recipients():
self.send_mail()
# If there is no more recipient left, mark as sending
if not self.pending_recipients():
self.state = 'sending'
self._process_sending()
elif self.mass_mailing_id.state not in {'sending', 'error'}:
self.mass_mailing_id.state = 'sending'
@api.multi
def _process_sending(self):
# Check if there is any mail_mail object not sent
self.ensure_one()
if not self.pending_emails():
self.mass_mailing_id.state = 'done'
self.write({
'state': 'sent',
'date_end': fields.Datetime.now(),
})
elif self.mass_mailing_id.state not in {'sending', 'error'}:
self.mass_mailing_id.state = 'sending'
@api.multi
def _process(self):
self.ensure_one()
method = getattr(self, '_process_%s' % self.state, None)
if method and hasattr(method, '__call__'):
return method()
return False # pragma: no cover
@api.model
def sendings_running(self):
return self.search([
('state', 'in', ('enqueued', 'sending')),
])
@api.model
def cron(self):
# Process all mail.mass_mailing.sending in enqueue or sending state
sendings = self.sendings_running()
for sending in sendings:
_logger.info("Sending [%d] mass mailing [%d] '%s' (%s)",
sending.id, sending.mass_mailing_id.id,
sending.mass_mailing_id.name, sending.state)
# Process sending using user who created it
sending = sending.sudo(user=sending.create_uid.id)
ctx = sending.create_uid.context_get()
sending.with_context(**ctx)._process()
return True
@api.multi
def _compute_pending_count(self):
for sending in self.filtered(lambda r: r.state == 'enqueued'):
sending.pending_count = len(sending.pending_recipients())
@api.multi
def _compute_sending_count(self):
m_stats = self.env['mail.mail.statistics']
for sending in self.filtered(
lambda r: r.state in {'enqueued', 'sending'}):
sending.sending_count = m_stats.search_count([
('mass_mailing_sending_id', '=', sending.id),
('scheduled', '!=', False),
('sent', '=', False),
('exception', '=', False),
])
@api.multi
def _compute_sent_count(self):
m_stats = self.env['mail.mail.statistics']
for sending in self:
sending.sent_count = m_stats.search_count([
('mass_mailing_sending_id', '=', sending.id),
('scheduled', '!=', False),
('sent', '!=', False),
('exception', '=', False),
])
@api.multi
def _compute_failed_count(self):
m_stats = self.env['mail.mail.statistics']
for sending in self:
sending.failed_count = m_stats.search_count([
('mass_mailing_sending_id', '=', sending.id),
('scheduled', '!=', False),
('exception', '!=', False),
])

3
mass_mailing_sending_queue/security/ir.model.access.csv

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_mass_mailing_sending_admin,mail_mass_mailing_sending group_marketing_user,model_mail_mass_mailing_sending,base.group_user,1,1,1,0
manage_mail_mass_mailing_sending_admin,mail_mass_mailing_sending group_system,model_mail_mass_mailing_sending,base.group_system,1,1,1,1

BIN
mass_mailing_sending_queue/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

5
mass_mailing_sending_queue/tests/__init__.py

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_mass_mailing_sending

179
mass_mailing_sending_queue/tests/test_mass_mailing_sending.py

@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp.tests.common import TransactionCase
from openerp.exceptions import Warning as UserError
class TestMassMailingSending(TransactionCase):
def setUp(self, *args, **kwargs):
super(TestMassMailingSending, self).setUp(*args, **kwargs)
self.list = self.env['mail.mass_mailing.list'].create({
'name': 'Test list',
})
# Define a lower batch size for testing purposes
self.env['ir.config_parameter'].set_param(
'mail.mass_mailing.sending.batch_size', 5)
self.contact_a = self.env['mail.mass_mailing.contact'].create({
'list_id': self.list.id,
'name': 'Test contact A',
'email': 'contact_a@example.org',
})
self.contact_b = self.env['mail.mass_mailing.contact'].create({
'list_id': self.list.id,
'name': 'Test contact B',
'email': 'contact_b@example.org',
})
for i in range(1, 6):
self.env['mail.mass_mailing.contact'].create({
'list_id': self.list.id,
'name': 'Test contact %s' % i,
'email': 'contact_%s@example.org' % i,
})
self.mass_mailing = self.env['mail.mass_mailing'].create({
'name': 'Test mass mailing',
'email_from': 'from@example.org',
'mailing_model': 'mail.mass_mailing.contact',
'mailing_domain': [
('list_id', 'in', [self.list.id]),
('opt_out', '=', False),
],
'contact_list_ids': [(6, False, [self.list.id])],
'body_html': '<p>Hello world!</p>',
'reply_to_mode': 'email',
})
self.partner = self.env['res.partner'].create({
'name': 'Test partner',
'email': 'partner@example.org',
})
self.mass_mailing_short = self.env['mail.mass_mailing'].create({
'name': 'Test mass mailing short',
'email_from': 'from@example.org',
'mailing_model': 'res.partner',
'mailing_domain': [
('id', 'in', [self.partner.id]),
('opt_out', '=', False),
],
'body_html': '<p>Hello partner!</p>',
'reply_to_mode': 'email',
})
def test_cron_contacts(self):
self.mass_mailing.with_context(sending_queue_enabled=True).send_mail()
sendings = self.env['mail.mass_mailing.sending'].search([
('mass_mailing_id', '=', self.mass_mailing.id),
])
stats = self.env['mail.mail.statistics'].search([
('mass_mailing_id', '=', self.mass_mailing.id),
])
# Sending in 'enqueued' state and 0 email stats created
self.assertEqual(1, len(sendings))
self.assertEqual(0, len(stats))
sending = sendings[0]
self.assertEqual('enqueued', sending.state)
self.assertEqual(7, sending.pending_count)
self.assertEqual('sending', self.mass_mailing.state)
self.assertEqual(7, self.mass_mailing.pending_count)
# Create email stats
sending.cron()
stats = self.env['mail.mail.statistics'].search([
('mass_mailing_id', '=', self.mass_mailing.id),
])
self.env.invalidate_all()
# Sending in 'enqueued' state and 5 stats created, 2 pending, 5 sending
self.assertEqual(5, len(stats))
self.assertEqual('enqueued', sending.state)
self.assertEqual(2, sending.pending_count)
self.assertEqual(5, sending.sending_count)
self.assertEqual('sending', self.mass_mailing.state)
for stat in stats:
if stat.mail_mail_id:
stat.mail_mail_id.send()
self.env.invalidate_all()
# Check that 5 emails are already sent
self.assertEqual(0, sending.sending_count)
self.assertEqual(5, sending.sent_count)
sending.cron()
stats = self.env['mail.mail.statistics'].search([
('mass_mailing_id', '=', self.mass_mailing.id),
])
self.env.invalidate_all()
# Sending in 'sending' state and 7 stats created, 0 pending, 2 sending
self.assertEqual(7, len(stats))
self.assertEqual('sending', sending.state)
self.assertEqual(0, sending.pending_count)
self.assertEqual(2, sending.sending_count)
self.assertEqual('sending', self.mass_mailing.state)
for stat in stats:
if stat.mail_mail_id:
stat.mail_mail_id.send()
self.env.invalidate_all()
# Check that 7 emails are already sent
self.assertEqual('sent', sending.state)
self.assertEqual(0, sending.sending_count)
self.assertEqual(7, sending.sent_count)
self.assertEqual(0, sending.failed_count)
self.assertEqual('done', self.mass_mailing.state)
def test_cron_partners(self):
self.mass_mailing_short.with_context(
sending_queue_enabled=True).send_mail()
sendings = self.env['mail.mass_mailing.sending'].search([
('mass_mailing_id', '=', self.mass_mailing_short.id),
])
stats = self.env['mail.mail.statistics'].search([
('mass_mailing_id', '=', self.mass_mailing_short.id),
])
# Sending in 'draft' state and 1 email stats created
self.assertEqual(1, len(sendings))
self.assertEqual(1, len(stats))
sending = sendings[0]
self.assertEqual('sending', sending.state)
self.assertEqual(0, sending.pending_count)
self.assertEqual('sending', self.mass_mailing_short.state)
self.assertEqual(1, self.mass_mailing_short.pending_count)
for stat in stats:
if stat.mail_mail_id:
stat.mail_mail_id.send()
self.env.invalidate_all()
# Check that 1 email are already sent
self.assertEqual('sent', sending.state)
self.assertEqual(0, sending.sending_count)
self.assertEqual(1, sending.sent_count)
self.assertEqual(0, sending.failed_count)
self.assertEqual('done', self.mass_mailing_short.state)
def test_concurrent(self):
self.mass_mailing.with_context(sending_queue_enabled=True).send_mail()
with self.assertRaises(UserError):
self.mass_mailing.with_context(
sending_queue_enabled=True).send_mail()
def test_read_group(self):
groups = self.env['mail.mass_mailing'].read_group(
[('sent_date', '<', '1900-12-31')], ['state', 'name'], ['state'])
self.assertTrue([
x for x in groups if (
x['state_count'] == 0 and x['state'][0] == 'sending')
])
def test_no_recipients(self):
empty_list = self.env['mail.mass_mailing.list'].create({
'name': 'Test list with no recipients',
})
mass_mailing = self.env['mail.mass_mailing'].create({
'name': 'Test mass mailing with no recipients',
'email_from': 'from@example.org',
'mailing_model': 'mail.mass_mailing.contact',
'mailing_domain': [
('list_id', 'in', [empty_list.id]),
('opt_out', '=', False),
],
'contact_list_ids': [(6, False, [empty_list.id])],
'body_html': '<p>Hello no one!</p>',
'reply_to_mode': 'email',
})
with self.assertRaises(UserError):
mass_mailing.send_mail()

81
mass_mailing_sending_queue/views/mass_mailing_sending_view.xml

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data>
<record model="ir.ui.view" id="view_mail_mass_mailing_sending_tree">
<field name="name">mail.mass_mailing.sending.tree</field>
<field name="model">mail.mass_mailing.sending</field>
<field name="arch" type="xml">
<tree string="Mass mailing sendings">
<field name="mass_mailing_id"/>
<field name="date_start"/>
<field name="state"/>
<field name="pending_count"/>
<field name="sending_count"/>
<field name="sent_count"/>
<field name="failed_count"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_sending_form">
<field name="name">mail.mass_mailing.sending.form</field>
<field name="model">mail.mass_mailing.sending</field>
<field name="arch" type="xml">
<form string="Mass mailing sending">
<group>
<group>
<field name="mass_mailing_id"/>
<field name="state"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
<group>
<field name="pending_count"/>
<field name="sending_count"/>
<field name="sent_count"/>
<field name="failed_count"/>
</group>
</group>
<group attrs="{'invisible': [('error', '=', False)]}">
<field name="error"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_sending_search">
<field name="name">mail.mass_mailing.sending.search</field>
<field name="model">mail.mass_mailing.sending</field>
<field name="arch" type="xml">
<search string="Mass mailing sending">
<field name="mass_mailing_id"/>
<filter string="Enqueued" name="enqueued" domain="[('state', '=', 'enqueued')]"/>
<filter string="Sending" name="sending" domain="[('state', '=', 'sending')]"/>
<filter string="Running" name="running" domain="[('state', '!=', 'sent')]"/>
<filter string="Sent" name="sent" domain="[('state', '=' ,'sent')]"/>
<group expand="0" string="Group By">
<filter string="Mass mailing" name="group_mass_mailing" context="{'group_by': 'mass_mailing_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_view_mail_mass_mailing_sending" model="ir.actions.act_window">
<field name="name">Mass mailing sendings</field>
<field name="res_model">mail.mass_mailing.sending</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_running': 1}</field>
<field name="search_view_id" ref="view_mail_mass_mailing_sending_search"/>
</record>
<menuitem name="Mass mailing sending" id="menu_mass_mailing_sending"
parent="base.menu_email" sequence="60"
action="action_view_mail_mass_mailing_sending"/>
</data>
</openerp>

48
mass_mailing_sending_queue/views/mass_mailing_view.xml

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data>
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
<field name="name">Add pending emails to be sent</field>
<field name="model">mail.mass_mailing</field>
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='send_mail'][1]" position="attributes">
<attribute name="attrs">{'invisible': ['|', ('state', 'in', ('done', 'sending')), ('body_html', '=', False)]}</attribute>
<attribute name="context">{'sending_queue_enabled': True}</attribute>
</xpath>
<xpath expr="//button[@name='send_mail'][2]" position="attributes">
<attribute name="attrs">{'invisible': ['|', ('state', 'not in', ('done', )), ('body_html', '=', False)]}</attribute>
<attribute name="context">{'sending_queue_enabled': True}</attribute>
</xpath>
<xpath expr="div[@class='oe_form_box_info oe_text_center']" position="attributes">
<attribute name="attrs">{'invisible': [('pending_count', '=', 0)]}</attribute>
</xpath>
<field name="scheduled" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="scheduled" position="after">
<field name="pending_count" class="oe_inline"/>
</field>
<notebook position="inside">
<page string="Sending tasks">
<field name="mass_mailing_sending_ids" nolabel="1">
<tree string="Sending tasks">
<field name="date_start"/>
<field name="state"/>
<field name="pending_count"/>
<field name="sending_count"/>
<field name="sent_count"/>
<field name="failed_count"/>
</tree>
</field>
</page>
</notebook>
</field>
</record>
</data>
</openerp>
Loading…
Cancel
Save