[IMP] *: optimize code with search_fetch() and fetch()

closes odoo/odoo#112126

Related: odoo/enterprise#36782
Signed-off-by: Raphael Collet <rco@odoo.com>
This commit is contained in:
Raphael Collet
2023-03-05 15:12:57 +01:00
parent 8c81074e60
commit 6ef3772847
22 changed files with 129 additions and 105 deletions
+2 -3
View File
@@ -63,8 +63,7 @@ class CompanyLDAP(models.Model):
:rtype: list of dictionaries
"""
ldaps = self.sudo().search([('ldap_server', '!=', False)], order='sequence')
res = ldaps.read([
res = self.sudo().search_read([('ldap_server', '!=', False)], [
'id',
'company',
'ldap_server',
@@ -76,7 +75,7 @@ class CompanyLDAP(models.Model):
'user',
'create_user',
'ldap_tls'
])
], order='sequence')
return res
def _connect(self, conf):
+4 -2
View File
@@ -23,7 +23,10 @@ class Partner(models.Model):
def _compute_meeting(self):
if self.ids:
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
# prefetch 'parent_id'
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)], ['parent_id'],
)
query = self.env['calendar.event']._search([]) # ir.rules will be applied
query_str, params = query.subselect()
@@ -43,7 +46,6 @@ class Partner(models.Model):
meetings[m[0]].add(m[1])
# Add the events linked to the children of the partner
all_partners.read(['parent_id'])
for p in all_partners:
partner = p
while partner:
+3 -2
View File
@@ -45,8 +45,9 @@ class Partner(models.Model):
def _compute_opportunity_count(self):
# retrieve all children partners and prefetch 'parent_id' on them
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
all_partners.read(['parent_id'])
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)], ['parent_id'],
)
opportunity_data = self.env['crm.lead'].with_context(active_test=False)._read_group(
domain=[('partner_id', 'in', all_partners.ids)],
@@ -513,13 +513,16 @@ class Challenge(models.Model):
domain.append(('user_id', '=', user.id))
goal = Goals.search(domain, limit=1)
goal = Goals.search_fetch(domain, ['current', 'completeness', 'state'], limit=1)
if not goal:
continue
if goal.state != 'reached':
return []
line_data.update(goal.read(['id', 'current', 'completeness', 'state'])[0])
line_data.update({
fname: goal[fname]
for fname in ['id', 'current', 'completeness', 'state']
})
res_lines.append(line_data)
continue
+6 -5
View File
@@ -594,11 +594,12 @@ class MailActivity(models.Model):
def activity_format(self):
activities = self.read()
mail_template_ids = set([template_id for activity in activities for template_id in activity["mail_template_ids"]])
mail_template_info = self.env["mail.template"].browse(mail_template_ids).read(['id', 'name'])
mail_template_dict = dict([(mail_template['id'], mail_template) for mail_template in mail_template_info])
for activity in activities:
activity['mail_template_ids'] = [mail_template_dict[mail_template_id] for mail_template_id in activity['mail_template_ids']]
self.mail_template_ids.fetch(['name'])
for record, activity in zip(self, activities):
activity['mail_template_ids'] = [
{'id': mail_template.id, 'name': mail_template.name}
for mail_template in record.mail_template_ids
]
return activities
@api.model
+6 -10
View File
@@ -742,26 +742,23 @@ class Message(models.Model):
messages.set_message_done()
return messages.ids
notifications = self.env['mail.notification'].sudo().search(notif_domain)
notifications = self.env['mail.notification'].sudo().search_fetch(notif_domain, ['mail_message_id'])
notifications.write({'is_read': True})
ids = [n['mail_message_id'] for n in notifications.read(['mail_message_id'])]
self.env['bus.bus']._sendone(self.env.user.partner_id, 'mail.message/mark_as_read', {
'message_ids': [id[0] for id in ids],
'message_ids': notifications.mail_message_id.ids,
'needaction_inbox_counter': self.env.user.partner_id._get_needaction_count(),
})
return ids
def set_message_done(self):
""" Remove the needaction from messages for the current partner. """
partner_id = self.env.user.partner_id
notifications = self.env['mail.notification'].sudo().search([
notifications = self.env['mail.notification'].sudo().search_fetch([
('mail_message_id', 'in', self.ids),
('res_partner_id', '=', partner_id.id),
('is_read', '=', False)])
('is_read', '=', False),
], ['mail_message_id'])
if not notifications:
return
@@ -782,9 +779,8 @@ class Message(models.Model):
starred_messages = self.search([('starred_partner_ids', 'in', partner_id)])
starred_messages.write({'starred_partner_ids': [Command.unlink(partner_id)]})
ids = [m.id for m in starred_messages]
self.env['bus.bus']._sendone(self.env.user.partner_id, 'mail.message/toggle_star', {
'message_ids': ids,
'message_ids': starred_messages.ids,
'starred': False,
})
+18 -21
View File
@@ -132,37 +132,34 @@ class MailThread(models.AbstractModel):
"""
# TOFIX make it work with not in
assert operator != "not in", "Do not search message_follower_ids with 'not in'"
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('partner_id', operator, operand)])
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
# prefetch 'res_id' for the performance of mapped() below
followers = self.env['mail.followers'].sudo().search_fetch(
[('res_model', '=', self._name), ('partner_id', operator, operand)],
['res_id'],
)
return [('id', 'in', followers.mapped('res_id'))]
@api.depends('message_follower_ids')
def _compute_message_is_follower(self):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('partner_id', '=', self.env.user.partner_id.id),
])
# using read() below is much faster than followers.mapped('res_id')
following_ids = [res['res_id'] for res in followers.read(['res_id'])]
followers = self.env['mail.followers'].sudo().search_fetch(
[('res_model', '=', self._name), ('res_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)],
['res_id'],
)
following_ids = set(followers.mapped('res_id'))
for record in self:
record.message_is_follower = record.id in following_ids
@api.model
def _search_message_is_follower(self, operator, operand):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('partner_id', '=', self.env.user.partner_id.id),
])
followers = self.env['mail.followers'].sudo().search_fetch(
[('res_model', '=', self._name), ('partner_id', '=', self.env.user.partner_id.id)],
['res_id'],
)
# Cases ('message_is_follower', '=', True) or ('message_is_follower', '!=', False)
if (operator == '=' and operand) or (operator == '!=' and not operand):
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
return [('id', 'in', followers.mapped('res_id'))]
else:
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'not in', [res['res_id'] for res in followers.read(['res_id'])])]
return [('id', 'not in', followers.mapped('res_id'))]
def _compute_has_message(self):
self.env['mail.message'].flush_model()
@@ -172,7 +169,7 @@ class MailThread(models.AbstractModel):
WHERE res_id = any(%s)
AND mm.model=%s
""", [self.ids, self._name])
channel_ids = [r[0] for r in self.env.cr.fetchall()]
channel_ids = {r[0] for r in self.env.cr.fetchall()}
for record in self:
record.has_message = record.id in channel_ids
+4 -3
View File
@@ -1104,10 +1104,11 @@ class MailComposer(models.TransientModel):
self._cr.execute("SELECT email FROM mail_blacklist WHERE active=true")
blacklist = {x[0] for x in self._cr.fetchall()}
if blacklist:
targets = self.env[self.model].browse(mail_values_dict.keys()).read(['email_normalized'])
targets = self.env[self.model].browse(mail_values_dict.keys())
targets.fetch(['email_normalized'])
# First extract email from recipient before comparing with blacklist
blacklisted_rec_ids.update(target['id'] for target in targets
if target['email_normalized'] in blacklist)
blacklisted_rec_ids.update(target.id for target in targets
if target.email_normalized in blacklist)
return blacklisted_rec_ids
def _get_done_emails(self, mail_values_dict):
@@ -91,13 +91,14 @@ class ReportBomStructure(models.AbstractModel):
route_info = product_info.get(parent_product.id, {}).get(parent_bom.id, {})
if route_info and route_info['route_type'] == 'subcontract':
subcontracting_loc = route_info['supplier'].partner_id.property_stock_subcontractor
subloc_product = product.with_context(location=subcontracting_loc.id, warehouse=False).read(['free_qty', 'qty_available'])[0]
subloc_product = product.with_context(location=subcontracting_loc.id, warehouse=False)
subloc_product.fetch(['free_qty', 'qty_available'])
stock_loc = f"subcontract_{subcontracting_loc.id}"
if not product_info[product.id]['consumptions'].get(stock_loc, False):
product_info[product.id]['consumptions'][stock_loc] = 0
return {
'free_qty': product.uom_id._compute_quantity(subloc_product['free_qty'], bom_uom),
'on_hand_qty': product.uom_id._compute_quantity(subloc_product['qty_available'], bom_uom),
'free_qty': product.uom_id._compute_quantity(subloc_product.free_qty, bom_uom),
'on_hand_qty': product.uom_id._compute_quantity(subloc_product.qty_available, bom_uom),
'stock_loc': stock_loc,
}
+3 -5
View File
@@ -13,12 +13,10 @@ class AccountTax(models.Model):
'include_base_amount', 'is_base_affected',
])
if forbidden_fields & set(vals.keys()):
tax_ids = self.env['pos.order.line'].sudo().search([
open_lines = self.env['pos.order.line'].sudo().search([
('order_id.session_id.state', '!=', 'closed')
]).read(['tax_ids'])
# Flatten the list of taxes, see https://stackoverflow.com/questions/952914
tax_ids = set([i for sl in [t['tax_ids'] for t in tax_ids] for i in sl])
if tax_ids & set(self.ids):
])
if not set(self.ids).isdisjoint(open_lines.tax_ids.ids):
raise UserError(_(
'It is forbidden to modify a tax used in a POS order not posted. '
'You must close the POS sessions before modifying the tax.'
+18 -10
View File
@@ -1586,21 +1586,29 @@ class PosSession(models.Model):
return loaded_data
def _get_attributes_by_ptal_id(self):
product_attributes = self.env['product.attribute'].search([('create_variant', '=', 'no_variant')])
product_attributes_by_id = {product_attribute.id: product_attribute for product_attribute in product_attributes}
domain = [('attribute_id', 'in', product_attributes.mapped('id'))]
product_template_attribute_values = self.env['product.template.attribute.value'].search(domain)
key = lambda ptav: (ptav.attribute_line_id.id, ptav.attribute_id.id)
# performance trick: prefetch fields with search_fetch() and fetch()
product_attributes = self.env['product.attribute'].search_fetch(
[('create_variant', '=', 'no_variant')],
['name', 'display_type'],
)
product_template_attribute_values = self.env['product.template.attribute.value'].search_fetch(
[('attribute_id', 'in', product_attributes.ids)],
['attribute_id', 'attribute_line_id', 'product_attribute_value_id', 'price_extra'],
)
product_template_attribute_values.product_attribute_value_id.fetch(['name', 'is_custom', 'html_color'])
key1 = lambda ptav: (ptav.attribute_line_id.id, ptav.attribute_id.id)
key2 = lambda ptav: (ptav.attribute_line_id.id, ptav.attribute_id)
res = {}
for key, group in groupby(sorted(product_template_attribute_values, key=key), key=key):
attribute_line_id, attribute_id = key
for key, group in groupby(sorted(product_template_attribute_values, key=key1), key=key2):
attribute_line_id, attribute = key
values = [{**ptav.product_attribute_value_id.read(['name', 'is_custom', 'html_color'])[0],
'price_extra': ptav.price_extra} for ptav in list(group)]
res[attribute_line_id] = {
'id': attribute_line_id,
'name': product_attributes_by_id[attribute_id].name,
'display_type': product_attributes_by_id[attribute_id].display_type,
'values': values
'name': attribute.name,
'display_type': attribute.display_type,
'values': values,
}
return res
+4 -3
View File
@@ -16,9 +16,10 @@ class ResPartner(models.Model):
def _compute_pos_order(self):
# retrieve all children partners and prefetch 'parent_id' on them
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
all_partners.read(['parent_id'])
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)],
['parent_id'],
)
pos_order_data = self.env['pos.order']._read_group(
domain=[('partner_id', 'in', all_partners.ids)],
fields=['partner_id'], groupby=['partner_id']
@@ -9,7 +9,10 @@ class PortalRating(http.Controller):
@http.route(['/website/rating/comment'], type='json', auth="user", methods=['POST'], website=True)
def publish_rating_comment(self, rating_id, publisher_comment):
rating = request.env['rating.rating'].search([('id', '=', int(rating_id))])
rating = request.env['rating.rating'].search_fetch(
[('id', '=', int(rating_id))],
['publisher_comment', 'publisher_id', 'publisher_datetime'],
)
if not rating:
return {'error': _('Invalid rating')}
rating.write({'publisher_comment': publisher_comment})
+10 -11
View File
@@ -444,23 +444,21 @@ class ProductProduct(models.Model):
result = []
# Prefetch the fields used by the `name_get`, so `browse` doesn't fetch other fields
# Use `load=False` to not call `name_get` for the `product_tmpl_id`
self.sudo().read(['name', 'default_code', 'product_tmpl_id'], load=False)
# prefetch the fields used by the `name_get`
self.sudo().fetch(['name', 'default_code', 'product_tmpl_id'])
product_template_ids = self.sudo().mapped('product_tmpl_id').ids
product_template_ids = self.sudo().product_tmpl_id.ids
if partner_ids:
supplier_info = self.env['product.supplierinfo'].sudo().search([
('product_tmpl_id', 'in', product_template_ids),
('partner_id', 'in', partner_ids),
])
# Prefetch the fields used by the `name_get`, so `browse` doesn't fetch other fields
# Use `load=False` to not call `name_get` for the `product_tmpl_id` and `product_id`
supplier_info.sudo().read(['product_tmpl_id', 'product_id', 'product_name', 'product_code'], load=False)
# prefetch the fields used by the `name_get`
supplier_info = self.env['product.supplierinfo'].sudo().search_fetch(
[('product_tmpl_id', 'in', product_template_ids), ('partner_id', 'in', partner_ids)],
['product_tmpl_id', 'product_id', 'company_id', 'product_name', 'product_code'],
)
supplier_info_by_template = {}
for r in supplier_info:
supplier_info_by_template.setdefault(r.product_tmpl_id, []).append(r)
for product in self.sudo():
variant = product.product_template_attribute_value_ids._get_combination_name()
@@ -496,6 +494,7 @@ class ProductProduct(models.Model):
'default_code': product.default_code,
}
result.append(_name_get(mydict))
return result
@api.model
+1 -1
View File
@@ -485,7 +485,7 @@ class ProductTemplate(models.Model):
def name_get(self):
# Prefetch the fields used by the `name_get`, so `browse` doesn't fetch other fields
self.browse(self.ids).read(['name', 'default_code'])
self.fetch(['name', 'default_code'])
return [(template.id, '%s%s' % (template.default_code and '[%s] ' % template.default_code or '', template.name))
for template in self]
+4 -3
View File
@@ -16,9 +16,10 @@ class ResPartner(models.Model):
def _compute_task_count(self):
# retrieve all children partners and prefetch 'parent_id' on them
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
all_partners.read(['parent_id'])
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)],
['parent_id'],
)
task_data = self.env['project.task']._read_group(
domain=[('partner_id', 'in', all_partners.ids)],
fields=['partner_id'], groupby=['partner_id']
+8 -6
View File
@@ -11,9 +11,10 @@ class res_partner(models.Model):
def _compute_purchase_order_count(self):
# retrieve all children partners and prefetch 'parent_id' on them
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
all_partners.read(['parent_id'])
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)],
['parent_id'],
)
purchase_order_groups = self.env['purchase.order']._read_group(
domain=[('partner_id', 'in', all_partners.ids)],
fields=['partner_id'], groupby=['partner_id']
@@ -30,9 +31,10 @@ class res_partner(models.Model):
def _compute_supplier_invoice_count(self):
# retrieve all children partners and prefetch 'parent_id' on them
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
all_partners.read(['parent_id'])
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)],
['parent_id'],
)
supplier_invoice_groups = self.env['account.move']._read_group(
domain=[('partner_id', 'in', all_partners.ids),
('move_type', 'in', ('in_invoice', 'in_refund'))],
+4 -3
View File
@@ -19,9 +19,10 @@ class ResPartner(models.Model):
def _compute_sale_order_count(self):
# retrieve all children partners and prefetch 'parent_id' on them
all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
all_partners.read(['parent_id'])
all_partners = self.with_context(active_test=False).search_fetch(
[('id', 'child_of', self.ids)],
['parent_id'],
)
sale_order_groups = self.env['sale.order']._read_group(
domain=expression.AND([self._get_sale_order_domain_count(), [('partner_id', 'in', all_partners.ids)]]),
fields=['partner_id'], groupby=['partner_id']
+1 -1
View File
@@ -83,7 +83,7 @@ class SaleOrderLine(models.Model):
('product_tmpl_id', 'in', self.env.registry.populated_models["product.template"])
]).ids
self.env['product.product'].browse(product_ids).read(['uom_id']) # prefetch all uom_id
self.env['product.product'].browse(product_ids).fetch(['uom_id']) # prefetch all uom_id
def get_product_uom(values, counter, random):
return self.env['product.product'].browse(values['product_id']).uom_id.id
@@ -13,7 +13,7 @@ class ResCompany(models.Model):
)
existing_companies = companies.exists()
# prefetch both fields
existing_companies.read(["fiscalyear_last_day", "fiscalyear_last_month"])
existing_companies.fetch(["fiscalyear_last_day", "fiscalyear_last_month"])
results = []
for data, company in zip(payload, companies):
+14 -7
View File
@@ -481,7 +481,7 @@ Please change the quantity done or the rounding precision of your unit of measur
self.forecast_expected_date = False
# Prefetch product info to avoid fetching all product fields
self.product_id.read(['type', 'uom_id'], load=False)
self.product_id.fetch(['type', 'uom_id'])
not_product_moves = self.filtered(lambda move: move.product_id.type != 'product')
for move in not_product_moves:
@@ -2167,17 +2167,24 @@ Please change the quantity done or the rounding precision of your unit of measur
product_ids = self.product_id
wh_location_query = self.env['stock.location']._search([('id', 'child_of', warehouse.view_location_id.id)])
# Prefetch data to avoid future request
in_domain, out_domain = self.env['stock.forecasted_product_product']._move_confirmed_domain(
None, product_ids.ids, wh_location_query
)
outs = self.env['stock.move'].search(out_domain, order='reservation_date, priority desc, date, id')
reserved_outs = self.env['stock.move'].search(
outs = self.env['stock.move'].search_fetch(
out_domain,
['product_id', 'product_uom', 'product_qty', 'state'],
order='reservation_date, priority desc, date, id',
)
reserved_outs = self.env['stock.move'].search_fetch(
out_domain + [('state', 'in', ('partially_available', 'assigned'))],
['product_id', 'product_uom'],
order='priority desc, date, id')
ins = self.env['stock.move'].search(in_domain, order='priority desc, date, id')
# Prefetch data to avoid future request
(outs - self).read(['product_id', 'product_uom', 'product_qty', 'state'], load=False) # remove self because data is already fetch
ins.read(['product_id', 'product_qty', 'date', 'move_dest_ids'], load=False)
ins = self.env['stock.move'].search_fetch(
in_domain,
['product_id', 'product_qty', 'date', 'move_dest_ids'],
order='priority desc, date, id',
)
currents = product_ids.with_context(warehouse=warehouse.id)._get_only_qty_available()
+5 -2
View File
@@ -180,5 +180,8 @@ class Http(models.AbstractModel):
def get_currencies(self):
Currency = self.env['res.currency']
currencies = Currency.search([]).read(['symbol', 'position', 'decimal_places'])
return {c['id']: {'symbol': c['symbol'], 'position': c['position'], 'digits': [69,c['decimal_places']]} for c in currencies}
currencies = Currency.search_fetch([], ['symbol', 'position', 'decimal_places'])
return {
c.id: {'symbol': c.symbol, 'position': c.position, 'digits': [69, c.decimal_places]}
for c in currencies
}