This question has been flagged
2 Replies
6701 Views

how to avoid adding or importing duplicate contacts in mass mailing lists ?
 


Avatar
Discard
Author Best Answer

this more like a tutorial for those who needs something similar. i have done the following modifications mass_mailing_contact model. that handles create and import. all other modules out of this modification (import)

extend mass_mailing model and add the following code:


# -*- coding: utf-8 -*-

import logging

try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO

import psycopg2

_logger = logging.getLogger(__name__)

from openerp.models import fix_import_export_id_paths, PGERROR_TO_OE
from openerp import models, _


class MailMassMailingContact(models.Model):
_inherit = 'mail.mass_mailing.contact'

_sql_constraints = [
('mailing_contact_email_uniq', 'unique(email)', _('Mass mailing contact already exists'))
]

def load(self, cr, uid, fields, data, context=None):
"""
Attempts to load the data matrix, and returns a list of ids (or
``False`` if there was an error and no id could be generated) and a
list of messages.

The ids are those of the records created and saved (in database), in
the same order they were extracted from the file. They can be passed
directly to :meth:`~read`

:param fields: list of fields to import, at the same index as the corresponding data
:type fields: list(str)
:param data: row-major matrix of data to import
:type data: list(list(str))
:param dict context:
:returns: {ids: list(int)|False, messages: [Message]}
"""
cr.execute('SAVEPOINT model_load')
messages = []

fields = map(fix_import_export_id_paths, fields)
ModelData = self.pool['ir.model.data'].clear_caches()

fg = self.fields_get(cr, uid, context=context)

mode = 'init'
current_module = ''
noupdate = False

ids = []
for id, xid, record, info in self._convert_records(cr, uid,
self._extract_records(cr, uid, fields, data,
context=context, log=messages.append),
context=context, log=messages.append):
try:
cr.execute('SAVEPOINT model_load_save')
except psycopg2.InternalError, e:
# broken transaction, exit and hope the source error was
# already logged
if not any(message['type'] == 'error' for message in messages):
messages.append(dict(info, type='error', message=
u"Unknown database error: '%s'" % e))
break
try:
ids.append(ModelData._update(cr, uid, self._name,
current_module, record, mode=mode, xml_id=xid,
noupdate=noupdate, res_id=id, context=context))
cr.execute('RELEASE SAVEPOINT model_load_save')
except psycopg2.Warning, e:
messages.append(dict(info, type='warning', message=str(e)))
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
except psycopg2.Error, e:
# postgres returns 23505 error on unique key violations, so skip it to continue importing.
# import/update can be implemented in this point ??
if e.pgcode != '23505':
messages.append(dict(
info, type='error',
**PGERROR_TO_OE[e.pgcode](self, fg, info, e)))
# Failed to write, log to messages, rollback savepoint (to
# avoid broken transaction) and keep going
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
except Exception, e:
message = (_('Unknown error during import:') +
' %s: %s' % (type(e), unicode(e)))
moreinfo = _('Resolve other errors first')
messages.append(dict(info, type='error',
message=message,
moreinfo=moreinfo))
# Failed for some reason, perhaps due to invalid data supplied,
# rollback savepoint and keep going
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
if any(message['type'] == 'error' for message in messages):
cr.execute('ROLLBACK TO SAVEPOINT model_load')
ids = False
return {'ids': ids, 'messages': messages}


if you get the following error message during install, it means you have duplicate contact. first get rid of them one-by-one ( I had about 35 ) then it should install without any problem.


2017-11-13 16:19:24,917 22599 ERROR odoov8 openerp.sql_db: bad query: INSERT INTO "mail_mass_mailing_contact" ("id", "name", "list_id", "email", "opt_out", "create_uid", "write_uid", "create_date", "write_date") VALUES(nextval('mail_mass_mailing_contact_id_seq'), 'test-name', 11, 'test-name@gmail.com', false, 1, 1, (now() at time zone 'UTC'), (now() at time zone 'UTC')) RETURNING id Traceback (most recent call last):
File "/opt/projects/odoo/v8/openerp/sql_db.py", line 234, in execute
res = self._obj.execute(query, params)
IntegrityError: duplicate key value violates unique constraint "mail_mass_mailing_contact_mailing_contact_email_uniq"
DETAIL: Key (email)=(test-name@gmail.com) already exists.


when you try to add new contact you will see the following message on popup, mailing list not important here as odoo by default allows you to associate each email per list.


enjoy & happy mailing

Avatar
Discard
Best Answer

Instead of avoiding duplicates on mass mailing contacts, I try to make sure that emails are selected only once for a particular mass mailing. Working on v8, I have overwritten the get_recipients method of the mail.mass_mailing model:

class MassMailing(osv.Model):
    _inherit = 'mail.mass_mailing' 
   
    # remove duplicate and empty emails, inserted in overwritten method below
    def clean_recipients(self, cr, uid, res_ids, context=None):
        mc_obj = self.pool.get('mail.mass_mailing.contact')
        return list(set(mc_obj.browse(cr, uid, i).partner_id.email for i in res_ids))
  
       
    def get_recipients(self, cr, uid, mailing, context=None):
        if mailing.mailing_domain:
            domain = eval(mailing.mailing_domain)
            res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
        else:
            res_ids = []
            domain = [('id', 'in', res_ids)]

        # randomly choose a fragment
        if mailing.contact_ab_pc < 100:
            contact_nbr = self.pool[mailing.mailing_model].search(cr, uid, domain, count=True, context=context)
            topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
            if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
                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]
            else:
                already_mailed = set([])
            remaining = set(res_ids).difference(already_mailed)
            if topick > len(remaining):
                topick = len(remaining)
            res_ids = random.sample(remaining, topick)
        res_ids = self.clean_recipients(cr, uid, res_ids, context=context) ## added
        return res_ids

Avatar
Discard
Author

yes, this is another solution. in my case, I had to avoid duplicates and allow multiple newsletter group subscriptions.