From e3c329b9bcd7327d36279c45326cae1aed3a11c0 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Thu, 2 Apr 2020 19:35:33 +0200 Subject: [PATCH 1/3] [IMP] *: create users and partners in batch The main classes of partners and users support batch creation, but the majority of their overrides doesn't support creation in batch. Adapting those overrides to support records creation in batch shows great performance gains: * On `res_partner` : 2 to 3 times faster * On `res_users`, with the inherited `res_partner` created in batch: up to 10 times faster. Tests done with 500 to 4k records: * `res_partner` with only a name provided * `res_users` with a name and login X-original-commit: 9c3c5f161580039c1fe50f68acac808c18817997 --- addons/auth_signup/models/res_users.py | 20 +++++++++++--------- addons/base_vat/models/res_partner.py | 13 +++++++------ addons/digest/models/res_users.py | 13 +++++++------ addons/mail/models/res_users.py | 19 ++++++++++--------- odoo/addons/base/models/res_users.py | 26 +++++++++++++++----------- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py index dc1a471421a..c600d969e9d 100644 --- a/addons/auth_signup/models/res_users.py +++ b/addons/auth_signup/models/res_users.py @@ -233,16 +233,18 @@ class ResUsers(models.Model): inactive_users.with_context(create_user=True).action_reset_password() return res - @api.model - def create(self, values): + @api.model_create_multi + def create(self, vals_list): # overridden to automatically invite user to sign up - user = super(ResUsers, self).create(values) - if user.email and not self.env.context.get('no_reset_password'): - try: - user.with_context(create_user=True).action_reset_password() - except MailDeliveryException: - user.partner_id.with_context(create_user=True).signup_cancel() - return user + users = super(ResUsers, self).create(vals_list) + if not self.env.context.get('no_reset_password'): + users_with_email = users.filtered('email') + if users_with_email: + try: + users_with_email.with_context(create_user=True).action_reset_password() + except MailDeliveryException: + users_with_email.partner_id.with_context(create_user=True).signup_cancel() + return users @api.returns('self', lambda value: value.id) def copy(self, default=None): diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py index 24f581f9a31..c3376cd0ba5 100644 --- a/addons/base_vat/models/res_partner.py +++ b/addons/base_vat/models/res_partner.py @@ -481,12 +481,13 @@ class ResPartner(models.Model): vat_number = format_func(vat_number) return vat_country.upper() + vat_number - @api.model - def create(self, values): - if values.get('vat'): - country_id = values.get('country_id') - values['vat'] = self._fix_vat_number(values['vat'], country_id) - return super(ResPartner, self).create(values) + @api.model_create_multi + def create(self, vals_list): + for values in vals_list: + if values.get('vat'): + country_id = values.get('country_id') + values['vat'] = self._fix_vat_number(values['vat'], country_id) + return super(ResPartner, self).create(vals_list) def write(self, values): if values.get('vat') and len(self.mapped('country_id')) == 1: diff --git a/addons/digest/models/res_users.py b/addons/digest/models/res_users.py index 767ec444391..0f006b08dc7 100644 --- a/addons/digest/models/res_users.py +++ b/addons/digest/models/res_users.py @@ -6,13 +6,14 @@ from odoo import api, models class ResUsers(models.Model): _inherit = "res.users" - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): """ Automatically subscribe employee users to default digest if activated """ - user = super(ResUsers, self).create(vals) + users = super(ResUsers, self).create(vals_list) default_digest_emails = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_emails') default_digest_id = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_id') - if user.has_group('base.group_user') and default_digest_emails and default_digest_id: + if default_digest_emails and default_digest_id: digest = self.env['digest.digest'].sudo().browse(int(default_digest_id)) - digest.user_ids |= user - return user + user_group_id = self.env['ir.model.data'].xmlid_to_res_id('base.group_user') + digest.user_ids |= users.filtered_domain([('groups_id', 'in', [user_group_id])]) + return users diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py index 64e0d1cd746..ce00a1d2b72 100644 --- a/addons/mail/models/res_users.py +++ b/addons/mail/models/res_users.py @@ -71,17 +71,18 @@ GROUP BY channel_moderator.res_users_id""", [tuple(self.ids)]) type(self).SELF_READABLE_FIELDS.extend(['notification_type']) return init_res - @api.model - def create(self, values): - if not values.get('login', False): - action = self.env.ref('base.action_res_users') - msg = _("You cannot create a new user from here.\n To create new user please go to configuration panel.") - raise exceptions.RedirectWarning(msg, action.id, _('Go to the configuration panel')) + @api.model_create_multi + def create(self, vals_list): + for values in vals_list: + if not values.get('login', False): + action = self.env.ref('base.action_res_users') + msg = _("You cannot create a new user from here.\n To create new user please go to configuration panel.") + raise exceptions.RedirectWarning(msg, action.id, _('Go to the configuration panel')) - user = super(Users, self).create(values) + users = super(Users, self).create(vals_list) # Auto-subscribe to channels - self.env['mail.channel'].search([('group_ids', 'in', user.groups_id.ids)])._subscribe_users() - return user + self.env['mail.channel'].search([('group_ids', 'in', users.groups_id.ids)])._subscribe_users() + return users def write(self, vals): write_res = super(Users, self).write(vals) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index 0926c8dd922..5569978ceec 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -1250,17 +1250,21 @@ class ModuleCategory(models.Model): class UsersView(models.Model): _inherit = 'res.users' - @api.model - def create(self, values): - values = self._remove_reified_groups(values) - user = super(UsersView, self).create(values) - group_multi_company = self.env.ref('base.group_multi_company', False) - if group_multi_company and 'company_ids' in values: - if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids: - user.write({'groups_id': [(3, group_multi_company.id)]}) - elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids: - user.write({'groups_id': [(4, group_multi_company.id)]}) - return user + @api.model_create_multi + def create(self, vals_list): + new_vals_list = [] + for values in vals_list: + new_vals_list.append(self._remove_reified_groups(values)) + users = super(UsersView, self).create(new_vals_list) + group_multi_company_id = self.env['ir.model.data'].xmlid_to_res_id( + 'base.group_multi_company', raise_if_not_found=False) + if group_multi_company_id: + for user in users: + if len(user.company_ids) <= 1 and group_multi_company_id in user.groups_id.ids: + user.write({'groups_id': [(3, group_multi_company_id)]}) + elif len(user.company_ids) > 1 and group_multi_company_id in user.groups_id.ids: + user.write({'groups_id': [(4, group_multi_company_id)]}) + return users def write(self, values): values = self._remove_reified_groups(values) From 46d267c758c313c2b039d81d0c5f1d3d4741c171 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Thu, 2 Apr 2020 18:29:56 +0200 Subject: [PATCH 2/3] [IMP] base: user defaults groups performance Improve the performance of the default method for user groups by avoiding a `ref` call, using the group in cache instead. Calls to ref always trigger one SQL query to verify record still exists This means that creating 10k user would trigger 10k queries only for the default. X-original-commit: e67efb88338ef669059591728e7722fe1dff4bf0 --- odoo/addons/base/models/res_users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index 5569978ceec..e42376c8ba5 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -211,8 +211,8 @@ class Users(models.Model): SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image_1920', 'image_1024', 'image_512', 'image_256', 'image_128', 'lang', 'tz', 'tz_offset', 'groups_id', 'partner_id', '__last_update', 'action_id'] def _default_groups(self): - default_user = self.env.ref('base.default_user', raise_if_not_found=False) - return (default_user or self.env['res.users']).sudo().groups_id + default_user_id = self.env['ir.model.data'].xmlid_to_res_id('base.default_user', raise_if_not_found=False) + return self.env['res.users'].browse(default_user_id).sudo().groups_id if default_user_id else [] @api.model def _get_default_image(self): From 0279cbc4a79162ea837bf14db73e12b38cd0aca9 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 3 Apr 2020 14:28:17 +0200 Subject: [PATCH 3/3] [IMP] base: compute users `share` in batch. Instead of triggering one has_group by user (one sql query/user if not already ormcached), and potentially filling the `has_group` cache with new users data (we don't know if and when this data will be used anyway), compute this field from the groups information already in cache. X-original-commit: d771d7e9c8503543d29fdbf2ab961c2a32cc3bac --- addons/digest/models/res_users.py | 3 +-- odoo/addons/base/models/res_users.py | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/addons/digest/models/res_users.py b/addons/digest/models/res_users.py index 0f006b08dc7..432e140df33 100644 --- a/addons/digest/models/res_users.py +++ b/addons/digest/models/res_users.py @@ -14,6 +14,5 @@ class ResUsers(models.Model): default_digest_id = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_id') if default_digest_emails and default_digest_id: digest = self.env['digest.digest'].sudo().browse(int(default_digest_id)) - user_group_id = self.env['ir.model.data'].xmlid_to_res_id('base.group_user') - digest.user_ids |= users.filtered_domain([('groups_id', 'in', [user_group_id])]) + digest.user_ids |= users.filtered_domain([('share', '=', False)]) return users diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index e42376c8ba5..4d34c0a027d 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -357,8 +357,10 @@ class Users(models.Model): @api.depends('groups_id') def _compute_share(self): - for user in self: - user.share = not user.has_group('base.group_user') + user_group_id = self.env['ir.model.data'].xmlid_to_res_id('base.group_user') + internal_users = self.filtered_domain([('groups_id', 'in', [user_group_id])]) + internal_users.share = False + (self - internal_users).share = True def _compute_companies_count(self): self.companies_count = self.env['res.company'].sudo().search_count([])