Files
odoo_source/addons/mass_mailing/models/mailing_contact.py
T
Thibault Delavallée ed508ee1bd [REF] mass_mailing: move mailing capability on model
PURPOSE

Remove hardcoded list of models on which mailing is possible. Indeed it is
not modular and not really smart with enterprise code not being reachable
in community.

SPECIFICATIONS

Replace hardcoded list of models on which mailing (both mail or sms) is
possible by a computed searchable field on ``ir.model`` based on a class
attribute.

It allows to cleanly define models having mass mailing capabilities and add
this attribute in bridge modules (when existing) or directly on base model
definition to avoid bridge modules.

In this commit we introduce a basic ``_mailing_enabled`` class attribute
activating mailing on model.

Mailing models may also have a ``_mailing_get_default_domain`` method allowing
to define a custom default domain when sending a marketing mailing on records
on this class.

Mailing models can now define a ``_mailing_get_opt_out_list(_sms)`` method
allowing to define custom behavior to fetch opt-outed records. Instead of
defining a model-based behavior on Mailing itself, it now calls the model
defined one. We still have two methods, one for mailing and one for SMS
opt out computation as it relies on different underlying models and fields.

LINKS

Task ID-2431217
COM PR odoo/odoo#67322
ENT PR odoo/enterprise#16876
UPG PR odoo/upgrade#2236
2021-05-26 07:57:47 +00:00

