From b465ae97c65faca26ee3aa000898fecbec3f90f1 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Mon, 23 Oct 2017 13:59:20 +0200 Subject: [PATCH] [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 --- addons/mass_mailing/__manifest__.py | 1 + addons/mass_mailing/models/mass_mailing.py | 68 ++++++++++++++++++- addons/mass_mailing/tests/__init__.py | 1 + .../tests/test_mass_mailing_list_merge.py | 58 ++++++++++++++++ addons/mass_mailing/wizard/__init__.py | 1 + .../wizard/mass_mailing_list_merge.py | 37 ++++++++++ .../wizard/mass_mailing_list_merge_views.xml | 33 +++++++++ 7 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 addons/mass_mailing/tests/test_mass_mailing_list_merge.py create mode 100644 addons/mass_mailing/wizard/mass_mailing_list_merge.py create mode 100644 addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py index 81bc743ae52..a9f5dfaebc9 100644 --- a/addons/mass_mailing/__manifest__.py +++ b/addons/mass_mailing/__manifest__.py @@ -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', diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 48c3da82b85..e220bf6e149 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -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 diff --git a/addons/mass_mailing/tests/__init__.py b/addons/mass_mailing/tests/__init__.py index 2e7a36dd53b..8e86bd25001 100644 --- a/addons/mass_mailing/tests/__init__.py +++ b/addons/mass_mailing/tests/__init__.py @@ -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 diff --git a/addons/mass_mailing/tests/test_mass_mailing_list_merge.py b/addons/mass_mailing/tests/test_mass_mailing_list_merge.py new file mode 100644 index 00000000000..3408989d77e --- /dev/null +++ b/addons/mass_mailing/tests/test_mass_mailing_list_merge.py @@ -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'))) diff --git a/addons/mass_mailing/wizard/__init__.py b/addons/mass_mailing/wizard/__init__.py index af5b9192672..41ed9cc32f3 100644 --- a/addons/mass_mailing/wizard/__init__.py +++ b/addons/mass_mailing/wizard/__init__.py @@ -3,3 +3,4 @@ from . import test_mailing from . import mail_compose_message +from . import mass_mailing_list_merge diff --git a/addons/mass_mailing/wizard/mass_mailing_list_merge.py b/addons/mass_mailing/wizard/mass_mailing_list_merge.py new file mode 100644 index 00000000000..2cd408eca0c --- /dev/null +++ b/addons/mass_mailing/wizard/mass_mailing_list_merge.py @@ -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 diff --git a/addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml b/addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml new file mode 100644 index 00000000000..a199e82ff1c --- /dev/null +++ b/addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml @@ -0,0 +1,33 @@ + + + + + mass.mailing.list.merge.form + mass.mailing.list.merge + +
+ + + + + + + + + + + + +
+
+
+
+
+ + +