From ae8f0f2e83ee2c8c69006451ff96f56144d78631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 30 Sep 2022 13:39:47 +0000 Subject: [PATCH] [REF] mail: improve template recipients rendering and generation When computing recipients based on template we generally create new partners based on emails. This is due to the mail framework managing mainly partners and not low level emails. This code is currently sequential: fetching or creating partners one by one based on found emails, for each record. This is highly inefficient when sending emails on a lot of records based on templates. Purpose of this commit is to improve code and performances by batching. We do queries in batch: one search for all records, create partner in batch. This leads to code being a bit more complicated to correctly remember and dispatch again emails and partners to the correct records. Task-3034875 (Mail: Speedup and batch partners find or create with templates) Part-of: odoo/odoo#99482 --- addons/mail/models/mail_template.py | 72 ++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index edbb9bf8381..d4f4c999944 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -282,6 +282,7 @@ class MailTemplate(models.Model): self.ensure_one() if render_results is None: render_results = {} + records_sudo = None # if using default recipients -> ``_message_get_default_recipients`` gives # values for email_to, email_cc and partner_ids @@ -297,35 +298,60 @@ class MailTemplate(models.Model): for res_id in res_ids: render_results.setdefault(res_id, {})[field] = generated_field_values[res_id] - records_company = None + # classify records per company for partner fetch/creation if find_or_create_partners and self.model and 'company_id' in self.env[self.model]._fields: - records_dict = self.env[self.model].browse(res_ids).sudo().read(['company_id']) - records_company = { - rec['id']: rec['company_id'][0] if rec['company_id'] else False - for rec in records_dict - } + if not records_sudo: + records_sudo = self.env[self.model].browse(res_ids).sudo() + records_per_company = {} + for read_record in records_sudo.read(['company_id']): + company_id = read_record['company_id'][0] if read_record['company_id'] else False + records_per_company.setdefault(company_id, []).append(read_record['id']) + else: + records_per_company = {False: res_ids} - # consolidate partner_ids: based on partner_to + create partners if requested - for res_id in res_ids: - record_values = render_results[res_id] + # create partners from emails if asked to + if find_or_create_partners: + for company_id, record_ids in records_per_company.items(): + all_emails = [] + email_to_res_ids = {} + for res_id in record_ids: + record_values = render_results.setdefault(res_id, {}) + mails = tools.email_split(record_values.pop('email_to', '')) + \ + tools.email_split(record_values.pop('email_cc', '')) + all_emails += mails + for mail in mails: + email_to_res_ids.setdefault(mail, []).append(res_id) - partner_ids = record_values.get('partner_ids', []) - if find_or_create_partners: - mails = tools.email_split(record_values.pop('email_to', '')) + tools.email_split(record_values.pop('email_cc', '')) - Partner = self.env['res.partner'] - if records_company: - Partner = Partner.with_context(default_company_id=records_company[res_id]) - for mail in mails: - partner = Partner.find_or_create(mail) - partner_ids.append(partner.id) + if not all_emails: + continue + additional_values = {} + if company_id: + additional_values['company_id'] = company_id + partners = self.env['res.partner']._find_or_create_from_emails( + all_emails, + additional_values=additional_values + ) + for original_email, partner in zip(all_emails, partners): + if not partner: + continue + for res_id in email_to_res_ids[original_email]: + render_results[res_id].setdefault('partner_ids', []).append(partner.id) + # update 'partner_to' rendered value to 'partner_ids' + all_partner_to = { + int(pid) + for record_values in render_results.values() + for pid in record_values.get('partner_to', '').split(',') + if pid and pid.strip() + } + existing_pids = set() + if all_partner_to: + existing_pids = set(self.env['res.partner'].sudo().browse(list(all_partner_to)).exists().ids) + for res_id, record_values in render_results.items(): partner_to = record_values.pop('partner_to', '') if partner_to: - # placeholders could generate '', 3, 2 due to some empty field values - tpl_partner_ids = [int(pid) for pid in partner_to.split(',') if pid] - partner_ids += self.env['res.partner'].sudo().browse(tpl_partner_ids).exists().ids - - record_values['partner_ids'] = partner_ids + tpl_partner_ids = set(int(pid) for pid in partner_to.split(',') if pid) & existing_pids + record_values.setdefault('partner_ids', []).extend(tpl_partner_ids) return render_results