[IMP] mail: allow to search on alias complete email
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 Currently alias search is done only on 'alias_name' field. Now that alias domains can be multiple we have to be able to search on complete alias email definition e.g. when checking destination aliases of incoming emails in mail gateway. We add a new 'alias_full_name' field that is computed based on alias_name and alias_domain_id.name. It is stored so that search is possible on it. Allow to search on 'alias_full_name' from the 'mail.alias.mixin(.optional)' by making 'alias_email' field searchable, based on 'alias_full_name'. Task-36879 (Mail: Support Multi Domains Aliases) Part-of: odoo/odoo#76734
This commit is contained in:
@@ -38,6 +38,8 @@ class Alias(models.Model):
|
||||
alias_name = fields.Char(
|
||||
'Alias Name', copy=False,
|
||||
help="The name of the email alias, e.g. 'jobs' if you want to catch emails for <jobs@example.odoo.com>")
|
||||
alias_full_name = fields.Char('Alias Email', compute='_compute_alias_full_name', store=True)
|
||||
display_name = fields.Char(string='Display Name', compute='_compute_display_name')
|
||||
alias_domain_id = fields.Many2one(
|
||||
'mail.alias.domain', string='Alias Domain', ondelete='restrict',
|
||||
default=lambda self: self.env.company.alias_domain_id)
|
||||
@@ -210,6 +212,18 @@ class Alias(models.Model):
|
||||
alias_names=', '.join(failing.mapped('display_name')))
|
||||
)
|
||||
|
||||
@api.depends('alias_domain_id.name', 'alias_name')
|
||||
def _compute_alias_full_name(self):
|
||||
""" A bit like display_name, but without the 'inactive alias' UI display.
|
||||
Moreover it is stored, allowing to search on it. """
|
||||
for record in self:
|
||||
if record.alias_domain_id and record.alias_name:
|
||||
record.alias_full_name = f"{record.alias_name}@{record.alias_domain_id.name}"
|
||||
elif record.alias_name:
|
||||
record.alias_full_name = record.alias_name
|
||||
else:
|
||||
record.alias_full_name = False
|
||||
|
||||
@api.depends('alias_domain', 'alias_name')
|
||||
def _compute_display_name(self):
|
||||
""" Return the mail alias display alias_name, including the catchall
|
||||
|
||||
@@ -25,7 +25,7 @@ class AliasMixinOptional(models.AbstractModel):
|
||||
related='alias_id.alias_domain_id', readonly=False)
|
||||
alias_domain = fields.Char('Alias Domain Name', related='alias_id.alias_domain')
|
||||
alias_defaults = fields.Text(related='alias_id.alias_defaults')
|
||||
alias_email = fields.Char('Email Alias', compute='_compute_alias_email')
|
||||
alias_email = fields.Char('Email Alias', compute='_compute_alias_email', search='_search_alias_email')
|
||||
|
||||
@api.depends('alias_domain', 'alias_name')
|
||||
def _compute_alias_email(self):
|
||||
@@ -36,6 +36,9 @@ class AliasMixinOptional(models.AbstractModel):
|
||||
for record in self.filtered(lambda rec: rec.alias_name and rec.alias_domain):
|
||||
record.alias_email = f"{record.alias_name}@{record.alias_domain}"
|
||||
|
||||
def _search_alias_email(self, operator, operand):
|
||||
return [('alias_id.alias_full_name', operator, operand)]
|
||||
|
||||
# --------------------------------------------------
|
||||
# CRUD
|
||||
# --------------------------------------------------
|
||||
|
||||
@@ -348,6 +348,69 @@ class TestMailAlias(TestMailAliasCommon):
|
||||
for alias in aliases:
|
||||
self.assertEqual(alias.alias_name, False)
|
||||
|
||||
@users('admin')
|
||||
def test_search(self):
|
||||
""" Test search on aliases, notably searching on display_name which should
|
||||
be split on searching on alias_name and alias_domain_id. """
|
||||
# ensure existing aliases to ease future asserts
|
||||
existing = self.env['mail.alias'].search([('alias_domain_id', '!=', False)])
|
||||
self.assertEqual(existing.alias_domain_id, self.mail_alias_domain)
|
||||
existing.write({'alias_name': False}) # don't be annoyed by existing aliases
|
||||
|
||||
mail_alias_domain = self.mail_alias_domain.with_env(self.env)
|
||||
mail_alias_domain_c2 = self.mail_alias_domain_c2.with_env(self.env)
|
||||
self.assertEqual(mail_alias_domain.name, 'test.mycompany.com')
|
||||
self.assertEqual(mail_alias_domain_c2.name, 'test.mycompany2.com')
|
||||
|
||||
aliases = self.env['mail.alias'].create([
|
||||
{
|
||||
'alias_model_id': self.env['ir.model']._get('mail.test.container.mc').id,
|
||||
'alias_name': f'test.search.{idx}',
|
||||
'alias_domain_id': domain.id,
|
||||
}
|
||||
for idx in range(5)
|
||||
for domain in (mail_alias_domain + mail_alias_domain_c2)
|
||||
])
|
||||
aliases_d1 = aliases.filtered(lambda a: a.alias_domain_id == mail_alias_domain)
|
||||
aliases_d2 = aliases.filtered(lambda a: a.alias_domain_id == mail_alias_domain_c2)
|
||||
|
||||
# search on alias_name: classic search
|
||||
self.assertEqual(
|
||||
self.env['mail.alias'].search([('alias_name', 'ilike', 'test.search')]),
|
||||
aliases
|
||||
)
|
||||
|
||||
# search on alias_fullname: search on aggregated of {name}@{domain}
|
||||
for search_term, expected, msg in [
|
||||
('mycompany', aliases,
|
||||
'Match all aliases on both domains as "mycompany" is contained in those two'),
|
||||
(mail_alias_domain.name, aliases_d1,
|
||||
'Exact match on domain 1: should find all aliases in that domain'),
|
||||
(mail_alias_domain_c2.name, aliases_d2,
|
||||
'Exact match on domain 2: should find all aliases in that domain'),
|
||||
('search.0@test.mycompany', aliases.filtered(lambda a: a.alias_name == 'test.search.0'),
|
||||
'Match in both domains'),
|
||||
('search.0@test.mycompany.com', aliases.filtered(lambda a: a.alias_name == 'test.search.0' and a.alias_domain_id == mail_alias_domain),
|
||||
'Match only in domain 1'),
|
||||
('search@test.mycompany.com', self.env['mail.alias'],
|
||||
'Does not match even as ilike'),
|
||||
]:
|
||||
with self.subTest(search_term=search_term):
|
||||
self.assertEqual(
|
||||
self.env['mail.alias'].search([('alias_full_name', 'ilike', search_term)]),
|
||||
expected, msg
|
||||
)
|
||||
|
||||
# search using IN operator
|
||||
for search_term, expected, msg in [
|
||||
(['mycompany'], self.env['mail.alias'], 'mycompany is too vague: does not match a left- and right- part (!= ilike)'),
|
||||
([mail_alias_domain.name], self.env['mail.alias'], 'Match only right-part of aliases emails'),
|
||||
]:
|
||||
with self.subTest(search_term=search_term):
|
||||
self.assertEqual(self.env['mail.alias'].search([('alias_full_name', 'in', search_term)]),
|
||||
expected, msg
|
||||
)
|
||||
|
||||
@users('admin')
|
||||
def test_alias_setup(self):
|
||||
""" Test various constraints / configuration of alias model"""
|
||||
@@ -679,6 +742,32 @@ class TestMailAliasMixin(TestMailAliasCommon):
|
||||
with self.assertRaises(exceptions.ValidationError):
|
||||
record.write({'alias_defaults': "{'custom_field': brokendict"})
|
||||
|
||||
@users('erp_manager')
|
||||
def test_alias_mixin_alias_email(self):
|
||||
""" Test 'alias_email' mixin field computation and search capability """
|
||||
Model = self.env['mail.test.container.mc']
|
||||
records = Model.create([
|
||||
{
|
||||
'alias_name': f'alias.email.{idx}', # will be present in all companies
|
||||
'company_id': company.id,
|
||||
'name': f'Test {company.id} {idx}',
|
||||
}
|
||||
for company in (self.company_admin, self.company_2)
|
||||
for idx in range(5)
|
||||
])
|
||||
self.assertEqual(
|
||||
Model.search([('alias_email', 'ilike', 'alias.email')]), records,
|
||||
'Search: partial search: any domain, matching all left parts')
|
||||
self.assertEqual(
|
||||
Model.search([('alias_email', 'ilike', 'alias.email.0')]), records[0] + records[5],
|
||||
'Search: partial search: any domain, matching some left parts')
|
||||
self.assertEqual(
|
||||
Model.search([('alias_email', '=', self.mail_alias_domain.name)]), Model,
|
||||
'Search: partial search: does not match any complete email')
|
||||
self.assertEqual(
|
||||
Model.search([('alias_email', '=', f'alias.email.1@{self.mail_alias_domain.name}')]), records[1],
|
||||
'Search: both part search: search on name + domain')
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.base.models.ir_model')
|
||||
def test_alias_mixin_alias_id_management(self):
|
||||
|
||||
Reference in New Issue
Block a user