diff --git a/addons/hr_recruitment/models/hr_recruitment.py b/addons/hr_recruitment/models/hr_recruitment.py index 1535dec3eef..395470d9b0d 100644 --- a/addons/hr_recruitment/models/hr_recruitment.py +++ b/addons/hr_recruitment/models/hr_recruitment.py @@ -95,6 +95,7 @@ class Applicant(models.Model): _description = "Applicant" _order = "priority desc, id desc" _inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin'] + _mailing_enabled = True name = fields.Char("Subject / Application Name", required=True, help="Email subject for applications sent via email") active = fields.Boolean("Active", default=True, help="If the active field is set to false, it will allow you to hide the case without removing it.") diff --git a/addons/mass_mailing/models/__init__.py b/addons/mass_mailing/models/__init__.py index 9d9a63bd96f..3f108206778 100644 --- a/addons/mass_mailing/models/__init__.py +++ b/addons/mass_mailing/models/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import ir_model from . import link_tracker from . import mailing_contact from . import mailing_list @@ -10,6 +11,7 @@ from . import mail_mail from . import mail_render_mixin from . import mail_thread from . import res_config_settings +from . import res_partner from . import res_users from . import utm from . import res_company diff --git a/addons/mass_mailing/models/ir_model.py b/addons/mass_mailing/models/ir_model.py new file mode 100644 index 00000000000..6035a8ee635 --- /dev/null +++ b/addons/mass_mailing/models/ir_model.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models, _ + + +class IrModel(models.Model): + _inherit = 'ir.model' + + is_mailing_enabled = fields.Boolean( + string="Mailing Enabled", + compute='_compute_is_mailing_enabled', search='_search_is_mailing_enabled', + help="Whether this model supports marketing mailing capabilities (notably email and SMS).", + ) + + def _compute_is_mailing_enabled(self): + for model in self: + model.is_mailing_enabled = getattr(self.env[model.model], '_mailing_enabled', False) + + def _search_is_mailing_enabled(self, operator, value): + if operator not in ('=', '!='): + raise ValueError(_("Searching Mailing Enabled models supports only direct search using '='' or '!='.")) + + valid_models = self.env['ir.model'] + for model in self.search([]): + if model.model not in self.env or model.is_transient(): + continue + if getattr(self.env[model.model], '_mailing_enabled', False): + valid_models |= model + + search_is_mailing_enabled = (operator == '=' and value) or (operator == '!=' and not value) + if search_is_mailing_enabled: + return [('id', 'in', valid_models.ids)] + return [('id', 'not in', valid_models.ids)] diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py index f25f7b138b0..568a283cf1b 100644 --- a/addons/mass_mailing/models/mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -10,7 +10,6 @@ import re import threading import werkzeug.urls from ast import literal_eval -from datetime import datetime from dateutil.relativedelta import relativedelta from werkzeug.urls import url_join @@ -20,17 +19,6 @@ from odoo.osv import expression _logger = logging.getLogger(__name__) -MASS_MAILING_BUSINESS_MODELS = [ - 'crm.lead', - 'event.registration', - 'hr.applicant', - 'res.partner', - 'event.track', - 'sale.order', - 'mailing.list', - 'mailing.contact' -] - # Syntax of the data URL Scheme: https://tools.ietf.org/html/rfc2397#section-3 # Used to find inline images image_re = re.compile(r"data:(image/[A-Za-z]+);base64,(.*)") @@ -128,7 +116,7 @@ class MassMailing(models.Model): mailing_model_real = fields.Char(string='Recipients Real Model', compute='_compute_model') mailing_model_id = fields.Many2one( 'ir.model', string='Recipients Model', ondelete='cascade', required=True, - domain=[('model', 'in', MASS_MAILING_BUSINESS_MODELS)], + domain=[('is_mailing_enabled', '=', True)], default=lambda self: self.env.ref('mass_mailing.model_mailing_list').id) mailing_model_name = fields.Char( string='Recipients Model Name', related='mailing_model_id.model', @@ -485,20 +473,16 @@ class MassMailing(models.Model): # ------------------------------------------------------ def _get_opt_out_list(self): - """Returns a set of emails opted-out in target model""" + """ Give list of opt-outed emails, depending on specific model-based + computation if available. + + :return list: opt-outed emails, preferably normalized (aka not records) + """ self.ensure_one() opt_out = {} target = self.env[self.mailing_model_real] - if self.mailing_model_real == "mailing.contact": - # if user is opt_out on One list but not on another - # or if two user with same email address, one opted in and the other one opted out, send the mail anyway - # TODO DBE Fixme : Optimise the following to get real opt_out and opt_in - target_list_contacts = self.env['mailing.contact.subscription'].search( - [('list_id', 'in', self.contact_list_ids.ids)]) - opt_out_contacts = target_list_contacts.filtered(lambda rel: rel.opt_out).mapped('contact_id.email_normalized') - opt_in_contacts = target_list_contacts.filtered(lambda rel: not rel.opt_out).mapped('contact_id.email_normalized') - opt_out = set(c for c in opt_out_contacts if c not in opt_in_contacts) - + if hasattr(self.env[self.mailing_model_name], '_mailing_get_opt_out_list'): + opt_out = self.env[self.mailing_model_name]._mailing_get_opt_out_list(self) _logger.info( "Mass-mailing %s targets %s, blacklist: %s emails", self, target._name, len(opt_out)) @@ -848,8 +832,8 @@ class MassMailing(models.Model): def _get_default_mailing_domain(self): mailing_domain = [] - if self.mailing_model_name == 'mailing.list' and self.contact_list_ids: - mailing_domain = [('list_ids', 'in', self.contact_list_ids.ids)] + if hasattr(self.env[self.mailing_model_name], '_mailing_get_default_domain'): + mailing_domain = self.env[self.mailing_model_name]._mailing_get_default_domain(self) if self.mailing_type == 'mail' and 'is_blacklisted' in self.env[self.mailing_model_name]._fields: mailing_domain = expression.AND([[('is_blacklisted', '=', False)], mailing_domain]) diff --git a/addons/mass_mailing/models/mailing_contact.py b/addons/mass_mailing/models/mailing_contact.py index 579599ae4bb..75ea8d0f15c 100644 --- a/addons/mass_mailing/models/mailing_contact.py +++ b/addons/mass_mailing/models/mailing_contact.py @@ -54,6 +54,7 @@ class MassMailingContact(models.Model): _inherit = ['mail.thread.blacklist'] _description = 'Mailing Contact' _order = 'email' + _mailing_enabled = True def default_get(self, fields): """ When coming from a mailing list we may have a default_list_ids context diff --git a/addons/mass_mailing/models/mailing_list.py b/addons/mass_mailing/models/mailing_list.py index e8cbc7922b8..c3aa31baedb 100644 --- a/addons/mass_mailing/models/mailing_list.py +++ b/addons/mass_mailing/models/mailing_list.py @@ -10,6 +10,7 @@ class MassMailingList(models.Model): _name = 'mailing.list' _order = 'name' _description = 'Mailing List' + _mailing_enabled = True name = fields.Char(string='Mailing List', required=True) active = fields.Boolean(default=True) @@ -224,6 +225,24 @@ class MassMailingList(models.Model): def close_dialog(self): return {'type': 'ir.actions.act_window_close'} + # ------------------------------------------------------ + # MAILING + # ------------------------------------------------------ + + def _mailing_get_default_domain(self, mailing): + return [('list_ids', 'in', mailing.contact_list_ids.ids)] + + def _mailing_get_opt_out_list(self, mailing): + """ Check subscription on all involved mailing lists. If user is opt_out + on one list but not on another if two users with same email address, one + opted in and the other one opted out, send the mail anyway. """ + # TODO DBE Fixme : Optimize the following to get real opt_out and opt_in + subscriptions = self.subscription_ids if self else mailing.contact_list_ids.subscription_ids + opt_out_contacts = subscriptions.filtered(lambda rel: rel.opt_out).mapped('contact_id.email_normalized') + opt_in_contacts = subscriptions.filtered(lambda rel: not rel.opt_out).mapped('contact_id.email_normalized') + opt_out = set(c for c in opt_out_contacts if c not in opt_in_contacts) + return opt_out + # ------------------------------------------------------ # UTILITY # ------------------------------------------------------ diff --git a/addons/mass_mailing/models/res_partner.py b/addons/mass_mailing/models/res_partner.py new file mode 100644 index 00000000000..ecc80b745c0 --- /dev/null +++ b/addons/mass_mailing/models/res_partner.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class Partner(models.Model): + _inherit = 'res.partner' + _mailing_enabled = True diff --git a/addons/mass_mailing/tests/test_mailing_internals.py b/addons/mass_mailing/tests/test_mailing_internals.py index 2d762f5dfcd..696e187d90e 100644 --- a/addons/mass_mailing/tests/test_mailing_internals.py +++ b/addons/mass_mailing/tests/test_mailing_internals.py @@ -92,7 +92,7 @@ class TestMassMailValues(MassMailCommon): self.assertEqual(mailing.reply_to_mode, 'new') self.assertEqual(mailing.reply_to, self.email_reply_to) # default for mailing list: depends upon contact_list_ids - self.assertEqual(literal_eval(mailing.mailing_domain), []) + self.assertEqual(literal_eval(mailing.mailing_domain), [('list_ids', 'in', [])]) mailing.write({ 'contact_list_ids': [(4, self.mailing_list_1.id), (4, self.mailing_list_2.id)] }) diff --git a/addons/mass_mailing_crm/models/__init__.py b/addons/mass_mailing_crm/models/__init__.py index 9733ab5351b..c6a6f373a22 100644 --- a/addons/mass_mailing_crm/models/__init__.py +++ b/addons/mass_mailing_crm/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import crm_lead from . import mailing_mailing diff --git a/addons/mass_mailing_crm/models/crm_lead.py b/addons/mass_mailing_crm/models/crm_lead.py new file mode 100644 index 00000000000..f2a13f5356b --- /dev/null +++ b/addons/mass_mailing_crm/models/crm_lead.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class CrmLead(models.Model): + _inherit = 'crm.lead' + _mailing_enabled = True diff --git a/addons/mass_mailing_event/models/__init__.py b/addons/mass_mailing_event/models/__init__.py index 80862024115..00b4cca8520 100644 --- a/addons/mass_mailing_event/models/__init__.py +++ b/addons/mass_mailing_event/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import event +from . import event_event +from . import event_registration diff --git a/addons/mass_mailing_event/models/event.py b/addons/mass_mailing_event/models/event_event.py similarity index 97% rename from addons/mass_mailing_event/models/event.py rename to addons/mass_mailing_event/models/event_event.py index 648a900795b..0212db092f5 100644 --- a/addons/mass_mailing_event/models/event.py +++ b/addons/mass_mailing_event/models/event_event.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, _ +from odoo import models class Event(models.Model): diff --git a/addons/mass_mailing_event/models/event_registration.py b/addons/mass_mailing_event/models/event_registration.py new file mode 100644 index 00000000000..26b5f8f0a65 --- /dev/null +++ b/addons/mass_mailing_event/models/event_registration.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class EventRegistration(models.Model): + _inherit = 'event.registration' + _mailing_enabled = True diff --git a/addons/mass_mailing_event_track/models/__init__.py b/addons/mass_mailing_event_track/models/__init__.py index 80862024115..560fda625cd 100644 --- a/addons/mass_mailing_event_track/models/__init__.py +++ b/addons/mass_mailing_event_track/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import event +from . import event_event +from . import event_track diff --git a/addons/mass_mailing_event_track/models/event.py b/addons/mass_mailing_event_track/models/event_event.py similarity index 100% rename from addons/mass_mailing_event_track/models/event.py rename to addons/mass_mailing_event_track/models/event_event.py diff --git a/addons/mass_mailing_event_track/models/event_track.py b/addons/mass_mailing_event_track/models/event_track.py new file mode 100644 index 00000000000..6fb71e7ef09 --- /dev/null +++ b/addons/mass_mailing_event_track/models/event_track.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class EventTrack(models.Model): + _inherit = 'event.track' + _mailing_enabled = True diff --git a/addons/mass_mailing_sale/models/__init__.py b/addons/mass_mailing_sale/models/__init__.py index 9733ab5351b..68af919bac3 100644 --- a/addons/mass_mailing_sale/models/__init__.py +++ b/addons/mass_mailing_sale/models/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import mailing_mailing +from . import sale_order diff --git a/addons/mass_mailing_sale/models/sale_order.py b/addons/mass_mailing_sale/models/sale_order.py new file mode 100644 index 00000000000..36dccbd4ffa --- /dev/null +++ b/addons/mass_mailing_sale/models/sale_order.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + _mailing_enabled = True diff --git a/addons/mass_mailing_sms/models/mailing_list.py b/addons/mass_mailing_sms/models/mailing_list.py index c07258e5eb3..bc2347ecbde 100644 --- a/addons/mass_mailing_sms/models/mailing_list.py +++ b/addons/mass_mailing_sms/models/mailing_list.py @@ -51,3 +51,15 @@ class MailingList(models.Model): return super(MailingList, self)._get_contact_statistics_joins() + ''' LEFT JOIN phone_blacklist bl_sms ON c.phone_sanitized = bl_sms.number and bl_sms.active ''' + + def _mailing_get_opt_out_list_sms(self, mailing): + """ Check subscription on all involved mailing lists. If user is opt_out + on one list but not on another, one opted in and the other one opted out, + send mailing anyway. + + :return list: opt-outed record IDs + """ + subscriptions = self.subscription_ids if self else mailing.contact_list_ids.subscription_ids + opt_out_contacts = subscriptions.filtered(lambda sub: sub.opt_out).mapped('contact_id') + opt_in_contacts = subscriptions.filtered(lambda sub: not sub.opt_out).mapped('contact_id') + return list(set(c.id for c in opt_out_contacts if c not in opt_in_contacts)) diff --git a/addons/mass_mailing_sms/models/mailing_mailing.py b/addons/mass_mailing_sms/models/mailing_mailing.py index ef1f3445ea1..2a36ef8329b 100644 --- a/addons/mass_mailing_sms/models/mailing_mailing.py +++ b/addons/mass_mailing_sms/models/mailing_mailing.py @@ -145,20 +145,16 @@ class Mailing(models.Model): # -------------------------------------------------- def _get_opt_out_list_sms(self): - """Returns a set of emails opted-out in target model""" + """ Give list of opt-outed records, depending on specific model-based + computation if available. + + :return list: opt-outed record IDs + """ self.ensure_one() opt_out = [] target = self.env[self.mailing_model_real] - if self.mailing_model_real == 'mailing.contact': - # if user is opt_out on One list but not on another - # or if two user with same email address, one opted in and the other one opted out, send the mail anyway - # TODO DBE Fixme : Optimise the following to get real opt_out and opt_in - subscriptions = self.env['mailing.contact.subscription'].sudo().search( - [('list_id', 'in', self.contact_list_ids.ids)]) - opt_out_contacts = subscriptions.filtered(lambda sub: sub.opt_out).mapped('contact_id') - opt_in_contacts = subscriptions.filtered(lambda sub: not sub.opt_out).mapped('contact_id') - opt_out = list(set(c.id for c in opt_out_contacts if c not in opt_in_contacts)) - + if hasattr(self.env[self.mailing_model_name], '_mailing_get_opt_out_list_sms'): + opt_out = self.env[self.mailing_model_name]._mailing_get_opt_out_list_sms(self) _logger.info("Mass SMS %s targets %s: optout: %s contacts", self, target._name, len(opt_out)) else: _logger.info("Mass SMS %s targets %s: no opt out list available", self, target._name) diff --git a/addons/mass_mailing_sms/tests/test_mailing_internals.py b/addons/mass_mailing_sms/tests/test_mailing_internals.py index 46f1e508665..bcbb28b656e 100644 --- a/addons/mass_mailing_sms/tests/test_mailing_internals.py +++ b/addons/mass_mailing_sms/tests/test_mailing_internals.py @@ -54,7 +54,7 @@ class TestMassMailValues(MassSMSCommon): self.assertEqual(mailing.mailing_model_name, 'mailing.list') self.assertEqual(mailing.mailing_model_real, 'mailing.contact') # default for mailing list: depends upon contact_list_ids - self.assertEqual(literal_eval(mailing.mailing_domain), []) + self.assertEqual(literal_eval(mailing.mailing_domain), [('list_ids', 'in', [])]) mailing.write({ 'contact_list_ids': [(4, self.mailing_list_1.id), (4, self.mailing_list_2.id)] }) diff --git a/addons/test_mail_full/models/__init__.py b/addons/test_mail_full/models/__init__.py index 0fe839f20a6..26ed2919633 100644 --- a/addons/test_mail_full/models/__init__.py +++ b/addons/test_mail_full/models/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- from . import test_mail_models -from . import mailing_mailing diff --git a/addons/test_mail_full/models/test_mail_models.py b/addons/test_mail_full/models/test_mail_models.py index 0a8ad967414..39a9cd2effe 100644 --- a/addons/test_mail_full/models/test_mail_models.py +++ b/addons/test_mail_full/models/test_mail_models.py @@ -10,6 +10,7 @@ class MailTestSMS(models.Model): _description = 'Chatter Model for SMS Gateway' _name = 'mail.test.sms' _inherit = ['mail.thread'] + _mailing_enabled = True _order = 'name asc, id asc' name = fields.Char() @@ -32,6 +33,7 @@ class MailTestSMSBL(models.Model): _description = 'SMS Mailing Blacklist Enabled' _name = 'mail.test.sms.bl' _inherit = ['mail.thread.phone'] + _mailing_enabled = True _order = 'name asc, id asc' name = fields.Char() @@ -54,6 +56,7 @@ class MailTestSMSOptout(models.Model): _description = 'SMS Mailing Blacklist / Optout Enabled' _name = 'mail.test.sms.bl.optout' _inherit = ['mail.thread.phone'] + _mailing_enabled = True _order = 'name asc, id asc' name = fields.Char() @@ -64,6 +67,13 @@ class MailTestSMSOptout(models.Model): customer_id = fields.Many2one('res.partner', 'Customer') opt_out = fields.Boolean() + def _mailing_get_opt_out_list_sms(self, mailing): + res_ids = mailing._get_recipients() + return self.search([ + ('id', 'in', res_ids), + ('opt_out', '=', True) + ]).ids + def _sms_get_partner_fields(self): return ['customer_id'] @@ -78,11 +88,19 @@ class MailTestSMSPartner(models.Model): _description = 'Chatter Model for SMS Gateway (Partner only)' _name = 'mail.test.sms.partner' _inherit = ['mail.thread'] + _mailing_enabled = True name = fields.Char() customer_id = fields.Many2one('res.partner', 'Customer') opt_out = fields.Boolean() + def _mailing_get_opt_out_list_sms(self, mailing): + res_ids = mailing._get_recipients() + return self.search([ + ('id', 'in', res_ids), + ('opt_out', '=', True) + ]).ids + def _sms_get_partner_fields(self): return ['customer_id'] @@ -96,11 +114,19 @@ class MailTestSMSPartner2Many(models.Model): _description = 'Chatter Model for SMS Gateway (M2M Partners only)' _name = 'mail.test.sms.partner.2many' _inherit = ['mail.thread'] + _mailing_enabled = True name = fields.Char() customer_ids = fields.Many2many('res.partner', string='Customers') opt_out = fields.Boolean() + def _mailing_get_opt_out_list_sms(self, mailing): + res_ids = mailing._get_recipients() + return self.search([ + ('id', 'in', res_ids), + ('opt_out', '=', True) + ]).ids + def _sms_get_partner_fields(self): return ['customer_ids'] diff --git a/addons/test_mass_mailing/models/__init__.py b/addons/test_mass_mailing/models/__init__.py index ea86084ecd7..530865275cb 100644 --- a/addons/test_mass_mailing/models/__init__.py +++ b/addons/test_mass_mailing/models/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import mailing_mailing from . import mailing_models diff --git a/addons/test_mass_mailing/models/mailing_mailing.py b/addons/test_mass_mailing/models/mailing_mailing.py deleted file mode 100644 index 12c888dbb8a..00000000000 --- a/addons/test_mass_mailing/models/mailing_mailing.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import logging - -from odoo import models - -_logger = logging.getLogger(__name__) - - -class Mailing(models.Model): - _inherit = 'mailing.mailing' - - def _get_opt_out_list(self): - """Returns a set of emails opted-out in target model""" - self.ensure_one() - if self.mailing_model_real == 'mailing.test.optout': - res_ids = self._get_recipients() - opt_out_contacts = set(self.env['mailing.test.optout'].search([ - ('id', 'in', res_ids), - ('opt_out', '=', True) - ]).mapped('email_normalized')) - _logger.info( - "Mass-mailing %s targets %s, optout: %s emails", - self, self.mailing_model_real, len(opt_out_contacts)) - return opt_out_contacts - return super(Mailing, self)._get_opt_out_list() diff --git a/addons/test_mass_mailing/models/mailing_models.py b/addons/test_mass_mailing/models/mailing_models.py index 54ede3f8e14..cafda8d5cab 100644 --- a/addons/test_mass_mailing/models/mailing_models.py +++ b/addons/test_mass_mailing/models/mailing_models.py @@ -51,6 +51,14 @@ class MailingOptOut(models.Model): customer_id = fields.Many2one('res.partner', 'Customer', tracking=True) user_id = fields.Many2one('res.users', 'Responsible', tracking=True) + def _mailing_get_opt_out_list(self, mailing): + res_ids = mailing._get_recipients() + opt_out_contacts = set(self.search([ + ('id', 'in', res_ids), + ('opt_out', '=', True) + ]).mapped('email_normalized')) + return opt_out_contacts + class MailingPerformance(models.Model): """ A very simple model only inheriting from mail.thread to test pure mass diff --git a/addons/test_mass_mailing/tests/test_mailing.py b/addons/test_mass_mailing/tests/test_mailing.py index 7615d858a45..7f534e20de8 100644 --- a/addons/test_mass_mailing/tests/test_mailing.py +++ b/addons/test_mass_mailing/tests/test_mailing.py @@ -303,10 +303,13 @@ class TestMassMailing(TestMassMailCommon): # contact_1 is optout but same email is not optout from the same list # contact 3 is optout in list 1 but not in list 2 # contact 5 is optout - Sub = self.env['mailing.contact.subscription'] - Sub.search([('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id)]).write({'opt_out': True}) - Sub.search([('contact_id', '=', mailing_contact_3.id), ('list_id', '=', mailing_list_1.id)]).write({'opt_out': True}) - Sub.search([('contact_id', '=', mailing_contact_5.id), ('list_id', '=', mailing_list_1.id)]).write({'opt_out': True}) + subs = self.env['mailing.contact.subscription'].search([ + '|', '|', + '&', ('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id), + '&', ('contact_id', '=', mailing_contact_3.id), ('list_id', '=', mailing_list_1.id), + '&', ('contact_id', '=', mailing_contact_5.id), ('list_id', '=', mailing_list_1.id) + ]) + subs.write({'opt_out': True}) # create mass mailing record mailing = self.env['mailing.mailing'].create({