mail_thread_blacklist.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo import api, fields, models, tools, _
  4. from odoo.exceptions import AccessError, UserError
  5. class MailBlackListMixin(models.AbstractModel):
  6. """ Mixin that is inherited by all model with opt out. This mixin stores a normalized
  7. email based on primary_email field.
  8. A normalized email is considered as :
  9. - having a left part + @ + a right part (the domain can be without '.something')
  10. - being lower case
  11. - having no name before the address. Typically, having no 'Name <>'
  12. Ex:
  13. - Formatted Email : 'Name <NaMe@DoMaIn.CoM>'
  14. - Normalized Email : 'name@domain.com'
  15. The primary email field can be specified on the parent model, if it differs from the default one ('email')
  16. The email_normalized field can than be used on that model to search quickly on emails (by simple comparison
  17. and not using time consuming regex anymore).
  18. Using this email_normalized field, blacklist status is computed.
  19. Mail Thread capabilities are required for this mixin. """
  20. _name = 'mail.thread.blacklist'
  21. _inherit = ['mail.thread']
  22. _description = 'Mail Blacklist mixin'
  23. _primary_email = 'email'
  24. email_normalized = fields.Char(
  25. string='Normalized Email', compute="_compute_email_normalized", compute_sudo=True,
  26. store=True, invisible=True,
  27. help="This field is used to search on email address as the primary email field can contain more than strictly an email address.")
  28. # Note : is_blacklisted sould only be used for display. As the compute is not depending on the blacklist,
  29. # once read, it won't be re-computed again if the blacklist is modified in the same request.
  30. is_blacklisted = fields.Boolean(
  31. string='Blacklist', compute="_compute_is_blacklisted", compute_sudo=True, store=False,
  32. search="_search_is_blacklisted", groups="base.group_user",
  33. help="If the email address is on the blacklist, the contact won't receive mass mailing anymore, from any list")
  34. # messaging
  35. message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
  36. @api.depends(lambda self: [self._primary_email])
  37. def _compute_email_normalized(self):
  38. self._assert_primary_email()
  39. for record in self:
  40. record.email_normalized = tools.email_normalize(record[self._primary_email], strict=False)
  41. @api.model
  42. def _search_is_blacklisted(self, operator, value):
  43. # Assumes operator is '=' or '!=' and value is True or False
  44. self.flush_model(['email_normalized'])
  45. self.env['mail.blacklist'].flush_model(['email', 'active'])
  46. self._assert_primary_email()
  47. if operator != '=':
  48. if operator == '!=' and isinstance(value, bool):
  49. value = not value
  50. else:
  51. raise NotImplementedError()
  52. if value:
  53. query = """
  54. SELECT m.id
  55. FROM mail_blacklist bl
  56. JOIN %s m
  57. ON m.email_normalized = bl.email AND bl.active
  58. """
  59. else:
  60. query = """
  61. SELECT m.id
  62. FROM %s m
  63. LEFT JOIN mail_blacklist bl
  64. ON m.email_normalized = bl.email AND bl.active
  65. WHERE bl.id IS NULL
  66. """
  67. self._cr.execute((query + " FETCH FIRST ROW ONLY") % self._table)
  68. res = self._cr.fetchall()
  69. if not res:
  70. return [(0, '=', 1)]
  71. return [('id', 'inselect', (query % self._table, []))]
  72. @api.depends('email_normalized')
  73. def _compute_is_blacklisted(self):
  74. # TODO : Should remove the sudo as compute_sudo defined on methods.
  75. # But if user doesn't have access to mail.blacklist, doen't work without sudo().
  76. blacklist = set(self.env['mail.blacklist'].sudo().search([
  77. ('email', 'in', self.mapped('email_normalized'))]).mapped('email'))
  78. for record in self:
  79. record.is_blacklisted = record.email_normalized in blacklist
  80. def _assert_primary_email(self):
  81. if not hasattr(self, "_primary_email") or not isinstance(self._primary_email, str):
  82. raise UserError(_('Invalid primary email field on model %s', self._name))
  83. if self._primary_email not in self._fields or self._fields[self._primary_email].type != 'char':
  84. raise UserError(_('Invalid primary email field on model %s', self._name))
  85. def _message_receive_bounce(self, email, partner):
  86. """ Override of mail.thread generic method. Purpose is to increment the
  87. bounce counter of the record. """
  88. super(MailBlackListMixin, self)._message_receive_bounce(email, partner)
  89. for record in self:
  90. record.message_bounce = record.message_bounce + 1
  91. def _message_reset_bounce(self, email):
  92. """ Override of mail.thread generic method. Purpose is to reset the
  93. bounce counter of the record. """
  94. super(MailBlackListMixin, self)._message_reset_bounce(email)
  95. self.write({'message_bounce': 0})
  96. def mail_action_blacklist_remove(self):
  97. # wizard access rights currently not working as expected and allows users without access to
  98. # open this wizard, therefore we check to make sure they have access before the wizard opens.
  99. can_access = self.env['mail.blacklist'].check_access_rights('write', raise_exception=False)
  100. if can_access:
  101. return {
  102. 'name': _('Are you sure you want to unblacklist this Email Address?'),
  103. 'type': 'ir.actions.act_window',
  104. 'view_mode': 'form',
  105. 'res_model': 'mail.blacklist.remove',
  106. 'target': 'new',
  107. }
  108. else:
  109. raise AccessError(_("You do not have the access right to unblacklist emails. Please contact your administrator."))
  110. @api.model
  111. def _detect_loop_sender_domain(self, email_from_normalized):
  112. """Return the domain to be used to detect duplicated records created by alias.
  113. :param email_from_normalized: FROM of the incoming email, normalized
  114. """
  115. return [('email_normalized', '=', email_from_normalized)]