PURPOSE Allow alias domains to be multiple, notably to be used in a multi company environment where each company has its own alias domain. SPECIFICATIONS Add 'mail.alias.domain' information on alias model. Aliases do not use global configuration parameters anymore. Instead they are linked to an alias domain e.g. 'sales' linked to 'mycompany.com' alias domain: 'sales@mycompany.com'. It is now considered as a different alias compared to 'sales@mycompany.in' which has the same alias_name but a different alias domain. Constraints and checks are added, as * uniqueness of aliases is now checked inside a given domain; * an alias name must not clash with its domain bounce or catchall; * a combination of (alias_name, alias_domain_id) must be unique; Crm multi-company environment is also updated to match the new alias domain behavior. Task-36879 (Mail: Support Multi Domains Aliases) Part-of: odoo/odoo#76734
155 lines
7.4 KiB
Python
155 lines
7.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, exceptions, fields, models, _
|
|
from odoo.addons.mail.models.mail_alias import dot_atom_text
|
|
|
|
class AliasDomain(models.Model):
|
|
""" Model alias domains, now company-specific. Alias domains are email
|
|
domains used to receive emails through catchall and bounce aliases, as
|
|
well as using mail.alias records to redirect email replies.
|
|
|
|
This replaces ``mail.alias.domain`` configuration parameter use until v16.
|
|
"""
|
|
_name = 'mail.alias.domain'
|
|
_description = "Email Domain"
|
|
_order = 'sequence ASC, id ASC'
|
|
|
|
name = fields.Char('Name', required=True)
|
|
company_ids = fields.One2many(
|
|
'res.company', 'alias_domain_id', string='Companies')
|
|
sequence = fields.Integer(default=10)
|
|
bounce_alias = fields.Char('Bounce Alias', default='bounce', required=True)
|
|
bounce_email = fields.Char('Bounce Email', compute='_compute_bounce_email')
|
|
catchall_alias = fields.Char('Catchall Alias', default='catchall', required=True)
|
|
catchall_email = fields.Char('Catchall Email', compute='_compute_catchall_email')
|
|
default_from = fields.Char('Default From Alias', default='notifications')
|
|
default_from_email = fields.Char('Default From', compute='_compute_default_from_email')
|
|
|
|
_sql_constraints = [
|
|
(
|
|
'bounce_email_uniques',
|
|
'UNIQUE(bounce_alias, name)',
|
|
'Bounce emails should be unique'
|
|
),
|
|
(
|
|
'catchall_email_uniques',
|
|
'UNIQUE(catchall_alias, name)',
|
|
'Catchall emails should be unique'
|
|
),
|
|
]
|
|
|
|
@api.depends('bounce_alias', 'name')
|
|
def _compute_bounce_email(self):
|
|
self.bounce_email = ''
|
|
for domain in self.filtered('bounce_alias'):
|
|
domain.bounce_email = f'{domain.bounce_alias}@{domain.name}'
|
|
|
|
@api.depends('catchall_alias', 'name')
|
|
def _compute_catchall_email(self):
|
|
self.catchall_email = ''
|
|
for domain in self.filtered('catchall_alias'):
|
|
domain.catchall_email = f'{domain.catchall_alias}@{domain.name}'
|
|
|
|
@api.depends('default_from', 'name')
|
|
def _compute_default_from_email(self):
|
|
self.default_from_email = ''
|
|
for domain in self.filtered('default_from'):
|
|
domain.default_from_email = f'{domain.default_from}@{domain.name}'
|
|
|
|
@api.constrains('bounce_alias', 'catchall_alias')
|
|
def _check_bounce_catchall_uniqueness(self):
|
|
names = self.filtered('bounce_alias').mapped('bounce_alias') + self.filtered('catchall_alias').mapped('catchall_alias')
|
|
if not names:
|
|
return
|
|
|
|
similar_domains = self.env['mail.alias.domain'].search([('name', 'in', self.mapped('name'))])
|
|
for tocheck in self:
|
|
if any(similar.bounce_alias == tocheck.bounce_alias
|
|
for similar in similar_domains if similar != tocheck and similar.name == tocheck.name):
|
|
raise exceptions.ValidationError(
|
|
_('Bounce alias %(bounce)s is already used for another domain with same name. '
|
|
'Use another bounce or simply use the other alias domain.',
|
|
bounce=tocheck.bounce_email)
|
|
)
|
|
if any(similar.catchall_alias == tocheck.catchall_alias
|
|
for similar in similar_domains if similar != tocheck and similar.name == tocheck.name):
|
|
raise exceptions.ValidationError(
|
|
_('Catchall alias %(catchall)s is already used for another domain with same name. '
|
|
'Use another catchall or simply use the other alias domain.',
|
|
catchall=tocheck.catchall_email)
|
|
)
|
|
|
|
# search on left-part only to speedup, then filter on right part
|
|
potential_aliases = self.env['mail.alias'].search([
|
|
('alias_name', 'in', list(set(names))),
|
|
('alias_domain_id', '!=', False)
|
|
])
|
|
existing = next(
|
|
(alias for alias in potential_aliases
|
|
if alias.display_name in (self.mapped('bounce_email') + self.mapped('catchall_email'))),
|
|
self.env['mail.alias']
|
|
)
|
|
if existing:
|
|
document_name = False
|
|
# If owner or target: display document name also in the warning
|
|
if existing.alias_parent_model_id and existing.alias_parent_thread_id:
|
|
document_name = self.env[existing.alias_parent_model_id.model].sudo().browse(existing.alias_parent_thread_id).display_name
|
|
elif existing.alias_model_id and existing.alias_force_thread_id:
|
|
document_name = self.env[existing.alias_model_id.model].sudo().browse(existing.alias_force_thread_id).display_name
|
|
if document_name:
|
|
raise exceptions.ValidationError(
|
|
_("Bounce/Catchall '%(matching_alias_name)s' is already used by %(document_name)s. Choose another alias or change it on the other document.",
|
|
matching_alias_name=existing.display_name,
|
|
document_name=document_name)
|
|
)
|
|
raise exceptions.ValidationError(
|
|
_("Bounce/Catchall '%(matching_alias_name)s' is already used. Choose another alias or change it on the linked model.",
|
|
matching_alias_name=existing.display_name)
|
|
)
|
|
|
|
@api.constrains('name')
|
|
def _check_name(self):
|
|
""" Should match a sanitized version of itself, otherwise raise to warn
|
|
user (do not dynamically change it, would be confusing). """
|
|
for domain in self:
|
|
if not dot_atom_text.match(domain.name):
|
|
raise exceptions.ValidationError(
|
|
_("You cannot use anything else than unaccented latin characters in the domain name %(domain_name)s.",
|
|
domain_name=domain.name)
|
|
)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
""" Sanitize bounce_alias / catchall_alias """
|
|
for vals in vals_list:
|
|
if vals.get('bounce_alias'):
|
|
vals['bounce_alias'] = self.env['mail.alias']._sanitize_alias_name(vals['bounce_alias'])
|
|
if vals.get('catchall_alias'):
|
|
vals['catchall_alias'] = self.env['mail.alias']._sanitize_alias_name(vals['catchall_alias'])
|
|
if vals.get('default_from'):
|
|
vals['default_from'] = self.env['mail.alias']._sanitize_alias_name(vals['default_from'])
|
|
|
|
alias_domains = super().create(vals_list)
|
|
|
|
# alias domain init: populate companies and aliases at first creation
|
|
if alias_domains and self.search_count([]) == len(alias_domains):
|
|
self.env['res.company'].search(
|
|
[('alias_domain_id', '=', False)]
|
|
).alias_domain_id = alias_domains[0].id
|
|
self.env['mail.alias'].sudo().search(
|
|
[('alias_domain_id', '=', False)]
|
|
).alias_domain_id = alias_domains[0].id
|
|
|
|
return alias_domains
|
|
|
|
def write(self, vals):
|
|
""" Sanitize bounce_alias / catchall_alias """
|
|
if vals.get('bounce_alias'):
|
|
vals['bounce_alias'] = self.env['mail.alias']._sanitize_alias_name(vals['bounce_alias'])
|
|
if vals.get('catchall_alias'):
|
|
vals['catchall_alias'] = self.env['mail.alias']._sanitize_alias_name(vals['catchall_alias'])
|
|
if vals.get('default_from'):
|
|
vals['default_from'] = self.env['mail.alias']._sanitize_alias_name(vals['default_from'])
|
|
return super().write(vals)
|