From 186cfb5ebd35d572d0f635cd7975de7cff0d1055 Mon Sep 17 00:00:00 2001 From: ryv-odoo Date: Thu, 28 Nov 2019 15:18:33 +0000 Subject: [PATCH] [REF] mail: make alias mixin multi-enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Purpose of this commit is to allow multi-create in alias mixin by correctly creating / updating aliases in batch. Task ID 1919277 Community PR odoo/odoo#41160 Enterprise PR odoo/enterprise#6983 Upgrade PR odoo/upgrade#872 Co-Authored-By: Rémy Voet Co-Authored-By: Thibault Delavallée --- addons/account/models/account_journal.py | 13 +++-- addons/crm/models/crm_team.py | 3 -- addons/mail/models/mail_alias.py | 10 ++-- addons/mail/models/mail_alias_mixin.py | 53 ++++++++++++++++----- addons/mail/security/ir.model.access.csv | 2 +- addons/project/security/ir.model.access.csv | 1 - addons/test_mail/tests/test_mail_gateway.py | 15 +++++- doc/reference/mixins.rst | 12 ++--- 8 files changed, 74 insertions(+), 35 deletions(-) diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index 04b68f6134a..68d72456151 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -35,6 +35,9 @@ class AccountJournal(models.Model): def _get_bank_statements_available_sources(self): return self.__get_bank_statements_available_sources() + def _default_alias_domain(self): + return self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain") + name = fields.Char(string='Journal Name', required=True) code = fields.Char(string='Short Code', size=5, required=True, help="Shorter name used for display. The journal entries of this journal will also be named using this prefix by default.") active = fields.Boolean(default=True, help="Set active to false to hide the Journal without removing it.") @@ -140,7 +143,7 @@ class AccountJournal(models.Model): # alias configuration for journals alias_id = fields.Many2one('mail.alias', string='Alias', copy=False) - alias_domain = fields.Char('Alias domain', compute='_compute_alias_domain', default=lambda self: self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")) + alias_domain = fields.Char('Alias domain', compute='_compute_alias_domain', default=_default_alias_domain, compute_sudo=True) alias_name = fields.Char('Alias Name', related='alias_id.alias_name', help="It creates draft invoices and bills by sending an email.", readonly=False) journal_group_ids = fields.Many2many('account.journal.group', @@ -170,7 +173,7 @@ class AccountJournal(models.Model): journal.suspense_account_id = False def _compute_alias_domain(self): - alias_domain = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain") + alias_domain = self._default_alias_domain() for record in self: record.alias_domain = alias_domain @@ -306,7 +309,7 @@ class AccountJournal(models.Model): accounts = self.search([('bank_account_id', '=', bank_account.id)]) if accounts <= self: bank_accounts += bank_account - self.mapped('alias_id').unlink() + self.mapped('alias_id').sudo().unlink() ret = super(AccountJournal, self).unlink() bank_accounts.unlink() return ret @@ -323,11 +326,11 @@ class AccountJournal(models.Model): self.ensure_one() alias_values = self._get_alias_values(type=vals.get('type') or self.type, alias_name=vals.get('alias_name')) if self.alias_id: - self.alias_id.write(alias_values) + self.alias_id.sudo().write(alias_values) else: alias_values['alias_model_id'] = self.env['ir.model']._get('account.move').id alias_values['alias_parent_model_id'] = self.env['ir.model']._get('account.journal').id - self.alias_id = self.env['mail.alias'].create(alias_values) + self.alias_id = self.env['mail.alias'].sudo().create(alias_values) if vals.get('alias_name'): # remove alias_name to avoid useless write on alias diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index 94062248003..695823a0d80 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -99,9 +99,6 @@ class Team(models.Model): def write(self, vals): result = super(Team, self).write(vals) - if 'use_leads' in vals or 'alias_defaults' in vals: - for team in self: - team.alias_id.write(team.get_alias_values()) if 'use_leads' in vals or 'use_opportunities' in vals: self.filtered(lambda team: not team.use_leads and not team.use_opportunities).alias_name = False return result diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py index ed36d8a3a63..e66cf303b98 100644 --- a/addons/mail/models/mail_alias.py +++ b/addons/mail/models/mail_alias.py @@ -27,6 +27,9 @@ class Alias(models.Model): _rec_name = 'alias_name' _order = 'alias_model_id, alias_name' + def _default_alias_domain(self): + return self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain") + 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 ") alias_model_id = fields.Many2one('ir.model', 'Aliased Model', required=True, ondelete="cascade", help="The model (Odoo Document Kind) to which this alias " @@ -48,8 +51,7 @@ class Alias(models.Model): 'Record Thread ID', help="Optional ID of a thread (record) to which all incoming messages will be attached, even " "if they did not reply to it. If set, this will disable the creation of new records completely.") - alias_domain = fields.Char('Alias domain', compute='_get_alias_domain', - default=lambda self: self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")) + alias_domain = fields.Char('Alias domain', compute='_compute_alias_domain', default=_default_alias_domain) alias_parent_model_id = fields.Many2one( 'ir.model', 'Parent Model', help="Parent model holding the alias. The model holding the alias reference " @@ -73,8 +75,8 @@ class Alias(models.Model): ('alias_unique', 'UNIQUE(alias_name)', 'Unfortunately this email alias is already used, please choose a unique one') ] - def _get_alias_domain(self): - alias_domain = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain") + def _compute_alias_domain(self): + alias_domain = self._default_alias_domain() for record in self: record.alias_domain = alias_domain diff --git a/addons/mail/models/mail_alias_mixin.py b/addons/mail/models/mail_alias_mixin.py index f60acdc677f..f753136669f 100644 --- a/addons/mail/models/mail_alias_mixin.py +++ b/addons/mail/models/mail_alias_mixin.py @@ -16,6 +16,7 @@ class AliasMixin(models.AbstractModel): _name = 'mail.alias.mixin' _inherits = {'mail.alias': 'alias_id'} _description = 'Email Aliases Mixin' + ALIAS_WRITEABLE_FIELDS = ['alias_name', 'alias_contact', 'alias_defaults'] alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=True) @@ -23,28 +24,54 @@ class AliasMixin(models.AbstractModel): # CRUD # -------------------------------------------------- - @api.model - def create(self, vals): - """ Create a record with ``vals``, and create a corresponding alias. """ - new_alias = not 'alias_id' in vals - if new_alias: - alias_vals, record_vals = self._alias_filter_fields(vals) - alias_vals.update(self._alias_get_creation_values()) - alias = self.env['mail.alias'].create(alias_vals) - record_vals['alias_id'] = alias.id + @api.model_create_multi + def create(self, vals_list): + """ Create a record with each ``vals`` or ``vals_list`` and create a corresponding alias. """ + valid_vals_list = [] + for vals in vals_list: + new_alias = not vals.get('alias_id') + if new_alias: + alias_vals, record_vals = self._alias_filter_fields(vals) + alias_vals.update(self._alias_get_creation_values()) + alias = self.env['mail.alias'].sudo().create(alias_vals) + record_vals['alias_id'] = alias.id + valid_vals_list.append(record_vals) + else: + valid_vals_list.append(vals) - record = super(AliasMixin, self).create(record_vals) - alias.sudo().write(record._alias_get_creation_values()) + records = super(AliasMixin, self).create(valid_vals_list) - return record + for record in records: + record.alias_id.sudo().write(record._alias_get_creation_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 """ + alias_vals, record_vals = self._alias_filter_fields(vals, filters=self.ALIAS_WRITEABLE_FIELDS) + if record_vals: + super(AliasMixin, self).write(record_vals) + 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(AliasMixin, self).unlink() - aliases.unlink() + aliases.sudo().unlink() return res + @api.returns(None, lambda value: value[0]) + def copy_data(self, default=None): + data = super(AliasMixin, self).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] + def _init_column(self, name): """ Create aliases for existing rows. """ super(AliasMixin, self)._init_column(name) diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 49c90572ebb..bc8d2cdb8fb 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -19,7 +19,7 @@ access_mail_channel_partner_portal,mail.channel.partner.portal,model_mail_channe access_mail_channel_partner_user,mail.channel.partner.user,model_mail_channel_partner,base.group_user,1,1,1,1 access_mail_moderation_user,mail.moderation.user,model_mail_moderation,base.group_user,1,1,1,1 access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0 -access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,1 +access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,0,0,0 access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1 access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0 access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,base.group_user,1,1,1,1 diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv index ad520ea8b56..f45dddfe5c3 100644 --- a/addons/project/security/ir.model.access.csv +++ b/addons/project/security/ir.model.access.csv @@ -18,7 +18,6 @@ access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.mode access_project_tags_all,project.project_tags_all,model_project_tags,,1,0,0,0 access_project_tags_manager,project.project_tags_manager,model_project_tags,project.group_project_manager,1,1,1,1 access_project_tags_portal,project_tags_portal,project.model_project_tags,base.group_portal,1,0,0,0 -access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1 access_mail_activity_type_project_manager,mail.activity.type.project.manager,mail.model_mail_activity_type,project.group_project_manager,1,1,1,1 access_account_analytic_account_user,account.analytic.account,analytic.model_account_analytic_account,project.group_project_user,1,0,0,0 access_account_analytic_account_manager,account.analytic.account,analytic.model_account_analytic_account,project.group_project_manager,1,1,1,1 diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index 41db5b25f2e..2a585118d59 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -89,12 +89,23 @@ class TestMailAlias(TestMailCommon): self.assertEqual(record.alias_id.alias_contact, 'followers') record.write({ - 'alias_force_thread_id': False, + 'alias_name': 'better.alias.test', 'alias_defaults': "{'default_name': 'defaults'}" }) - self.assertFalse(record.alias_id.alias_force_thread_id) + self.assertEqual(record.alias_id.alias_name, 'better.alias.test') self.assertEqual(record.alias_id.alias_defaults, "{'default_name': 'defaults'}") + with self.assertRaises(exceptions.AccessError): + record.write({ + 'alias_force_thread_id': 0, + }) + + with self.assertRaises(exceptions.AccessError): + record.write({ + 'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id, + }) + + def test_alias_setup(self): alias = self.env['mail.alias'].create({ 'alias_model_id': self.env['ir.model']._get('mail.test').id, diff --git a/doc/reference/mixins.rst b/doc/reference/mixins.rst index c80ffc52c79..d290e418652 100644 --- a/doc/reference/mixins.rst +++ b/doc/reference/mixins.rst @@ -556,7 +556,7 @@ specific overrides to work correctly. These overrides will specify the values of the created alias, like the kind of record it must create and possibly some default values these records may have depending on the parent object: -.. method:: get_alias_model_name(vals) +.. method:: _get_alias_model_name(vals) Return the model name for the alias. Incoming emails that are not replies to existing records will cause the creation of a new record @@ -568,7 +568,7 @@ some default values these records may have depending on the parent object: :return: model name :rtype: str -.. method:: get_alias_values() +.. method:: _get_alias_values() Return values to create an alias, or to write on the alias after its creation. While not completely mandatory, it is usually required to make @@ -579,7 +579,7 @@ some default values these records may have depending on the parent object: :return: dictionnary of values that will be written to the new alias :rtype: dict -The ``get_alias_values()`` override is particularly interesting as it allows you +The ``_get_alias_values()`` override is particularly interesting as it allows you to modify the behaviour of your aliases easily. Among the fields that can be set on the alias, the following are of particular interest: @@ -632,13 +632,13 @@ you to make your alias easily configurable from the record's form view. alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=True) - def get_alias_model_name(self, vals): + def _get_alias_model_name(self, vals): """ Specify the model that will get created when the alias receives a message """ return 'business.expense' - def get_alias_values(self): + def _get_alias_values(self): """ Specify some default values that will be set in the alias at its creation """ - values = super(BusinessTrip, self).get_alias_values() + values = super(BusinessTrip, self)._get_alias_values() # alias_defaults holds a dictionnary that will be written # to all records created by this alias #