[IMP] mass_mailing: Add the possibility to merge mailing lists

Purpose
=======

Mailing lists are not flexible. It should be possible to merge mailing lists and remove duplicates when doing it.

Specification
=============

- When selecting records, in the action menu, add "Merge selected mailing lists"
- A modal will open :
	- There's the choice between merging the contacts into a new mailing list or an existing one
	- There's the choice to archive the source mailing lists or not
	- The duplicates are avoided in the destination mailing list
	- Opt-out contacts are not copied in the destination list
This commit is contained in:
Yannick Tivisse
2017-10-23 13:59:20 +02:00
parent bb86e802aa
commit b465ae97c6
7 changed files with 196 additions and 3 deletions
+1
View File
@@ -20,6 +20,7 @@
'security/mass_mailing_security.xml',
'data/mass_mailing_data.xml',
'wizard/mail_compose_message_views.xml',
'wizard/mass_mailing_list_merge_views.xml',
'wizard/test_mailing_views.xml',
'views/mass_mailing_report_views.xml',
'views/mass_mailing_stats_views.xml',
+65 -3
View File
@@ -3,7 +3,6 @@
import hashlib
import hmac
from datetime import datetime
import logging
import random
import threading
@@ -11,7 +10,6 @@ import threading
from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
from odoo.tools.translate import html_translate
_logger = logging.getLogger(__name__)
@@ -25,6 +23,7 @@ MASS_MAILING_BUSINESS_MODELS = [
'mail.mass_mailing.list',
]
class MassMailingTag(models.Model):
"""Model of categories of mass mailing, i.e. marketing, newsletter, ... """
_name = 'mail.mass_mailing.tag'
@@ -59,7 +58,7 @@ class MassMailingList(models.Model):
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
where
c.opt_out <> true
@@ -70,6 +69,69 @@ class MassMailingList(models.Model):
for mailing_list in self:
mailing_list.contact_nbr = data.get(mailing_list.id, 0)
@api.multi
def action_merge(self, src_lists, archive):
"""
Insert all the contact from the mailing lists 'src_lists' to the
mailing list in 'self'. Possibility to archive the mailing lists
'src_lists' after the merge except the destination mailing list 'self'.
"""
# Explation of the SQL query with an example. There are the following lists
# A (id=4): yti@odoo.com; yti@example.com
# B (id=5): yti@odoo.com; yti@openerp.com
# C (id=6): nothing
# To merge the mailing lists A and B into C, we build the view st that looks
# like this with our example:
#
# contact_id | email | row_number | list_id |
# ------------+---------------------------+------------------------
# 4 | yti@odoo.com | 1 | 4 |
# 6 | yti@odoo.com | 2 | 5 |
# 5 | yti@example.com | 1 | 4 |
# 7 | yti@openerp.com | 1 | 5 |
#
# The row_column is kind of an occurence counter for the email address.
# Then we create the Many2many relation between the destination list and the contacts
# while avoiding to insert an existing email address (if the destination is in the source
# for example)
self.ensure_one()
# Put destination is sources lists if not already the case
src_lists |= self
self.env.cr.execute("""
INSERT INTO mail_mass_mailing_contact_list_rel (contact_id, list_id)
SELECT st.contact_id AS contact_id, %s AS list_id
FROM
(
SELECT
contact.id AS contact_id,
contact.email AS email,
mailing_list.id AS list_id,
row_number() OVER (PARTITION BY email ORDER BY email) AS rn
FROM
mail_mass_mailing_contact contact,
mail_mass_mailing_contact_list_rel contact_list_rel,
mail_mass_mailing_list mailing_list
WHERE contact.id=contact_list_rel.contact_id
AND contact.opt_out = FALSE
AND mailing_list.id=contact_list_rel.list_id
AND mailing_list.id IN %s
AND NOT EXISTS
(
SELECT 1
FROM
mail_mass_mailing_contact contact2,
mail_mass_mailing_contact_list_rel contact_list_rel2
WHERE contact2.email = contact.email
AND contact_list_rel2.contact_id = contact2.id
AND contact_list_rel2.list_id = %s
)
) st
WHERE st.rn = 1;""", (self.id, tuple(src_lists.ids), self.id))
self.invalidate_cache()
if archive:
(src_lists - self).write({'active': False})
class MassMailingContact(models.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
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_mail
from . import test_mass_mailing_list_merge
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import common
class TestMassMailingCommon(common.TransactionCase):
def test_00_test_mass_mailing_list_merge(self):
# Data set up
mailing_list_A = self.env['mail.mass_mailing.list'].create({
'name': 'A',
'contact_ids': [
(0, 0, {'name': 'Noel Flantier', 'email': 'noel.flantier@example.com'}),
(0, 0, {'name': 'Gorramts', 'email': 'gorramts@example.com'}),
(0, 0, {'name': 'Ybrant', 'email': 'ybrant@example.com'}),
]
})
mailing_list_B = self.env['mail.mass_mailing.list'].create({
'name': 'B',
'contact_ids': [
(0, 0, {'name': 'Icallhimtest', 'email': 'icallhimtest@example.com'}),
(0, 0, {'name': 'Norbert', 'email': 'norbert@example.com'}),
(0, 0, {'name': 'Ybrant_Zoulou', 'email': 'ybrant@example.com'}),
]
})
mailing_list_C = self.env['mail.mass_mailing.list'].create({
'name': 'C',
'contact_ids': [
(0, 0, {'name': 'Norberto', 'email': 'norbert@example.com'}),
]
})
# TEST CASE: Merge A,B into the existing mailing list C
# The mailing list C contains the same email address than 'Rorbert' in list B
# This test ensure that the mailing lists are correctly merged and no
# duplicates are appearing in C
result_list = self.env['mass.mailing.list.merge'].create({
'src_list_ids': [(4, list_id) for list_id in [mailing_list_A.id, mailing_list_B.id]],
'dest_list_id': mailing_list_C.id,
'merge_options': 'existing',
'new_list_name': False,
'archive_src_lists': False,
}).action_mailing_lists_merge()
# Assert the number of contacts is correct
self.assertEqual(
len(result_list.contact_ids.ids), 5,
'The number of contacts on the mailing list C is not equal to 5')
# Assert there's no duplicated email address
self.assertEqual(
len(list(set(result_list.contact_ids.mapped('email')))), 5,
'Duplicates have been merged into the destination mailing list. Check %s' % (result_list.contact_ids.mapped('email')))
+1
View File
@@ -3,3 +3,4 @@
from . import test_mailing
from . import mail_compose_message
from . import mass_mailing_list_merge
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class MassMailingListMerge(models.TransientModel):
_name = 'mass.mailing.list.merge'
_description = 'Merge Mass Mailing Lists'
src_list_ids = fields.Many2many('mail.mass_mailing.list', string='Mailing Lists')
dest_list_id = fields.Many2one('mail.mass_mailing.list', string='Destination Mailing List')
merge_options = fields.Selection([
('new', 'Merge into a new mailing list'),
('existing', 'Merge into an existing mailing list'),
], 'Merge Option', required=True, default='new')
new_list_name = fields.Char('New Mailing List Name')
archive_src_lists = fields.Boolean('Archive source mailing lists', default=True)
@api.model
def default_get(self, fields):
res = super(MassMailingListMerge, self).default_get(fields)
src_list_ids = self.env.context.get('active_ids')
res.update({
'src_list_ids': src_list_ids,
'dest_list_id': src_list_ids and src_list_ids[0] or False,
})
return res
@api.multi
def action_mailing_lists_merge(self):
if self.merge_options == 'new':
self.dest_list_id = self.env['mail.mass_mailing.list'].create({
'name': self.new_list_name,
}).id
self.dest_list_id.action_merge(self.src_list_ids, self.archive_src_lists)
return self.dest_list_id
@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<odoo>
<!-- Merge Mailing List -->
<record id="mass_mailing_list_merge_view_form" model="ir.ui.view">
<field name="name">mass.mailing.list.merge.form</field>
<field name="model">mass.mailing.list.merge</field>
<field name="arch" type="xml">
<form string="Merge Mass Mailing List">
<group>
<field name="merge_options" widget="selection"/>
<field name="new_list_name" attrs="{'invisible': [('merge_options', '=', 'existing')], 'required': [('merge_options', '=', 'new')]}"/>
<field name="dest_list_id" attrs="{'invisible': [('merge_options', '=', 'new')], 'required': [('merge_options', '=', 'existing')]}"/>
<field name="archive_src_lists"/>
</group>
<field name="src_list_ids">
<tree>
<field name="name"/>
<field name="contact_nbr" string="Number of Recipients"/>
</tree>
</field>
<footer>
<button name="action_mailing_lists_merge" type="object" string="Merge" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<act_window id="mass_mailing_list_merge_action" name="Merge Selected Mailing Lists"
multi="True" key2="client_action_multi"
res_model="mass.mailing.list.merge" src_model="mail.mass_mailing.list"
view_mode="form" target="new" view_type="form"/>
</odoo>