187 lines
8.7 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.osv import expression
class MassMailingContactListRel(models.Model):
""" Intermediate model between mass mailing list and mass mailing contact
Indicates if a contact is opted out for a particular list
"""
_name = 'mailing.contact.subscription'
_description = 'Mass Mailing Subscription Information'
_table = 'mailing_contact_list_rel'
_rec_name = 'contact_id'
contact_id = fields.Many2one('mailing.contact', string='Contact', ondelete='cascade', required=True)
list_id = fields.Many2one('mailing.list', string='Mailing List', ondelete='cascade', required=True)
opt_out = fields.Boolean(string='Opt Out',
help='The contact has chosen not to receive mails anymore from this list', default=False)
unsubscription_date = fields.Datetime(string='Unsubscription Date')
message_bounce = fields.Integer(related='contact_id.message_bounce', store=False, readonly=False)
is_blacklisted = fields.Boolean(related='contact_id.is_blacklisted', store=False, readonly=False)
_sql_constraints = [
('unique_contact_list', 'unique (contact_id, list_id)',
'A mailing contact cannot subscribe to the same mailing list multiple times.')
]
@api.model_create_multi
def create(self, vals_list):
now = fields.Datetime.now()
for vals in vals_list:
if 'opt_out' in vals and 'unsubscription_date' not in vals:
vals['unsubscription_date'] = now if vals['opt_out'] else False
if vals.get('unsubscription_date'):
vals['opt_out'] = True
return super().create(vals_list)
def write(self, vals):
if 'opt_out' in vals and 'unsubscription_date' not in vals:
vals['unsubscription_date'] = fields.Datetime.now() if vals['opt_out'] else False
if vals.get('unsubscription_date'):
vals['opt_out'] = True
return super(MassMailingContactListRel, self).write(vals)
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
be able to deal with large contact list to email without bloating the partner
base."""
_name = 'mailing.contact'
_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
key. We should use it to create subscription_list_ids default value that
are displayed to the user as list_ids is not displayed on form view. """
res = super(MassMailingContact, self).default_get(fields)
if 'subscription_list_ids' in fields and not res.get('subscription_list_ids'):
list_ids = self.env.context.get('default_list_ids')
if 'default_list_ids' not in res and list_ids and isinstance(list_ids, (list, tuple)):
res['subscription_list_ids'] = [
(0, 0, {'list_id': list_id}) for list_id in list_ids]
return res
name = fields.Char()
company_name = fields.Char(string='Company Name')
title_id = fields.Many2one('res.partner.title', string='Title')
email = fields.Char('Email')
list_ids = fields.Many2many(
'mailing.list', 'mailing_contact_list_rel',
'contact_id', 'list_id', string='Mailing Lists')
subscription_list_ids = fields.One2many('mailing.contact.subscription', 'contact_id', string='Subscription Information')
country_id = fields.Many2one('res.country', string='Country')
tag_ids = fields.Many2many('res.partner.category', string='Tags')
opt_out = fields.Boolean('Opt Out', compute='_compute_opt_out', search='_search_opt_out',
help='Opt out flag for a specific mailing list.'
'This field should not be used in a view without a unique and active mailing list context.')
@api.model
def _search_opt_out(self, operator, value):
# Assumes operator is '=' or '!=' and value is True or False
if operator != '=':
if operator == '!=' and isinstance(value, bool):
value = not value
else:
raise NotImplementedError()
if 'default_list_ids' in self._context and isinstance(self._context['default_list_ids'], (list, tuple)) and len(self._context['default_list_ids']) == 1:
[active_list_id] = self._context['default_list_ids']
contacts = self.env['mailing.contact.subscription'].search([('list_id', '=', active_list_id)])
return [('id', 'in', [record.contact_id.id for record in contacts if record.opt_out == value])]
else:
return expression.FALSE_DOMAIN if value else expression.TRUE_DOMAIN
@api.depends('subscription_list_ids')
@api.depends_context('default_list_ids')
def _compute_opt_out(self):
if 'default_list_ids' in self._context and isinstance(self._context['default_list_ids'], (list, tuple)) and len(self._context['default_list_ids']) == 1:
[active_list_id] = self._context['default_list_ids']
for record in self:
active_subscription_list = record.subscription_list_ids.filtered(lambda l: l.list_id.id == active_list_id)
record.opt_out = active_subscription_list.opt_out
else:
for record in self:
record.opt_out = False
def get_name_email(self, name):
name, email = self.env['res.partner']._parse_partner_name(name)
if name and not email:
email = name
if email and not name:
name = email
return name, email
@api.model_create_multi
def create(self, vals_list):
""" Synchronize default_list_ids (currently used notably for computed
fields) default key with subscription_list_ids given by user when creating
contacts.
Those two values have the same purpose, adding a list to to the contact
either through a direct write on m2m, either through a write on middle
model subscription.
This is a bit hackish but is due to default_list_ids key being
used to compute oupt_out field. This should be cleaned in master but here
we simply try to limit issues while keeping current behavior. """
default_list_ids = self._context.get('default_list_ids')
default_list_ids = default_list_ids if isinstance(default_list_ids, (list, tuple)) else []
if default_list_ids:
for vals in vals_list:
current_list_ids = []
subscription_ids = vals.get('subscription_list_ids') or []
for subscription in subscription_ids:
if len(subscription) == 3:
current_list_ids.append(subscription[2]['list_id'])
for list_id in set(default_list_ids) - set(current_list_ids):
subscription_ids.append((0, 0, {'list_id': list_id}))
vals['subscription_list_ids'] = subscription_ids
return super(MassMailingContact, self.with_context(default_list_ids=False)).create(vals_list)
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
""" Cleans the default_list_ids while duplicating mailing contact in context of
a mailing list because we already have subscription lists copied over for newly
created contact, no need to add the ones from default_list_ids again """
if self.env.context.get('default_list_ids'):
self = self.with_context(default_list_ids=False)
return super().copy(default)
@api.model
def name_create(self, name):
name, email = self.get_name_email(name)
contact = self.create({'name': name, 'email': email})
return contact.name_get()[0]
@api.model
def add_to_list(self, name, list_id):
name, email = self.get_name_email(name)
contact = self.create({'name': name, 'email': email, 'list_ids': [(4, list_id)]})
return contact.name_get()[0]
def _message_get_default_recipients(self):
return {r.id: {
'partner_ids': [],
'email_to': r.email_normalized,
'email_cc': False}
for r in self
}
def action_add_to_mailing_list(self):
ctx = dict(self.env.context, default_contact_ids=self.ids)
action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing.mailing_contact_to_list_action")
action['view_mode'] = 'form'
action['target'] = 'new'
action['context'] = ctx
return action