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
204 lines
9.1 KiB
Python
204 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
|
|
from odoo import api, fields, models
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AliasMixinOptional(models.AbstractModel):
|
|
""" A mixin for models that handles underlying 'mail.alias' records to use
|
|
the mail gateway. Field is not mandatory and its creation is done dynamically
|
|
based on given 'alias_name', allowing to gradually populate the alias table
|
|
without having void aliases as when used with an inherits-like implementation.
|
|
"""
|
|
_name = 'mail.alias.mixin.optional'
|
|
_description = 'Email Aliases Mixin (light)'
|
|
ALIAS_WRITEABLE_FIELDS = ['alias_domain_id', 'alias_name', 'alias_contact', 'alias_defaults', 'alias_bounced_content']
|
|
|
|
alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=False)
|
|
alias_name = fields.Char(related='alias_id.alias_name', readonly=False)
|
|
alias_domain_id = fields.Many2one(
|
|
'mail.alias.domain', string='Alias Domain',
|
|
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', search='_search_alias_email')
|
|
|
|
@api.depends('alias_domain', 'alias_name')
|
|
def _compute_alias_email(self):
|
|
""" Alias email can be used in views, as it is Falsy when having no domain
|
|
or no name. Alias display name itself contains more info and cannot be
|
|
used as it is in views. """
|
|
self.alias_email = False
|
|
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
|
|
# --------------------------------------------------
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
""" Create aliases using sudo if an alias is required, notably if its
|
|
name is given. """
|
|
# prefetch company information, used for alias domain
|
|
company_fname = self._mail_get_company_field()
|
|
if company_fname:
|
|
company_id_default = self.default_get([company_fname]).get(company_fname) or self.env.company.id
|
|
company_prefetch_ids = {vals[company_fname] for vals in vals_list if vals.get(company_fname)}
|
|
company_prefetch_ids.add(company_id_default)
|
|
else:
|
|
company_id_default = self.env.company.id
|
|
company_prefetch_ids = {company_id_default}
|
|
|
|
# prepare all alias values
|
|
alias_vals_list, record_vals_list = [], []
|
|
for vals in vals_list:
|
|
if vals.get('alias_name'):
|
|
vals['alias_name'] = self.env['mail.alias']._sanitize_alias_name(vals['alias_name'])
|
|
if self._require_new_alias(vals):
|
|
company_id = vals.get(company_fname) or company_id_default
|
|
company = self.env['res.company'].with_prefetch(company_prefetch_ids).browse(company_id)
|
|
alias_vals, record_vals = self._alias_filter_fields(vals)
|
|
# generate record-agnostic base alias values
|
|
alias_vals.update(self.env[self._name].with_context(
|
|
default_alias_domain_id=company.alias_domain_id.id,
|
|
)._alias_get_creation_values())
|
|
alias_vals_list.append(alias_vals)
|
|
record_vals_list.append(record_vals)
|
|
|
|
# create all aliases
|
|
alias_ids = []
|
|
if alias_vals_list:
|
|
alias_ids = iter(self.env['mail.alias'].sudo().create(alias_vals_list).ids)
|
|
|
|
# update alias values in create vals directly
|
|
valid_vals_list = []
|
|
record_vals_iter = iter(record_vals_list)
|
|
for vals in vals_list:
|
|
if self._require_new_alias(vals):
|
|
record_vals = next(record_vals_iter)
|
|
record_vals['alias_id'] = next(alias_ids)
|
|
valid_vals_list.append(record_vals)
|
|
else:
|
|
valid_vals_list.append(vals)
|
|
|
|
records = super().create(valid_vals_list)
|
|
|
|
# update alias values with values coming from record, post-create to have
|
|
# access to all its values (notably its ID)
|
|
records_walias = records.filtered('alias_id')
|
|
for record in records_walias:
|
|
alias_values = record._alias_get_creation_values()
|
|
record.alias_id.sudo().write(alias_values)
|
|
|
|
return records
|
|
|
|
def write(self, vals):
|
|
""" Split writable fields of mail.alias and other fields alias fields will
|
|
write with sudo and the other normally. Also handle alias_domain_id
|
|
update. If alias does not exist and we try to set a name, create the
|
|
alias automatically. """
|
|
# create missing aliases
|
|
if vals.get('alias_name'):
|
|
alias_create_values = [
|
|
dict(
|
|
record._alias_get_creation_values(),
|
|
alias_name=self.env['mail.alias']._sanitize_alias_name(vals['alias_name']),
|
|
)
|
|
for record in self.filtered(lambda rec: not rec.alias_id)
|
|
]
|
|
if alias_create_values:
|
|
aliases = self.env['mail.alias'].sudo().create(alias_create_values)
|
|
for record, alias in zip(self.filtered(lambda rec: not rec.alias_id), aliases):
|
|
record.alias_id = alias.id
|
|
|
|
alias_vals, record_vals = self._alias_filter_fields(vals, filters=self.ALIAS_WRITEABLE_FIELDS)
|
|
if record_vals:
|
|
super().write(record_vals)
|
|
|
|
# synchronize alias domain if company environment changed
|
|
company_fname = self._mail_get_company_field()
|
|
if company_fname in vals:
|
|
alias_domain_values = self.filtered('alias_id')._alias_get_alias_domain_id()
|
|
for record, alias_domain_id in alias_domain_values.items():
|
|
record.sudo().alias_domain_id = alias_domain_id.id
|
|
|
|
if alias_vals and (record_vals or self.check_access_rights('write', raise_exception=False)):
|
|
self.mapped('alias_id').sudo().write(alias_vals)
|
|
|
|
return True
|
|
|
|
def unlink(self):
|
|
""" Delete the given records, and cascade-delete their corresponding alias. """
|
|
aliases = self.mapped('alias_id')
|
|
res = super().unlink()
|
|
aliases.sudo().unlink()
|
|
return res
|
|
|
|
@api.returns(None, lambda value: value[0])
|
|
def copy_data(self, default=None):
|
|
data = super().copy_data(default)[0]
|
|
for fields_not_writable in set(self.env['mail.alias']._fields.keys()) - set(self.ALIAS_WRITEABLE_FIELDS):
|
|
if fields_not_writable in data:
|
|
del data[fields_not_writable]
|
|
return [data]
|
|
|
|
@api.model
|
|
def _require_new_alias(self, record_vals):
|
|
""" Create only if no existing alias, and if a name is given, to avoid
|
|
creating inactive aliases (falsy name). """
|
|
return not record_vals.get('alias_id') and record_vals.get('alias_name')
|
|
|
|
# --------------------------------------------------
|
|
# MIXIN TOOL OVERRIDE METHODS
|
|
# --------------------------------------------------
|
|
|
|
def _alias_get_alias_domain_id(self):
|
|
""" Return alias domain value to synchronize with owner's company.
|
|
Implementing it with a compute is complicated, as its 'alias_domain_id'
|
|
is a field on 'mail.alias' model, coming from 'alias_id' field and due
|
|
to current implementation of the mixin, notably the create / write
|
|
overrides, compute is not called in all cases. We therefore use a tool
|
|
method to call in the mixin. """
|
|
alias_domain_values = {}
|
|
record_companies = self._mail_get_companies()
|
|
for record in self:
|
|
record_company = record_companies[record.id]
|
|
alias_domain_values[record] = (
|
|
record_company.alias_domain_id
|
|
or record.alias_domain_id or self.env.company.alias_domain_id
|
|
)
|
|
return alias_domain_values
|
|
|
|
def _alias_get_creation_values(self):
|
|
""" Return values to create an alias, or to write on the alias after its
|
|
creation.
|
|
"""
|
|
values = {
|
|
'alias_parent_thread_id': self.id if self.id else False,
|
|
'alias_parent_model_id': self.env['ir.model']._get_id(self._name),
|
|
}
|
|
if self.env.context.get('default_alias_domain_id'):
|
|
values['alias_domain_id'] = self.env.context['default_alias_domain_id']
|
|
return values
|
|
|
|
def _alias_filter_fields(self, values, filters=False):
|
|
""" Split the vals dict into two dictionnary of vals, one for alias
|
|
field and the other for other fields """
|
|
if not filters:
|
|
filters = self.env['mail.alias']._fields.keys()
|
|
alias_values, record_values = {}, {}
|
|
for fname in values.keys():
|
|
if fname in filters:
|
|
alias_values[fname] = values.get(fname)
|
|
else:
|
|
record_values[fname] = values.get(fname)
|
|
return alias_values, record_values
|