[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:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')))
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user