[REF] mail: make blacklist a mixin built upon mail.thread and add message_bounce

* mail.blacklist.mixin now clearly make inheritance on mail.thread. Indeed
    we consider models using the blacklist mechanism as being used in mailing
    features, meaning they will anyway inherit form mail.thread. In standard
    Odoo it is the case for the 3 models using it (Lead/Opportunity, Contact
    and Mailing Contact);
  * define message_bounce directly in mail.blacklist.mixin. Bounce counter
    is indeed linked to the blacklist and mailing mechanism;
  * rename mail.blacklist.mixin to mail.thread.blacklist to ensure coherency
    with mail.thread and mail.thread.cc (another mixin build on mailL.thread);

Inherit declarations in various addons are updated accordingly.

Related to task ID 1911679
Linked to PR #29483
This commit is contained in:
Thibault Delavallée
2019-05-13 15:18:40 +00:00
parent 0f141607b8
commit dbd4aab6c3
10 changed files with 53 additions and 46 deletions
+1 -4
View File
@@ -50,7 +50,7 @@ class Lead(models.Model):
_name = "crm.lead"
_description = "Lead/Opportunity"
_order = "priority desc,activity_date_deadline,id desc"
_inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin', 'mail.blacklist.mixin']
_inherit = ['mail.thread.cc', 'mail.thread.blacklist', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin']
_primary_email = 'email_from'
def _default_probability(self):
@@ -96,9 +96,6 @@ class Lead(models.Model):
date_last_stage_update = fields.Datetime(string='Last Stage Update', index=True, default=fields.Datetime.now)
date_conversion = fields.Datetime('Conversion Date', readonly=True)
# Messaging and marketing
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
# Only used for type opportunity
probability = fields.Float('Probability', group_operator="avg", default=lambda self: self._default_probability())
planned_revenue = fields.Monetary('Expected Revenue', currency_field='company_currency', tracking=True)
+3 -3
View File
@@ -78,7 +78,7 @@ class IrModel(models.Model):
vals = super(IrModel, self)._reflect_model_params(model)
vals['is_mail_thread'] = issubclass(type(model), self.pool['mail.thread'])
vals['is_mail_activity'] = issubclass(type(model), self.pool['mail.activity.mixin'])
vals['is_mail_blacklist'] = issubclass(type(model), self.pool['mail.blacklist.mixin'])
vals['is_mail_blacklist'] = issubclass(type(model), self.pool['mail.thread.blacklist'])
return vals
@api.model
@@ -92,8 +92,8 @@ class IrModel(models.Model):
parents = model_class._inherit or []
parents = [parents] if isinstance(parents, str) else parents
model_class._inherit = parents + ['mail.activity.mixin']
if model_data.get('is_mail_blacklist') and model_class._name != 'mail.blacklist.mixin':
if model_data.get('is_mail_blacklist') and model_class._name != 'mail.thread.blacklist':
parents = model_class._inherit or []
parents = [parents] if isinstance(parents, str) else parents
model_class._inherit = parents + ['mail.blacklist.mixin']
model_class._inherit = parents + ['mail.thread.blacklist']
return model_class
+16 -16
View File
@@ -6,25 +6,25 @@ from odoo.exceptions import UserError
class MailAddressMixin(models.AbstractModel):
""" Purpose of this mixing is to store a normalized email based on the primary email field.
A normalized email is considered as :
- having a left part + @ + a right part (the domain can be without '.something')
- being lower case
- having no name before the address. Typically, having no 'Name <>'
Ex:
- Formatted Email : 'Name <NaMe@DoMaIn.CoM>'
- Normalized Email : 'name@domain.com'
The primary email field can be specified on the parent model, if it differs from the default one ('email')
The email_normalized field can than be used on that model to search quickly on emails (by simple comparison
and not using time consuming regex anymore).
"""
""" Purpose of this mixin is to store a normalized email based on the primary email field.
A normalized email is considered as :
- having a left part + @ + a right part (the domain can be without '.something')
- being lower case
- having no name before the address. Typically, having no 'Name <>'
Ex:
- Formatted Email : 'Name <NaMe@DoMaIn.CoM>'
- Normalized Email : 'name@domain.com'
The primary email field can be specified on the parent model, if it differs from the default one ('email')
The email_normalized field can than be used on that model to search quickly on emails (by simple comparison
and not using time consuming regex anymore). """
_name = 'mail.address.mixin'
_description = 'Email address mixin'
_description = 'Email Address Mixin'
_primary_email = 'email'
email_normalized = fields.Char(string='Normalized email address', compute="_compute_email_normalized", invisible=True,
compute_sudo=True, store=True, help="""This field is used to search on email address,
as the primary email field can contain more than strictly an email address.""")
email_normalized = fields.Char(
string='Normalized Email', compute="_compute_email_normalized", compute_sudo=True,
store=True, invisible=True,
help="This field is used to search on email address as the primary email field can contain more than strictly an email address.")
@api.depends(lambda self: [self._primary_email])
def _compute_email_normalized(self):
+19 -8
View File
@@ -94,19 +94,22 @@ class MailBlackList(models.Model):
class MailBlackListMixin(models.AbstractModel):
""" Mixin that is inherited by all model with opt out.
This mixin is inheriting of another mixin, mail.address.mixin which defines the _primary_email variable
and the email_normalized field that are mandatory to use the blacklist mixin.
"""
_name = 'mail.blacklist.mixin'
""" Mixin that is inherited by all model with opt out. This mixin inherits from
mail.address.mixin which defines the _primary_email variable and the email_normalized
field that are mandatory to use the blacklist mixin. Mail Thread capabilities
are required for this mixin. """
_name = 'mail.thread.blacklist'
_description = 'Mail Blacklist mixin'
_inherit = ['mail.address.mixin']
_inherit = ['mail.thread', 'mail.address.mixin']
# Note : is_blacklisted sould only be used for display. As the compute is not depending on the blacklist,
# once read, it won't be re-computed again if the blacklist is modified in the same request.
is_blacklisted = fields.Boolean(string='Blacklist', compute="_compute_is_blacklisted", compute_sudo=True,
store=False, search="_search_is_blacklisted", groups="base.group_user",
is_blacklisted = fields.Boolean(
string='Blacklist', compute="_compute_is_blacklisted", compute_sudo=True, store=False,
search="_search_is_blacklisted", groups="base.group_user",
help="If the email address is on the blacklist, the contact won't receive mass mailing anymore, from any list")
# messaging
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
@api.model
def _search_is_blacklisted(self, operator, value):
@@ -147,3 +150,11 @@ class MailBlackListMixin(models.AbstractModel):
('email', 'in', self.mapped('email_normalized'))]).mapped('email'))
for record in self:
record.is_blacklisted = record.email_normalized in blacklist
@api.multi
def _message_receive_bounce(self, email, partner, mail_id=None):
""" Override of mail.thread generic method. Purpose is to increment the
bounce counter of the record. """
super(MailBlackListMixin, self)._message_receive_bounce(email, partner, mail_id=mail_id)
for record in self:
record.message_bounce = record.message_bounce + 1
+9 -8
View File
@@ -1472,17 +1472,18 @@ class MailThread(models.AbstractModel):
def _message_receive_bounce(self, email, partner, mail_id=None):
"""Called by ``message_process`` when a bounce email (such as Undelivered
Mail Returned to Sender) is received for an existing thread. The default
behavior is to check is an integer ``message_bounce`` column exists.
behavior is to do nothing. This method is meant to be overridden in various
modules to add some specific behavior like blacklist management or mass
mailing statistics update. check is an integer ``message_bounce`` column exists.
If it is the case, its content is incremented.
:param record partner: partner matching the bounced email address, if any;
:param string email: email that caused the bounce;
:param mail_id: ID of the sent email that bounced. It may not exist anymore
but it could be usefull if the information was kept. This is
used notably in mass mailing.
:param RecordSet partner: partner matching the bounced email address, if any
:param string email: email that caused the bounce """
if 'message_bounce' in self._fields:
for record in self:
record.message_bounce = record.message_bounce + 1
but it could be useful if the information was kept. This is
used notably in mass mailing;
"""
pass
def _message_extract_payload_postprocess(self, message, body, attachments):
""" Perform some cleaning / postprocess in the body and attachments
+1 -2
View File
@@ -18,10 +18,9 @@ class Partner(models.Model):
""" Update partner to add a field about notification preferences. Add a generic opt-out field that can be used
to restrict usage of automatic email templates. """
_name = "res.partner"
_inherit = ['res.partner', 'mail.thread', 'mail.activity.mixin', 'mail.blacklist.mixin']
_inherit = ['res.partner', 'mail.activity.mixin', 'mail.thread.blacklist']
_mail_flat_thread = False
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
channel_ids = fields.Many2many('mail.channel', 'mail_channel_partner', 'partner_id', 'channel_id', string='Channels', copy=False)
# override the field to track the visibility of user
user_id = fields.Many2one(tracking=True)
+1 -1
View File
@@ -305,7 +305,7 @@ class MailComposer(models.TransientModel):
reply_to_value = self.env['mail.thread']._notify_get_reply_to_on_records(default=self.email_from, records=records)
blacklisted_rec_ids = []
if mass_mail_mode and issubclass(type(self.env[self.model]), self.pool['mail.blacklist.mixin']):
if mass_mail_mode and issubclass(type(self.env[self.model]), self.pool['mail.thread.blacklist']):
BL_sudo = self.env['mail.blacklist'].sudo()
blacklist = set(BL_sudo.search([]).mapped('email'))
if blacklist:
+1 -2
View File
@@ -211,7 +211,7 @@ class MassMailingContact(models.Model):
be able to deal with large contact list to email without bloating the partner
base."""
_name = 'mail.mass_mailing.contact'
_inherit = ['mail.thread', 'mail.blacklist.mixin']
_inherit = ['mail.thread.blacklist']
_description = 'Mass Mailing Contact'
_order = 'email'
_rec_name = 'email'
@@ -226,7 +226,6 @@ class MassMailingContact(models.Model):
'contact_id', 'list_id', string='Mailing Lists')
subscription_list_ids = fields.One2many('mail.mass_mailing.list_contact_rel',
'contact_id', string='Subscription Information')
message_bounce = fields.Integer(string='Bounced', help='Counter of the number of bounced emails for this contact.', default=0)
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',
+1 -1
View File
@@ -20,7 +20,7 @@ class MailTestGateway(models.Model):
mailing features and base performances. """
_description = 'Simple Chatter Model for Mail Gateway'
_name = 'mail.test.gateway'
_inherit = ['mail.blacklist.mixin', 'mail.thread']
_inherit = ['mail.thread.blacklist']
_primary_email = 'email_from'
name = fields.Char()
@@ -20,7 +20,7 @@ class MassMailTestBlacklist(models.Model):
""" Model using blacklist mechanism for mass mailing. """
_description = 'Mass Mailing Model w Blacklist'
_name = 'mass.mail.test.bl'
_inherit = ['mail.thread', 'mail.blacklist.mixin']
_inherit = ['mail.thread.blacklist']
_primary_email = 'email_from' # blacklist field to check