From caeda18becedf6bee6a876c034fb04467ef95895 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 14:29:49 +0000 Subject: [PATCH 01/13] [IMP] base, various: support batch creation Done for res.partner.bank, res.company and board models. Some override may have been ignored because heavily linked to business code (like company in stock). See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/base_iban/models/res_partner_bank.py | 19 +++++----- addons/board/models/board.py | 4 +- addons/l10n_fr/models/res_company.py | 17 +++++---- addons/l10n_fr_pos_cert/models/res_company.py | 17 +++++---- addons/product/models/res_company.py | 38 ++++++++++--------- addons/web/models/models.py | 10 ++--- 6 files changed, 56 insertions(+), 49 deletions(-) diff --git a/addons/base_iban/models/res_partner_bank.py b/addons/base_iban/models/res_partner_bank.py index 2c9f4f868b9..dfe1031acd5 100644 --- a/addons/base_iban/models/res_partner_bank.py +++ b/addons/base_iban/models/res_partner_bank.py @@ -67,15 +67,16 @@ class ResPartnerBank(models.Model): raise UserError(_("Cannot compute the BBAN because the account number is not an IBAN.")) return get_bban_from_iban(self.acc_number) - @api.model - def create(self, vals): - if vals.get('acc_number'): - try: - validate_iban(vals['acc_number']) - vals['acc_number'] = pretty_iban(normalize_iban(vals['acc_number'])) - except ValidationError: - pass - return super(ResPartnerBank, self).create(vals) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get('acc_number'): + try: + validate_iban(vals['acc_number']) + vals['acc_number'] = pretty_iban(normalize_iban(vals['acc_number'])) + except ValidationError: + pass + return super(ResPartnerBank, self).create(vals_list) def write(self, vals): if vals.get('acc_number'): diff --git a/addons/board/models/board.py b/addons/board/models/board.py index a3355755b2f..738f9850e08 100644 --- a/addons/board/models/board.py +++ b/addons/board/models/board.py @@ -9,8 +9,8 @@ class Board(models.AbstractModel): _description = "Board" _auto = False - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): return self @api.model diff --git a/addons/l10n_fr/models/res_company.py b/addons/l10n_fr/models/res_company.py index 4088a1afac4..6235d207b9a 100644 --- a/addons/l10n_fr/models/res_company.py +++ b/addons/l10n_fr/models/res_company.py @@ -23,14 +23,15 @@ class ResCompany(models.Model): return False return self.country_id and self.country_id.code in self._get_unalterable_country() - @api.model - def create(self, vals): - company = super(ResCompany, self).create(vals) - #when creating a new french company, create the securisation sequence as well - if company._is_accounting_unalterable(): - sequence_fields = ['l10n_fr_closing_sequence_id'] - company._create_secure_sequence(sequence_fields) - return company + @api.model_create_multi + def create(self, vals_list): + companies = super().create(vals_list) + for company in companies: + #when creating a new french company, create the securisation sequence as well + if company._is_accounting_unalterable(): + sequence_fields = ['l10n_fr_closing_sequence_id'] + company._create_secure_sequence(sequence_fields) + return companies def write(self, vals): res = super(ResCompany, self).write(vals) diff --git a/addons/l10n_fr_pos_cert/models/res_company.py b/addons/l10n_fr_pos_cert/models/res_company.py index c701c6f348e..e509dd52005 100644 --- a/addons/l10n_fr_pos_cert/models/res_company.py +++ b/addons/l10n_fr_pos_cert/models/res_company.py @@ -26,14 +26,15 @@ class ResCompany(models.Model): l10n_fr_pos_cert_sequence_id = fields.Many2one('ir.sequence') - @api.model - def create(self, vals): - company = super(ResCompany, self).create(vals) - #when creating a new french company, create the securisation sequence as well - if company._is_accounting_unalterable(): - sequence_fields = ['l10n_fr_pos_cert_sequence_id'] - company._create_secure_sequence(sequence_fields) - return company + @api.model_create_multi + def create(self, vals_list): + companies = super().create(vals_list) + for company in companies: + #when creating a new french company, create the securisation sequence as well + if company._is_accounting_unalterable(): + sequence_fields = ['l10n_fr_pos_cert_sequence_id'] + company._create_secure_sequence(sequence_fields) + return companies def write(self, vals): res = super(ResCompany, self).write(vals) diff --git a/addons/product/models/res_company.py b/addons/product/models/res_company.py index 30ae4e95931..e9420ed5133 100644 --- a/addons/product/models/res_company.py +++ b/addons/product/models/res_company.py @@ -7,24 +7,28 @@ from odoo import api, models, _ class ResCompany(models.Model): _inherit = "res.company" - @api.model - def create(self, vals): - new_company = super(ResCompany, self).create(vals) + @api.model_create_multi + def create(self, vals_list): + companies = super(ResCompany, self).create(vals_list) ProductPricelist = self.env['product.pricelist'] - pricelist = ProductPricelist.search([('currency_id', '=', new_company.currency_id.id), ('company_id', '=', False)], limit=1) - if not pricelist: - params = {'currency': new_company.currency_id.name} - pricelist = ProductPricelist.create({ - 'name': _("Default %(currency)s pricelist") % params, - 'currency_id': new_company.currency_id.id, - }) - self.env['ir.property']._set_default( - 'property_product_pricelist', - 'res.partner', - pricelist, - new_company, - ) - return new_company + for new_company in companies: + pricelist = ProductPricelist.search([ + ('currency_id', '=', new_company.currency_id.id), + ('company_id', '=', False) + ], limit=1) + if not pricelist: + params = {'currency': new_company.currency_id.name} + pricelist = ProductPricelist.create({ + 'name': _("Default %(currency)s pricelist") % params, + 'currency_id': new_company.currency_id.id, + }) + self.env['ir.property']._set_default( + 'property_product_pricelist', + 'res.partner', + pricelist, + new_company, + ) + return companies def write(self, values): # When we modify the currency of the company, we reflect the change on the list0 pricelist, if diff --git a/addons/web/models/models.py b/addons/web/models/models.py index 092dd9a1681..ba431b8addc 100644 --- a/addons/web/models/models.py +++ b/addons/web/models/models.py @@ -755,13 +755,13 @@ class Base(models.AbstractModel): class ResCompany(models.Model): _inherit = 'res.company' - @api.model - def create(self, values): - res = super().create(values) + @api.model_create_multi + def create(self, vals_list): + companies = super().create(vals_list) style_fields = {'external_report_layout_id', 'font', 'primary_color', 'secondary_color'} - if not style_fields.isdisjoint(values): + if any(not style_fields.isdisjoint(values) for values in vals_list): self._update_asset_style() - return res + return companies def write(self, values): res = super().write(values) From 646457d761571b1fd32d82b6529f6a05306085ff Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 23 Oct 2020 14:54:07 +0000 Subject: [PATCH 02/13] [IMP] mail, digest, fetchmail, hr_holidays, snailmail: support batch creation See merge commit for more details. Note that mail.alias model will be done in a separate commit. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/digest/models/digest.py | 13 ++--- addons/fetchmail/models/fetchmail.py | 6 +-- .../models/mail_message_subtype.py | 11 ++-- addons/mail/models/mail_activity.py | 53 +++++++++--------- addons/mail/models/mail_channel.py | 39 +++++++------- addons/mail/models/mail_message_subtype.py | 6 +-- addons/snailmail/models/snailmail_letter.py | 54 ++++++++++--------- 7 files changed, 95 insertions(+), 87 deletions(-) diff --git a/addons/digest/models/digest.py b/addons/digest/models/digest.py index 6d780f8095b..97a8e25001d 100644 --- a/addons/digest/models/digest.py +++ b/addons/digest/models/digest.py @@ -72,12 +72,13 @@ class Digest(models.Model): def _onchange_periodicity(self): self.next_run_date = self._get_next_run_date() - @api.model - def create(self, vals): - digest = super(Digest, self).create(vals) - if not digest.next_run_date: - digest.next_run_date = digest._get_next_run_date() - return digest + @api.model_create_multi + def create(self, vals_list): + digests = super().create(vals_list) + for digest in digests: + if not digest.next_run_date: + digest.next_run_date = digest._get_next_run_date() + return digests # ------------------------------------------------------------ # ACTIONS diff --git a/addons/fetchmail/models/fetchmail.py b/addons/fetchmail/models/fetchmail.py index 0850f5e19ee..2e6c72333fa 100644 --- a/addons/fetchmail/models/fetchmail.py +++ b/addons/fetchmail/models/fetchmail.py @@ -80,9 +80,9 @@ Example configuration for the postfix mta running locally: odoo_mailgate: "|/path/to/odoo-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s" """ % conf - @api.model - def create(self, values): - res = super(FetchmailServer, self).create(values) + @api.model_create_multi + def create(self, vals_list): + res = super(FetchmailServer, self).create(vals_list) self._update_cron() return res diff --git a/addons/hr_holidays/models/mail_message_subtype.py b/addons/hr_holidays/models/mail_message_subtype.py index f650ec1a898..613827e0629 100644 --- a/addons/hr_holidays/models/mail_message_subtype.py +++ b/addons/hr_holidays/models/mail_message_subtype.py @@ -34,11 +34,12 @@ class MailMessageSubtype(models.Model): }) return department_subtype - @api.model - def create(self, vals): - result = super(MailMessageSubtype, self).create(vals) - if result.res_model in ['hr.leave', 'hr.leave.allocation']: - result._update_department_subtype() + @api.model_create_multi + def create(self, vals_list): + result = super(MailMessageSubtype, self).create(vals_list) + result.filtered( + lambda st: st.res_model in ['hr.leave', 'hr.leave.allocation'] + )._update_department_subtype() return result def write(self, vals): diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 1ac59a08f3c..682e7dcddc6 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -346,34 +346,35 @@ class MailActivity(models.Model): # ORM overrides # ------------------------------------------------------ - @api.model - def create(self, values): - activity = super(MailActivity, self).create(values) - need_sudo = False - try: # in multicompany, reading the partner might break - partner_id = activity.user_id.partner_id.id - except exceptions.AccessError: - need_sudo = True - partner_id = activity.user_id.sudo().partner_id.id + @api.model_create_multi + def create(self, vals_list): + activities = super(MailActivity, self).create(vals_list) + for activity in activities: + need_sudo = False + try: # in multicompany, reading the partner might break + partner_id = activity.user_id.partner_id.id + except exceptions.AccessError: + need_sudo = True + partner_id = activity.user_id.sudo().partner_id.id - # send a notification to assigned user; in case of manually done activity also check - # target has rights on document otherwise we prevent its creation. Automated activities - # are checked since they are integrated into business flows that should not crash. - if activity.user_id != self.env.user: - if not activity.automated: - activity._check_access_assignation() - if not self.env.context.get('mail_activity_quick_update', False): - if need_sudo: - activity.sudo().action_notify() - else: - activity.action_notify() + # send a notification to assigned user; in case of manually done activity also check + # target has rights on document otherwise we prevent its creation. Automated activities + # are checked since they are integrated into business flows that should not crash. + if activity.user_id != self.env.user: + if not activity.automated: + activity._check_access_assignation() + if not self.env.context.get('mail_activity_quick_update', False): + if need_sudo: + activity.sudo().action_notify() + else: + activity.action_notify() - self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[partner_id]) - if activity.date_deadline <= fields.Date.today(): - self.env['bus.bus'].sendone( - (self._cr.dbname, 'res.partner', activity.user_id.partner_id.id), - {'type': 'activity_updated', 'activity_created': True}) - return activity + self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[partner_id]) + if activity.date_deadline <= fields.Date.today(): + self.env['bus.bus'].sendone( + (self._cr.dbname, 'res.partner', activity.user_id.partner_id.id), + {'type': 'activity_updated', 'activity_created': True}) + return activities def write(self, values): if values.get('user_id'): diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 10146ff98e8..5fdc1959bfb 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -204,26 +204,27 @@ class Channel(models.Model): else: self.moderator_ids |= self.env.user - @api.model - def create(self, vals): - # ensure image at quick create - if not vals.get('image_128'): - defaults = self.default_get(['image_128']) - vals['image_128'] = defaults['image_128'] + @api.model_create_multi + def create(self, vals_list): + defaults = self.default_get(['image_128']) + for vals in vals_list: + # ensure image at quick create + if not vals.get('image_128'): + vals['image_128'] = defaults['image_128'] # Create channel and alias - channel = super(Channel, self.with_context( + channels = super(Channel, self.with_context( mail_create_nolog=True, mail_create_nosubscribe=True) - ).create(vals) + ).create(vals_list) - if vals.get('group_ids'): - channel._subscribe_users() + channels._subscribe_users() # make channel listen itself: posting on a channel notifies the channel if not self._context.get('mail_channel_noautofollow'): - channel.message_subscribe(channel_ids=[channel.id]) + for channel in channels: + channel.message_subscribe(channel_ids=[channel.id]) - return channel + return channels def unlink(self): # Delete mail.channel @@ -266,13 +267,15 @@ class Channel(models.Model): def _subscribe_users(self): to_create = [] for mail_channel in self: - partners_to_add = mail_channel.group_ids.users.partner_id - mail_channel.channel_partner_ids - to_create += [{ - 'channel_id': mail_channel.id, - 'partner_id': partner.id, - } for partner in partners_to_add] + if mail_channel.group_ids: + partners_to_add = mail_channel.group_ids.users.partner_id - mail_channel.channel_partner_ids + to_create += [{ + 'channel_id': mail_channel.id, + 'partner_id': partner.id, + } for partner in partners_to_add] - self.env['mail.channel.partner'].create(to_create) + if to_create: + self.env['mail.channel.partner'].create(to_create) def action_follow(self): self.ensure_one() diff --git a/addons/mail/models/mail_message_subtype.py b/addons/mail/models/mail_message_subtype.py index 43922018e89..ee754acdbed 100644 --- a/addons/mail/models/mail_message_subtype.py +++ b/addons/mail/models/mail_message_subtype.py @@ -41,10 +41,10 @@ class MailMessageSubtype(models.Model): sequence = fields.Integer('Sequence', default=1, help="Used to order subtypes.") hidden = fields.Boolean('Hidden', help="Hide the subtype in the follower options") - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): self.clear_caches() - return super(MailMessageSubtype, self).create(vals) + return super(MailMessageSubtype, self).create(vals_list) def write(self, vals): self.clear_caches() diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py index 2eb19271b3c..cc9e2ccd851 100644 --- a/addons/snailmail/models/snailmail_letter.py +++ b/addons/snailmail/models/snailmail_letter.py @@ -77,35 +77,37 @@ class SnailmailLetter(models.Model): for res in self: res.reference = "%s,%s" % (res.model, res.res_id) - @api.model - def create(self, vals): - msg_id = self.env[vals['model']].browse(vals['res_id']).message_post( - body=_("Letter sent by post with Snailmail"), - message_type='snailmail' - ) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + msg_id = self.env[vals['model']].browse(vals['res_id']).message_post( + body=_("Letter sent by post with Snailmail"), + message_type='snailmail', + ) - partner_id = self.env['res.partner'].browse(vals['partner_id']) - vals.update({ - 'message_id': msg_id.id, - 'street': partner_id.street, - 'street2': partner_id.street2, - 'zip': partner_id.zip, - 'city': partner_id.city, - 'state_id': partner_id.state_id.id, - 'country_id': partner_id.country_id.id, - }) - letter = super(SnailmailLetter, self).create(vals) + partner_id = self.env['res.partner'].browse(vals['partner_id']) + vals.update({ + 'message_id': msg_id.id, + 'street': partner_id.street, + 'street2': partner_id.street2, + 'zip': partner_id.zip, + 'city': partner_id.city, + 'state_id': partner_id.state_id.id, + 'country_id': partner_id.country_id.id, + }) + letters = super().create(vals_list) - self.env['mail.notification'].sudo().create({ - 'mail_message_id': msg_id.id, - 'res_partner_id': partner_id.id, - 'notification_type': 'snail', - 'letter_id': letter.id, - 'is_read': True, # discard Inbox notification - 'notification_status': 'ready', - }) + for letter in letters: + self.env['mail.notification'].sudo().create({ + 'mail_message_id': letter.message_id.id, + 'res_partner_id': letter.partner_id.id, + 'notification_type': 'snail', + 'letter_id': letter.id, + 'is_read': True, # discard Inbox notification + 'notification_status': 'ready', + }) - return letter + return letters def _fetch_attachment(self): """ From 18b49836b76d9dc9a21b4e11541c5d2eb074bf3e Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 13:18:08 +0000 Subject: [PATCH 03/13] [REF] mail: make alias creation work in batch In this commit we rewrite part of alias name uniqueness check as well as alias mixin alias creation to enable batch create of * alias records (mail.alias); * records using aliases through mail.alias.mixin; See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/mail/models/ir_config_parameter.py | 4 +- addons/mail/models/mail_alias.py | 51 +++++++++++++++-------- addons/mail/models/mail_alias_mixin.py | 21 ++++++++-- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/addons/mail/models/ir_config_parameter.py b/addons/mail/models/ir_config_parameter.py index 40e153ba2cf..a3f67ce1331 100644 --- a/addons/mail/models/ir_config_parameter.py +++ b/addons/mail/models/ir_config_parameter.py @@ -11,11 +11,11 @@ class IrConfigParameter(models.Model): def create(self, vals_list): for vals in vals_list: if vals.get('key') in ['mail.bounce.alias', 'mail.catchall.alias']: - vals['value'] = self.env['mail.alias']._clean_and_check_unique(vals.get('value')) + vals['value'] = self.env['mail.alias']._clean_and_check_unique([vals.get('value')])[0] return super().create(vals_list) def write(self, vals): for parameter in self: if 'value' in vals and parameter.key in ['mail.bounce.alias', 'mail.catchall.alias'] and vals['value'] != parameter.value: - vals['value'] = self.env['mail.alias']._clean_and_check_unique(vals.get('value')) + vals['value'] = self.env['mail.alias']._clean_and_check_unique([vals.get('value')])[0] return super().write(vals) diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py index 246451724c5..b1f61503476 100644 --- a/addons/mail/models/mail_alias.py +++ b/addons/mail/models/mail_alias.py @@ -107,22 +107,34 @@ class Alias(models.Model): except Exception: raise ValidationError(_('Invalid expression, it must be a literal python dictionary definition e.g. "{\'field\': \'value\'}"')) - @api.model - def create(self, vals): - """ Creates an email.alias record according to the values provided in ``vals``, - with 2 alterations: the ``alias_name`` value may be cleaned by replacing - certain unsafe characters, and the ``alias_model_id`` value will set to the - model ID of the ``model_name`` context value, if provided. Also, it raises - UserError if given alias name is already assigned. + @api.model_create_multi + def create(self, vals_list): + """ Creates email.alias records according to the values provided in + ``vals`` with 1 alteration: + + * ``alias_name`` value may be cleaned by replacing certain unsafe + characters; + + :raise UserError: if given alias_name is already assigned or there are + duplicates in given vals_list; """ - if vals.get('alias_name'): - vals['alias_name'] = self._clean_and_check_unique(vals.get('alias_name')) - return super(Alias, self).create(vals) + alias_names = [vals['alias_name'] for vals in vals_list if vals.get('alias_name')] + if alias_names: + sanitized_names = self._clean_and_check_unique(alias_names) + for vals in vals_list: + if vals.get('alias_name'): + vals['alias_name'] = sanitized_names[alias_names.index(vals['alias_name'])] + return super(Alias, self).create(vals_list) def write(self, vals): """"Raises UserError if given alias name is already assigned""" if vals.get('alias_name') and self.ids: - vals['alias_name'] = self._clean_and_check_unique(vals.get('alias_name')) + if len(self) > 1: + raise UserError(_( + 'Email alias %(alias_name)s cannot be used on %(count)d records at the same time. Please update records one by one.', + alias_name=vals['alias_name'], count=len(self) + )) + vals['alias_name'] = self._clean_and_check_unique([vals.get('alias_name')])[0] return super(Alias, self).write(vals) def name_get(self): @@ -140,22 +152,25 @@ class Alias(models.Model): res.append((record['id'], _("Inactive Alias"))) return res - def _clean_and_check_unique(self, name): + def _clean_and_check_unique(self, names): """When an alias name appears to already be an email, we keep the local part only. A sanitizing / cleaning is also performed on the name. If name already exists an UserError is raised. """ - sanitized_name = remove_accents(name).lower().split('@')[0] - sanitized_name = re.sub(r'[^\w+.]+', '-', sanitized_name) - sanitized_name = sanitized_name.encode('ascii', errors='replace').decode() + sanitized_names = [] + for name in names: + sanitized_name = remove_accents(name).lower().split('@')[0] + sanitized_name = re.sub(r'[^\w+.]+', '-', sanitized_name) + sanitized_name = sanitized_name.encode('ascii', errors='replace').decode() + sanitized_names.append(sanitized_name) catchall_alias = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.alias') bounce_alias = self.env['ir.config_parameter'].sudo().get_param('mail.bounce.alias') - domain = [('alias_name', '=', sanitized_name)] + domain = [('alias_name', 'in', sanitized_names)] if self: domain += [('id', 'not in', self.ids)] - if sanitized_name in [catchall_alias, bounce_alias] or self.search_count(domain): + if any(sanitized_name in [catchall_alias, bounce_alias] for sanitized_name in sanitized_names) or self.search_count(domain): raise UserError(_('The e-mail alias is already used. Please enter another one.')) - return sanitized_name + return sanitized_names def open_document(self): if not self.alias_model_id or not self.alias_force_thread_id: diff --git a/addons/mail/models/mail_alias_mixin.py b/addons/mail/models/mail_alias_mixin.py index 12eb8d4406a..e38c8ccaab7 100644 --- a/addons/mail/models/mail_alias_mixin.py +++ b/addons/mail/models/mail_alias_mixin.py @@ -27,14 +27,29 @@ class AliasMixin(models.AbstractModel): @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 = [] + # prepare all alias values + alias_vals_list, record_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 + 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: + new_alias = not vals.get('alias_id') + if new_alias: + 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) From e045e5d925b6e16997e06c4c80ef827564df50df Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 4 Aug 2020 15:46:56 +0000 Subject: [PATCH 04/13] [IMP] snailmail: create notifications in batch UDPATE ME Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/snailmail/models/snailmail_letter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py index cc9e2ccd851..cdee59e1b15 100644 --- a/addons/snailmail/models/snailmail_letter.py +++ b/addons/snailmail/models/snailmail_letter.py @@ -97,8 +97,9 @@ class SnailmailLetter(models.Model): }) letters = super().create(vals_list) + notification_vals = [] for letter in letters: - self.env['mail.notification'].sudo().create({ + notification_vals.append({ 'mail_message_id': letter.message_id.id, 'res_partner_id': letter.partner_id.id, 'notification_type': 'snail', @@ -107,6 +108,8 @@ class SnailmailLetter(models.Model): 'notification_status': 'ready', }) + self.env['mail.notification'].sudo().create(notification_vals) + return letters def _fetch_attachment(self): From 3dc757008f3f28bfab501b1dd36ede595a570c82 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 23 Oct 2020 14:54:20 +0000 Subject: [PATCH 05/13] [IMP] mass_mailing: support batch creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 Co-Authored-By: Thibault Delavallée Co-Authored-By: Victor Feyens --- addons/mass_mailing/models/mailing.py | 16 +++++++++------- addons/mass_mailing/models/mailing_contact.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py index 06483415ab4..5715129952c 100644 --- a/addons/mass_mailing/models/mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -293,13 +293,15 @@ class MassMailing(models.Model): # ORM # ------------------------------------------------------ - @api.model - def create(self, values): - if values.get('subject') and not values.get('name'): - values['name'] = "%s %s" % (values['subject'], datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT)) - if values.get('body_html'): - values['body_html'] = self._convert_inline_images_to_urls(values['body_html']) - return super(MassMailing, self).create(values) + @api.model_create_multi + def create(self, vals_list): + now = fields.Datetime.now() + for values in vals_list: + if values.get('subject') and not values.get('name'): + values['name'] = "%s %s" % (values['subject'], now) + if values.get('body_html'): + values['body_html'] = self._convert_inline_images_to_urls(values['body_html']) + return super().create(vals_list) def write(self, values): if values.get('body_html'): diff --git a/addons/mass_mailing/models/mailing_contact.py b/addons/mass_mailing/models/mailing_contact.py index 6ebd1132820..16a13125359 100644 --- a/addons/mass_mailing/models/mailing_contact.py +++ b/addons/mass_mailing/models/mailing_contact.py @@ -27,15 +27,17 @@ class MassMailingContactListRel(models.Model): 'A contact cannot be subscribed multiple times to the same list!') ] - @api.model - def create(self, vals): - if 'opt_out' in vals: - vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now() - return super(MassMailingContactListRel, self).create(vals) + @api.model_create_multi + def create(self, vals_list): + now = fields.Datetime.now() + for vals in vals_list: + if 'opt_out' in vals: + vals['unsubscription_date'] = now if vals['opt_out'] else False + return super().create(vals_list) def write(self, vals): if 'opt_out' in vals: - vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now() + vals['unsubscription_date'] = fields.Datetime.now() if vals['opt_out'] else False return super(MassMailingContactListRel, self).write(vals) From b15d20d71323b391b83035d862aa2b78f89dcbc6 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 12:13:05 +0000 Subject: [PATCH 06/13] [IMP] crm_*, sales_team: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/crm_iap_lead_website/models/crm_reveal_rule.py | 6 +++--- addons/sales_team/models/crm_team.py | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/addons/crm_iap_lead_website/models/crm_reveal_rule.py b/addons/crm_iap_lead_website/models/crm_reveal_rule.py index 698a7c64ce4..a2c824d47e6 100644 --- a/addons/crm_iap_lead_website/models/crm_reveal_rule.py +++ b/addons/crm_iap_lead_website/models/crm_reveal_rule.py @@ -82,11 +82,11 @@ class CRMRevealRule(models.Model): (self._cr.dbname, 'res.partner', self.env.user.partner_id.id), {'type': 'simple_notification', 'title': _('Missing Library'), 'message': message, 'sticky': True, 'warning': True}) - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): self.clear_caches() # Clear the cache in order to recompute _get_active_rules self._assert_geoip() - return super(CRMRevealRule, self).create(vals) + return super().create(vals_list) def write(self, vals): fields_set = { diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index 104baaf1aee..fd15b3f5e90 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -85,12 +85,11 @@ class CrmTeam(models.Model): # CRUD # ------------------------------------------------------------ - @api.model - def create(self, values): - team = super(CrmTeam, self.with_context(mail_create_nosubscribe=True)).create(values) - if values.get('member_ids'): - team._add_members_to_favorites() - return team + @api.model_create_multi + def create(self, vals_list): + teams = super(CrmTeam, self.with_context(mail_create_nosubscribe=True)).create(vals_list) + teams.filtered(lambda t: t.member_ids)._add_members_to_favorites() + return teams def write(self, values): res = super(CrmTeam, self).write(values) From 8016591353ea5774be4dcc0c0097919f07d3b78f Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 13:24:45 +0000 Subject: [PATCH 07/13] [IMP] event(_*): support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/event/models/event_event.py | 20 +++++++++++--------- addons/website_event/models/event_event.py | 10 +++++----- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py index 942fa5f4328..e78651d9a55 100644 --- a/addons/event/models/event_event.py +++ b/addons/event/models/event_event.py @@ -481,16 +481,18 @@ class EventEvent(models.Model): def _read_group_stage_ids(self, stages, domain, order): return self.env['event.stage'].search([]) - @api.model - def create(self, vals): - # Temporary fix for ``seats_limited`` and ``date_tz`` required fields (see ``_compute_from_event_type`` - vals.update(self._sync_required_computed(vals)) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + # Temporary fix for ``seats_limited`` and ``date_tz`` required fields (see ``_compute_from_event_type`` + vals.update(self._sync_required_computed(vals)) - res = super(EventEvent, self).create(vals) - if res.organizer_id: - res.message_subscribe([res.organizer_id.id]) - res.flush() - return res + events = super(EventEvent, self).create(vals_list) + for res in events: + if res.organizer_id: + res.message_subscribe([res.organizer_id.id]) + events.flush() + return events def write(self, vals): res = super(EventEvent, self).write(vals) diff --git a/addons/website_event/models/event_event.py b/addons/website_event/models/event_event.py index 53a433df6a4..5dfcaa5a5c0 100644 --- a/addons/website_event/models/event_event.py +++ b/addons/website_event/models/event_event.py @@ -158,11 +158,11 @@ class Event(models.Model): # CRUD # ------------------------------------------------------------ - @api.model - def create(self, vals): - res = super(Event, self).create(vals) - res._update_website_menus() - return res + @api.model_create_multi + def create(self, vals_list): + events = super().create(vals_list) + events._update_website_menus() + return events def write(self, vals): menus_state_by_field = self._split_menus_state_by_field() From 9c87db3c6201d0ea3ce012fc8baa28509dc92099 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 23 Oct 2020 14:56:47 +0000 Subject: [PATCH 08/13] [IMP] gamification: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/gamification/models/badge.py | 9 +++++---- addons/gamification/models/challenge.py | 18 +++++++++--------- addons/gamification/models/goal.py | 24 +++++++++++++----------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py index 81a9d0564e8..9dd276096da 100644 --- a/addons/gamification/models/badge.py +++ b/addons/gamification/models/badge.py @@ -52,10 +52,11 @@ class BadgeUser(models.Model): return True - @api.model - def create(self, vals): - self.env['gamification.badge'].browse(vals['badge_id']).check_granting() - return super(BadgeUser, self).create(vals) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + self.env['gamification.badge'].browse(vals['badge_id']).check_granting() + return super().create(vals_list) class GamificationBadge(models.Model): diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index 2a1be3c6b88..02b13f1ba89 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -154,18 +154,18 @@ class Challenge(models.Model): return template.id if template else False - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): """Overwrite the create method to add the user of groups""" + for vals in vals_list: + if vals.get('user_domain'): + users = self._get_challenger_users(ustr(vals.get('user_domain'))) - if vals.get('user_domain'): - users = self._get_challenger_users(ustr(vals.get('user_domain'))) + if not vals.get('user_ids'): + vals['user_ids'] = [] + vals['user_ids'].extend((4, user.id) for user in users) - if not vals.get('user_ids'): - vals['user_ids'] = [] - vals['user_ids'].extend((4, user.id) for user in users) - - return super(Challenge, self).create(vals) + return super().create(vals_list) def write(self, vals): if vals.get('user_domain'): diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index ef48542a067..dad5b3b04fe 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -122,14 +122,16 @@ class GoalDefinition(models.Model): error=e )) - @api.model - def create(self, vals): - definition = super(GoalDefinition, self).create(vals) - if definition.computation_mode in ('count', 'sum'): - definition._check_domain_validity() - if vals.get('field_id'): - definition._check_model_validity() - return definition + @api.model_create_multi + def create(self, vals_list): + definitions = super(GoalDefinition, self).create(vals_list) + definitions.filtered_domain([ + ('computation_mode', 'in', ['count', 'sum']), + ])._check_domain_validity() + definitions.filtered_domain([ + ('field_id', '=', 'True'), + ])._check_model_validity() + return definitions def write(self, vals): res = super(GoalDefinition, self).write(vals) @@ -386,9 +388,9 @@ class Goal(models.Model): next goal update.""" return self.write({'state': 'inprogress'}) - @api.model - def create(self, vals): - return super(Goal, self.with_context(no_remind_goal=True)).create(vals) + @api.model_create_multi + def create(self, vals_list): + return super(Goal, self.with_context(no_remind_goal=True)).create(vals_list) def write(self, vals): """Overwrite the write method to update the last_update field to today From c1ac7b89f99481e9cece238807da83c2c43db36f Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 13:28:06 +0000 Subject: [PATCH 09/13] [IMP] rating: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/rating/models/rating.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py index c28e19d5890..bdaf8dbe3d9 100644 --- a/addons/rating/models/rating.py +++ b/addons/rating/models/rating.py @@ -125,11 +125,12 @@ class Rating(models.Model): else: rating.rating_text = 'no_rating' - @api.model - def create(self, values): - if values.get('res_model_id') and values.get('res_id'): - values.update(self._find_parent_data(values)) - return super(Rating, self).create(values) + @api.model_create_multi + def create(self, vals_list): + for values in vals_list: + if values.get('res_model_id') and values.get('res_id'): + values.update(self._find_parent_data(values)) + return super().create(vals_list) def write(self, values): if values.get('res_model_id') and values.get('res_id'): From a94cb65ef829f0e423b4a306b4a529555d9dd07c Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 14:30:36 +0000 Subject: [PATCH 10/13] [IMP] payment: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/payment/models/payment_acquirer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index f750461a3d3..38c30e5988d 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -325,11 +325,11 @@ class PaymentAcquirer(models.Model): journals += journal return journals - @api.model - def create(self, vals): - record = super(PaymentAcquirer, self).create(vals) - record._check_required_if_provider() - return record + @api.model_create_multi + def create(self, vals_list): + records = super(PaymentAcquirer, self).create(vals_list) + records._check_required_if_provider() + return records def write(self, vals): result = super(PaymentAcquirer, self).write(vals) From 467aca5ab6b3d27dd1dd15d111134adc6b568be9 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 13:28:37 +0000 Subject: [PATCH 11/13] [IMP] survey: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/survey/wizard/survey_invite.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/addons/survey/wizard/survey_invite.py b/addons/survey/wizard/survey_invite.py index df14dc1afd9..055f086eb7b 100644 --- a/addons/survey/wizard/survey_invite.py +++ b/addons/survey/wizard/survey_invite.py @@ -149,15 +149,16 @@ class SurveyInvite(models.TransientModel): invite.subject = invite.template_id.subject invite.body = invite.template_id.body_html - @api.model - def create(self, values): - if values.get('template_id') and not (values.get('body') or values.get('subject')): - template = self.env['mail.template'].browse(values['template_id']) - if not values.get('subject'): - values['subject'] = template.subject - if not values.get('body'): - values['body'] = template.body_html - return super(SurveyInvite, self).create(values) + @api.model_create_multi + def create(self, vals_list): + for values in vals_list: + if values.get('template_id') and not (values.get('body') or values.get('subject')): + template = self.env['mail.template'].browse(values['template_id']) + if not values.get('subject'): + values['subject'] = template.subject + if not values.get('body'): + values['body'] = template.body_html + return super().create(vals_list) # ------------------------------------------------------ # Wizard validation and send From 0e992e0b7271f8f109ac2f56d6c8de2367c12372 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 13:24:58 +0000 Subject: [PATCH 12/13] [IMP] website_forum: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/website_forum/models/forum.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index 9ecffd1e5c1..a567da2ad66 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -180,11 +180,14 @@ class Forum(models.Model): def _set_default_faq(self): self.faq = self.env['ir.ui.view']._render_template('website_forum.faq_accordion', {"forum": self}).decode('utf-8') - @api.model - def create(self, values): - res = super(Forum, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) - res._set_default_faq() # will trigger a write and call update_website_count - return res + @api.model_create_multi + def create(self, vals_list): + forums = super( + Forum, + self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True) + ).create(vals_list) + forums._set_default_faq() # will trigger a write and call update_website_count + return forums def write(self, vals): if 'privacy' in vals: From e2c3ab984d37da83612070fef9d152dfefda1d6a Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 3 Nov 2020 13:25:12 +0000 Subject: [PATCH 13/13] [IMP] website_slides: support batch creation See merge commit for more details. Task ID-2330149 COM PR odoo/odoo#61246 ENT PR odoo/enterprise#14561 --- addons/website_sale_slides/models/slide_channel.py | 13 +++++++------ addons/website_slides/models/res_users.py | 13 ++++++++----- addons/website_slides/models/slide_slide.py | 5 +++-- addons/website_slides_forum/models/slide_channel.py | 11 +++++------ 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/addons/website_sale_slides/models/slide_channel.py b/addons/website_sale_slides/models/slide_channel.py index fd6ea6d1f37..37d158e0898 100644 --- a/addons/website_sale_slides/models/slide_channel.py +++ b/addons/website_sale_slides/models/slide_channel.py @@ -33,12 +33,11 @@ class Channel(models.Model): for channel in self: channel.product_sale_revenues = rg_data.get(channel.product_id.id, 0) - @api.model - def create(self, vals): - channel = super(Channel, self).create(vals) - if channel.enroll == 'payment': - channel._synchronize_product_publish() - return channel + @api.model_create_multi + def create(self, vals_list): + channels = super(Channel, self).create(vals_list) + channels.filtered(lambda channel: channel.enroll == 'payment')._synchronize_product_publish() + return channels def write(self, vals): res = super(Channel, self).write(vals) @@ -47,6 +46,8 @@ class Channel(models.Model): return res def _synchronize_product_publish(self): + if not self: + return self.filtered(lambda channel: channel.is_published and not channel.product_id.is_published).sudo().product_id.write({'is_published': True}) self.filtered(lambda channel: not channel.is_published and channel.product_id.is_published).sudo().product_id.write({'is_published': False}) diff --git a/addons/website_slides/models/res_users.py b/addons/website_slides/models/res_users.py index 47f6baa980e..3b72ba7ef0e 100644 --- a/addons/website_slides/models/res_users.py +++ b/addons/website_slides/models/res_users.py @@ -7,12 +7,15 @@ from odoo import api, models class Users(models.Model): _inherit = 'res.users' - @api.model - def create(self, values): + @api.model_create_multi + def create(self, vals_list): """ Trigger automatic subscription based on user groups """ - user = super(Users, self).create(values) - self.env['slide.channel'].sudo().search([('enroll_group_ids', 'in', user.groups_id.ids)])._action_add_members(user.partner_id) - return user + users = super(Users, self).create(vals_list) + for user in users: + self.env['slide.channel'].sudo().search([ + ('enroll_group_ids', 'in', user.groups_id.ids) + ])._action_add_members(user.partner_id) + return users def write(self, vals): """ Trigger automatic subscription based on updated user groups """ diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py index 00bd90ea7bc..4a714e07052 100644 --- a/addons/website_slides/models/slide_slide.py +++ b/addons/website_slides/models/slide_slide.py @@ -35,8 +35,9 @@ class SlidePartnerRelation(models.Model): completed = fields.Boolean('Completed') quiz_attempts_count = fields.Integer('Quiz attempts count', default=0) - def create(self, values): - res = super(SlidePartnerRelation, self).create(values) + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) completed = res.filtered('completed') if completed: completed._set_completed_callback() diff --git a/addons/website_slides_forum/models/slide_channel.py b/addons/website_slides_forum/models/slide_channel.py index c45dc14c0d8..e59272e7a52 100644 --- a/addons/website_slides_forum/models/slide_channel.py +++ b/addons/website_slides_forum/models/slide_channel.py @@ -25,12 +25,11 @@ class Channel(models.Model): return action - @api.model - def create(self, vals): - channel = super(Channel, self.with_context(mail_create_nosubscribe=True)).create(vals) - if channel.forum_id: - channel.forum_id.privacy = False - return channel + @api.model_create_multi + def create(self, vals_list): + channels = super(Channel, self.with_context(mail_create_nosubscribe=True)).create(vals_list) + channels.forum_id.privacy = False + return channels def write(self, vals): old_forum = self.forum_id