diff --git a/addons/account/models/account_account.py b/addons/account/models/account_account.py index 1112db1bf15..fe17593a417 100644 --- a/addons/account/models/account_account.py +++ b/addons/account/models/account_account.py @@ -570,7 +570,7 @@ class AccountAccount(models.Model): domain = ['|', ('code', '=ilike', name.split(' ')[0] + '%'), ('name', operator, name)] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ['&', '!'] + domain[1:] - return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) + return self._search(expression.AND([domain, args]), limit=limit, order=self._order, access_rights_uid=name_get_uid) @api.onchange('account_type') def _onchange_account_type(self): @@ -842,7 +842,7 @@ class AccountGroup(models.Model): else: criteria_operator = ['|'] if operator not in expression.NEGATIVE_TERM_OPERATORS else ['&', '!'] domain = criteria_operator + [('code_prefix_start', '=ilike', name + '%'), ('name', operator, name)] - return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) + return self._search(expression.AND([domain, args]), limit=limit, order=self._order, access_rights_uid=name_get_uid) @api.constrains('code_prefix_start', 'code_prefix_end') def _constraint_prefix_overlap(self): diff --git a/addons/account/models/account_analytic_account.py b/addons/account/models/account_analytic_account.py index ad7404a03ec..ce0ce56b8ec 100644 --- a/addons/account/models/account_analytic_account.py +++ b/addons/account/models/account_analytic_account.py @@ -28,7 +28,6 @@ class AccountAnalyticAccount(models.Model): [str(account_id) for account_id in self.ids], ) - query.order = None query_string, query_param = query.select( 'jsonb_object_keys(account_move_line.analytic_distribution) as account_id', 'COUNT(DISTINCT(move_id)) as move_count', @@ -53,7 +52,6 @@ class AccountAnalyticAccount(models.Model): [str(account_id) for account_id in self.ids], ) - query.order = None query_string, query_param = query.select( 'jsonb_object_keys(account_move_line.analytic_distribution) as account_id', 'COUNT(DISTINCT(move_id)) as move_count', @@ -68,7 +66,6 @@ class AccountAnalyticAccount(models.Model): def action_view_invoice(self): self.ensure_one() query = self.env['account.move.line']._search([('move_id.move_type', 'in', self.env['account.move'].get_sale_types())]) - query.order = None query.add_where('analytic_distribution ? %s', [str(self.id)]) query_string, query_param = query.select('DISTINCT move_id') self._cr.execute(query_string, query_param) @@ -86,7 +83,6 @@ class AccountAnalyticAccount(models.Model): def action_view_vendor_bill(self): self.ensure_one() query = self.env['account.move.line']._search([('move_id.move_type', 'in', self.env['account.move'].get_purchase_types())]) - query.order = None query.add_where('analytic_distribution ? %s', [str(self.id)]) query_string, query_param = query.select('DISTINCT move_id') self._cr.execute(query_string, query_param) diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 8fc711ff30d..9a6715774cf 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -6,6 +6,7 @@ from functools import lru_cache from odoo import api, fields, models, Command, _ from odoo.exceptions import ValidationError, UserError +from odoo.osv import expression from odoo.tools import frozendict, formatLang, format_date, float_is_zero, Query from odoo.tools.sql import create_index from odoo.addons.web.controllers.utils import clean_action @@ -1551,14 +1552,12 @@ class AccountMoveLine(models.Model): if field_name != 'account_root_id' or set_count: return super()._search_panel_domain_image(field_name, domain, set_count, limit) - # Override in order to not read the complete move line table and use the index instead - query = self._search(domain, limit=1) - # if domain is logically equivalent to false - if not isinstance(query, Query): + if expression.is_false(self, domain): return {} - query.order = None + # Override in order to not read the complete move line table and use the index instead + query = self._search(domain, limit=1) query.add_where('account.id = account_move_line.account_id') query_str, query_param = query.select() self.env.cr.execute(f""" diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py index 4bcbfc4bbd8..1ef9b426ae6 100644 --- a/addons/event/models/event_event.py +++ b/addons/event/models/event_event.py @@ -350,8 +350,7 @@ class EventEvent(models.Model): domain = [('date_begin', '<=', now), ('date_end', '>', now)] else: domain = ['|', ('date_begin', '>', now), ('date_end', '<=', now)] - event_ids = self.env['event.event']._search(domain) - return [('id', 'in', event_ids)] + return domain @api.depends('date_begin', 'date_end', 'date_tz') def _compute_field_is_one_day(self): @@ -384,8 +383,7 @@ class EventEvent(models.Model): domain = [('date_end', '<=', now)] else: domain = [('date_end', '>', now)] - event_ids = self.env['event.event']._search(domain) - return [('id', 'in', event_ids)] + return domain @api.depends('event_type_id') def _compute_date_tz(self): diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py index 697109b8bd9..9d1986738c7 100644 --- a/addons/event/tests/test_event_internals.py +++ b/addons/event/tests/test_event_internals.py @@ -443,13 +443,13 @@ class TestEventData(TestEventInternalsCommon): 'date_end': datetime(2020, 2, 1, 18, 0, 0), }) self.assertTrue(event_1.is_ongoing) - ongoing_event_ids = self.env['event.event']._search([('is_ongoing', '=', True)]) - self.assertIn(event_1.id, ongoing_event_ids) + ongoing_events = self.env['event.event'].search([('is_ongoing', '=', True)]) + self.assertIn(event_1, ongoing_events) event_1.update({'date_begin': datetime(2020, 2, 1, 9, 0, 0)}) self.assertFalse(event_1.is_ongoing) - ongoing_event_ids = self.env['event.event']._search([('is_ongoing', '=', True)]) - self.assertNotIn(event_1.id, ongoing_event_ids) + ongoing_events = self.env['event.event'].search([('is_ongoing', '=', True)]) + self.assertNotIn(event_1, ongoing_events) event_2 = self.env['event.event'].create({ 'name': 'Test Event 2', @@ -457,13 +457,13 @@ class TestEventData(TestEventInternalsCommon): 'date_end': datetime(2020, 1, 28, 8, 0, 0), }) self.assertFalse(event_2.is_ongoing) - finished_or_upcoming_event_ids = self.env['event.event']._search([('is_ongoing', '=', False)]) - self.assertIn(event_2.id, finished_or_upcoming_event_ids) + finished_or_upcoming_events = self.env['event.event'].search([('is_ongoing', '=', False)]) + self.assertIn(event_2, finished_or_upcoming_events) event_2.update({'date_end': datetime(2020, 2, 2, 8, 0, 1)}) self.assertTrue(event_2.is_ongoing) - finished_or_upcoming_event_ids = self.env['event.event']._search([('is_ongoing', '=', False)]) - self.assertNotIn(event_2.id, finished_or_upcoming_event_ids) + finished_or_upcoming_events = self.env['event.event'].search([('is_ongoing', '=', False)]) + self.assertNotIn(event_2, finished_or_upcoming_events) @users('user_eventmanager') def test_event_seats(self): diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py index 08446c13dce..44a75bb3e26 100644 --- a/addons/hr_holidays/models/hr_leave_type.py +++ b/addons/hr_holidays/models/hr_leave_type.py @@ -566,7 +566,7 @@ class HolidaysType(models.Model): to the method. """ employee_id = self._get_contextual_employee_id() - if not order and employee_id: + if order == self._order and employee_id: # retrieve all leaves, sort them, then apply offset and limit leaves = self.browse(super()._search(domain, access_rights_uid=access_rights_uid)) leaves = leaves.sorted(key=self._model_sorting_key, reverse=True) diff --git a/addons/im_livechat/models/chatbot_script_answer.py b/addons/im_livechat/models/chatbot_script_answer.py index 9b5cce42db3..4a596a8d7b9 100644 --- a/addons/im_livechat/models/chatbot_script_answer.py +++ b/addons/im_livechat/models/chatbot_script_answer.py @@ -62,4 +62,4 @@ class ChatbotScriptAnswer(models.Model): if force_domain_chatbot_script_id: domain = expression.AND([domain, [('chatbot_script_id', '=', force_domain_chatbot_script_id)]]) - return self._search(domain, limit=limit, access_rights_uid=name_get_uid) + return self._search(domain, limit=limit, order=self._order, access_rights_uid=name_get_uid) diff --git a/addons/mail/models/mail_activity_mixin.py b/addons/mail/models/mail_activity_mixin.py index 9cc3c396fc7..2e0ab199954 100644 --- a/addons/mail/models/mail_activity_mixin.py +++ b/addons/mail/models/mail_activity_mixin.py @@ -258,7 +258,7 @@ class MailActivityMixin(models.AbstractModel): # explicitly check access rights, since we bypass the ORM self.check_access_rights('read') - self._flush_search(domain, fields=[group_by_fname], order='id') + self._flush_search(domain, fields=[group_by_fname]) self.env['mail.activity'].flush_model(['res_model', 'res_id', 'user_id', 'date_deadline']) self.env['res.users'].flush_model(['partner_id']) self.env['res.partner'].flush_model(['tz']) diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py index 4d1f4b8a22a..b4e054fa2a0 100644 --- a/addons/maintenance/models/maintenance.py +++ b/addons/maintenance/models/maintenance.py @@ -105,7 +105,7 @@ class MaintenanceEquipment(models.Model): args = args or [] equipment_ids = [] if name and operator not in expression.NEGATIVE_TERM_OPERATORS and operator != '=': - equipment_ids = self._search([('name', '=', name)] + args, limit=limit, access_rights_uid=name_get_uid) + equipment_ids = self._search([('name', '=', name)] + args, limit=limit, order=self._order, access_rights_uid=name_get_uid) return equipment_ids or super()._name_search(name, args, operator, limit, name_get_uid) name = fields.Char('Equipment Name', required=True, translate=True) diff --git a/addons/product/models/product_product.py b/addons/product/models/product_product.py index 9f7c1f91108..4ee46d18f37 100644 --- a/addons/product/models/product_product.py +++ b/addons/product/models/product_product.py @@ -506,19 +506,19 @@ class ProductProduct(models.Model): positive_operators = ['=', 'ilike', '=ilike', 'like', '=like'] product_ids = [] if operator in positive_operators: - product_ids = list(self._search([('default_code', '=', name)] + args, limit=limit, access_rights_uid=name_get_uid)) + product_ids = list(self._search([('default_code', '=', name)] + args, limit=limit, order=self._order, access_rights_uid=name_get_uid)) if not product_ids: - product_ids = list(self._search([('barcode', '=', name)] + args, limit=limit, access_rights_uid=name_get_uid)) + product_ids = list(self._search([('barcode', '=', name)] + args, limit=limit, order=self._order, access_rights_uid=name_get_uid)) if not product_ids and operator not in expression.NEGATIVE_TERM_OPERATORS: # Do not merge the 2 next lines into one single search, SQL search performance would be abysmal # on a database with thousands of matching products, due to the huge merge+unique needed for the # OR operator (and given the fact that the 'name' lookup results come from the ir.translation table # Performing a quick memory merge of ids in Python will give much better performance - product_ids = list(self._search(args + [('default_code', operator, name)], limit=limit)) + product_ids = list(self._search(args + [('default_code', operator, name)], limit=limit, order=self._order)) if not limit or len(product_ids) < limit: # we may underrun the limit because of dupes in the results, that's fine limit2 = (limit - len(product_ids)) if limit else False - product2_ids = self._search(args + [('name', operator, name), ('id', 'not in', product_ids)], limit=limit2, access_rights_uid=name_get_uid) + product2_ids = self._search(args + [('name', operator, name), ('id', 'not in', product_ids)], limit=limit2, order=self._order, access_rights_uid=name_get_uid) product_ids.extend(product2_ids) elif not product_ids and operator in expression.NEGATIVE_TERM_OPERATORS: domain = expression.OR([ @@ -526,12 +526,12 @@ class ProductProduct(models.Model): ['&', ('default_code', '=', False), ('name', operator, name)], ]) domain = expression.AND([args, domain]) - product_ids = list(self._search(domain, limit=limit, access_rights_uid=name_get_uid)) + product_ids = list(self._search(domain, limit=limit, order=self._order, access_rights_uid=name_get_uid)) if not product_ids and operator in positive_operators: ptrn = re.compile('(\[(.*?)\])') res = ptrn.search(name) if res: - product_ids = list(self._search([('default_code', '=', res.group(2))] + args, limit=limit, access_rights_uid=name_get_uid)) + product_ids = list(self._search([('default_code', '=', res.group(2))] + args, limit=limit, order=self._order, access_rights_uid=name_get_uid)) # still no results, partner in context: search on supplier info as last hope to find something if not product_ids and self._context.get('partner_id'): suppliers_ids = self.env['product.supplierinfo']._search([ @@ -540,9 +540,9 @@ class ProductProduct(models.Model): ('product_code', operator, name), ('product_name', operator, name)], access_rights_uid=name_get_uid) if suppliers_ids: - product_ids = self._search([('product_tmpl_id.seller_ids', 'in', suppliers_ids)], limit=limit, access_rights_uid=name_get_uid) + product_ids = self._search([('product_tmpl_id.seller_ids', 'in', suppliers_ids)], limit=limit, order=self._order, access_rights_uid=name_get_uid) else: - product_ids = self._search(args, limit=limit, access_rights_uid=name_get_uid) + product_ids = self._search(args, limit=limit, order=self._order, access_rights_uid=name_get_uid) return product_ids @api.model diff --git a/addons/project/models/project.py b/addons/project/models/project.py index d42fa7306a7..2ea51b16c8c 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -673,8 +673,10 @@ class Project(models.Model): @api.model def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None): + if not order: + return super()._search(domain, offset, limit, order, access_rights_uid) new_order, item_index, desc = [], -1, False - for index, order_item in enumerate((order or self._order).split(',')): + for index, order_item in enumerate(order.split(',')): order_item_list = order_item.strip().lower().split(' ') if order_item_list[0] == 'is_favorite': item_index = index @@ -683,7 +685,7 @@ class Project(models.Model): new_order.append(order_item) query = super()._search(domain, offset, limit, ', '.join(new_order), access_rights_uid) if item_index != -1: - query_order_list = query.order.split(',') + query_order_list = query.order.split(',') if query.order else [] query_order_list.insert(item_index, f""" "project_project"."id" IN ( SELECT project_id diff --git a/addons/project_hr_expense/models/project.py b/addons/project_hr_expense/models/project.py index 5efdc4e6311..7bbe925dce0 100644 --- a/addons/project_hr_expense/models/project.py +++ b/addons/project_hr_expense/models/project.py @@ -19,7 +19,6 @@ class Project(models.Model): query = self.env['hr.expense']._search([]) query.add_where('hr_expense.analytic_distribution ?| array[%s]', [str(account_id) for account_id in self.analytic_account_id.ids]) - query.order = None query_string, query_param = query.select( 'jsonb_object_keys(analytic_distribution) as account_id', 'COUNT(DISTINCT(id)) as expense_count', @@ -76,7 +75,6 @@ class Project(models.Model): ('move_id.expense_sheet_id', '!=', False), ('id', 'not in', move_line_ids), ]) - query.order = None return move_line_ids + list(query) def _get_expenses_profitability_items(self, with_action=True): @@ -84,7 +82,6 @@ class Project(models.Model): return {} can_see_expense = with_action and self.user_has_groups('hr_expense.group_hr_expense_team_approver') query = self.env['hr.expense']._search([('state', 'in', ['approved', 'done'])]) - query.order = None query.add_where('hr_expense.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) query_string, query_param = query.select('array_agg(id) as ids', 'SUM(untaxed_amount) as untaxed_amount') self._cr.execute(query_string, query_param) diff --git a/addons/project_purchase/models/project.py b/addons/project_purchase/models/project.py index e7c808c65d0..8240cfc9aa8 100644 --- a/addons/project_purchase/models/project.py +++ b/addons/project_purchase/models/project.py @@ -21,7 +21,6 @@ class Project(models.Model): query = self.env['purchase.order.line']._search([]) query.add_where('purchase_order_line.analytic_distribution ?| array[%s]', [str(account_id) for account_id in self.analytic_account_id.ids]) - query.order = None query_string, query_param = query.select( 'jsonb_object_keys(analytic_distribution) as account_id', 'COUNT(DISTINCT(order_id)) as purchase_order_count', @@ -127,7 +126,7 @@ class Project(models.Model): '|', ('qty_invoiced', '>', 0), '|', ('qty_to_invoice', '>', 0), ('product_uom_qty', '>', 0), - ]) + ], order=self.env['purchase.order.line']._order) query.add_where('purchase_order_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) query_string, query_param = query.select('"purchase_order_line".id', 'qty_invoiced', 'qty_to_invoice', 'product_uom_qty', 'price_unit', 'purchase_order_line.currency_id', '"purchase_order_line".analytic_distribution') self._cr.execute(query_string, query_param) @@ -173,7 +172,7 @@ class Project(models.Model): ('parent_state', 'in', ['draft', 'posted']), ('price_subtotal', '>', 0), ('id', 'not in', purchase_order_line_invoice_line_ids), - ]) + ], order=self.env['account.move.line']._order) query.add_where('account_move_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) # account_move_line__move_id is the alias of the joined table account_move in the query # we can use it, because of the "move_id.move_type" clause in the domain of the query, which generates the join diff --git a/addons/project_sale_expense/models/project_project.py b/addons/project_sale_expense/models/project_project.py index d2ad52329d1..5d65c436d04 100644 --- a/addons/project_sale_expense/models/project_project.py +++ b/addons/project_sale_expense/models/project_project.py @@ -15,7 +15,6 @@ class Project(models.Model): can_see_expense = with_action and self.user_has_groups('hr_expense.group_hr_expense_team_approver') query = self.env['hr.expense']._search([('state', 'in', ['approved', 'done'])]) query.add_where('hr_expense.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) - query.order = None query_string, query_param = query.select('sale_order_id', 'product_id', 'array_agg(id) as ids', 'SUM(untaxed_amount) as untaxed_amount') query_string = f"{query_string} GROUP BY sale_order_id, product_id" self._cr.execute(query_string, query_param) diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py index 659bb0f49c1..52ab0f0f871 100755 --- a/addons/sale_project/models/project.py +++ b/addons/sale_project/models/project.py @@ -155,7 +155,6 @@ class Project(models.Model): def action_open_project_invoices(self): query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['out_invoice', 'out_refund'])]) query.add_where('analytic_distribution ? %s', [str(self.analytic_account_id.id)]) - query.order = None query_string, query_param = query.select('DISTINCT move_id') self._cr.execute(query_string, query_param) invoice_ids = [line.get('move_id') for line in self._cr.dictfetchall()] @@ -539,7 +538,6 @@ class Project(models.Model): def action_open_project_vendor_bills(self): query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['in_invoice', 'in_refund'])]) query.add_where('analytic_distribution ? %s', [str(self.analytic_account_id.id)]) - query.order = None query_string, query_param = query.select('DISTINCT move_id') self._cr.execute(query_string, query_param) vendor_bill_ids = [line.get('move_id') for line in self._cr.dictfetchall()] diff --git a/addons/spreadsheet_account/models/account.py b/addons/spreadsheet_account/models/account.py index 08de7fef72b..f9b8769d5af 100644 --- a/addons/spreadsheet_account/models/account.py +++ b/addons/spreadsheet_account/models/account.py @@ -115,7 +115,6 @@ class AccountMove(models.Model): continue MoveLines = self.env["account.move.line"].with_company(company_id) query = MoveLines._search(domain) - query.order = None query_str, params = query.select( "SUM(debit) AS debit", "SUM(credit) AS credit" ) diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index 58d2d9a02a9..a03f7b838ca 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -746,11 +746,11 @@ class IrServerObjectLines(models.Model): try: value = int(value) if not self.env[line.col1.relation].browse(value).exists(): - record = list(self.env[line.col1.relation]._search([], limit=1)) - value = record[0] if record else 0 + record = self.env[line.col1.relation].search([], limit=1) + value = record.id or 0 except ValueError: - record = list(self.env[line.col1.relation]._search([], limit=1)) - value = record[0] if record else 0 + record = self.env[line.col1.relation].search([], limit=1) + value = record.id or 0 line.resource_ref = '%s,%s' % (line.col1.relation, value) else: line.resource_ref = False diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 2cd347e2bf4..0494c0adc6c 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -652,7 +652,7 @@ actual arch. from_clause, where_clause, where_params = e.query.get_sql() assert from_clause == '"ir_ui_view"', f"Unexpected from clause: {from_clause}" - self._flush_search(domain, fields=['inherit_id', 'priority', 'model', 'mode'], order='id') + self._flush_search(domain, fields=['inherit_id', 'priority', 'model', 'mode']) query = f""" WITH RECURSIVE ir_ui_view_inherits AS ( SELECT id, inherit_id, priority, mode, model diff --git a/odoo/addons/base/models/res_bank.py b/odoo/addons/base/models/res_bank.py index afb0dd48f12..084a57f42de 100644 --- a/odoo/addons/base/models/res_bank.py +++ b/odoo/addons/base/models/res_bank.py @@ -45,7 +45,7 @@ class Bank(models.Model): domain = ['|', ('bic', '=ilike', name + '%'), ('name', operator, name)] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ['&'] + domain - return self._search(domain + args, limit=limit, access_rights_uid=name_get_uid) + return self._search(domain + args, limit=limit, order=self._order, access_rights_uid=name_get_uid) @api.onchange('country') def _onchange_country_id(self): diff --git a/odoo/addons/base/models/res_country.py b/odoo/addons/base/models/res_country.py index e63c18b87c3..c3aa250a696 100644 --- a/odoo/addons/base/models/res_country.py +++ b/odoo/addons/base/models/res_country.py @@ -87,12 +87,12 @@ class Country(models.Model): ids = [] if len(name) == 2: - ids = list(self._search([('code', 'ilike', name)] + args, limit=limit)) + ids = list(self._search([('code', 'ilike', name)] + args, limit=limit, order=self._order)) search_domain = [('name', operator, name)] if ids: search_domain.append(('id', 'not in', ids)) - ids += list(self._search(search_domain + args, limit=limit)) + ids += list(self._search(search_domain + args, limit=limit, order=self._order)) return ids @@ -180,11 +180,11 @@ class CountryState(models.Model): first_domain = [('code', '=ilike', name)] domain = [('name', operator, name)] - first_state_ids = self._search(expression.AND([first_domain, args]), limit=limit, access_rights_uid=name_get_uid) if first_domain else [] + first_state_ids = self._search(expression.AND([first_domain, args]), limit=limit, order=self._order, access_rights_uid=name_get_uid) if first_domain else [] return list(first_state_ids) + [ state_id for state_id in self._search(expression.AND([domain, args]), - limit=limit, access_rights_uid=name_get_uid) + limit=limit, order=self._order, access_rights_uid=name_get_uid) if state_id not in first_state_ids ] diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index f40fa004271..d3a2718e3af 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -149,7 +149,7 @@ class PartnerCategory(models.Model): # Be sure name_search is symetric to name_get name = name.split(' / ')[-1] args = [('name', operator, name)] + args - return self._search(args, limit=limit, access_rights_uid=name_get_uid) + return self._search(args, limit=limit, order=self._order, access_rights_uid=name_get_uid) class PartnerTitle(models.Model): diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index 47304cec5a2..452e45230bb 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -659,9 +659,9 @@ class Users(models.Model): domain = [] else: domain = [('login', '=', name)] - user_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) + user_ids = self._search(expression.AND([domain, args]), limit=limit, order=self._order, access_rights_uid=name_get_uid) if not user_ids: - user_ids = self._search(expression.AND([[('name', operator, name)], args]), limit=limit, access_rights_uid=name_get_uid) + user_ids = self._search(expression.AND([[('name', operator, name)], args]), limit=limit, order=self._order, access_rights_uid=name_get_uid) return user_ids def copy(self, default=None): diff --git a/odoo/addons/test_impex/models.py b/odoo/addons/test_impex/models.py index eaff023da29..e1cf29488af 100644 --- a/odoo/addons/test_impex/models.py +++ b/odoo/addons/test_impex/models.py @@ -52,7 +52,7 @@ for name, field in MODELS: @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): if isinstance(name, str) and name.split(':')[0] == self._name: - return self._search([('value', operator, int(name.split(':')[1]))], access_rights_uid=name_get_uid) + return self._search([('value', operator, int(name.split(':')[1]))], limit=limit, order=self._order, access_rights_uid=name_get_uid) else: return [] @@ -73,7 +73,7 @@ class One2ManyChild(models.Model): @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): if isinstance(name, str) and name.split(':')[0] == self._name: - return self._search([('value', operator, int(name.split(':')[1]))], access_rights_uid=name_get_uid) + return self._search([('value', operator, int(name.split(':')[1]))], limit=limit, order=self._order, access_rights_uid=name_get_uid) else: return [] @@ -130,7 +130,7 @@ class Many2ManyChild(models.Model): @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): if isinstance(name, str) and name.split(':')[0] == self._name: - return self._search([('value', operator, int(name.split(':')[1]))], access_rights_uid=name_get_uid) + return self._search([('value', operator, int(name.split(':')[1]))], limit=limit, order=self._order, access_rights_uid=name_get_uid) else: return [] diff --git a/odoo/fields.py b/odoo/fields.py index ba409ab6587..7fc05f92f27 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -4659,7 +4659,7 @@ class Many2many(_RelationalMulti): context.update(self.context) comodel = records.env[self.comodel_name].with_context(**context) domain = self.get_domain_list(records) - comodel._flush_search(domain) + comodel._flush_search(domain, order=comodel._order) wquery = comodel._where_calc(domain) comodel._apply_ir_rules(wquery, 'read') order_by = comodel._generate_order_by(None, wquery) diff --git a/odoo/models.py b/odoo/models.py index 0cca07bf1e7..b5db88d1ae2 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -1469,7 +1469,7 @@ class BaseModel(metaclass=MetaModel): list to match all records. :param limit: maximum number of record to count (upperbound) (default: all) """ - query = self._search(domain, limit=limit, order='id') + query = self._search(domain, limit=limit) return len(query) @api.model @@ -1488,7 +1488,7 @@ class BaseModel(metaclass=MetaModel): :returns: at most ``limit`` records matching the search criteria :raise AccessError: if user is not allowed to access requested information """ - query = self._search(domain, offset=offset, limit=limit, order=order) + query = self._search(domain, offset=offset, limit=limit, order=order or self._order) return self.browse(query) # @@ -1604,7 +1604,7 @@ class BaseModel(metaclass=MetaModel): aggregator = expression.AND if operator in expression.NEGATIVE_TERM_OPERATORS else expression.OR domain = aggregator([[(field_name, operator, name)] for field_name in search_fnames]) args += domain - return self._search(args, limit=limit, access_rights_uid=name_get_uid) + return self._search(args, limit=limit, order=self._order, access_rights_uid=name_get_uid) @api.model def _add_missing_default_values(self, values): @@ -2273,7 +2273,7 @@ class BaseModel(metaclass=MetaModel): for gb in annotated_groupbys: select_terms.append('%s as "%s" ' % (gb['qualified_field'], gb['groupby'])) - self._flush_search(domain, fields=fnames + groupby_fields) + self._flush_search(domain, fields=fnames + groupby_fields, order=order) groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, annotated_groupbys, query) from_clause, where_clause, where_clause_params = query.get_sql() @@ -4535,7 +4535,11 @@ class BaseModel(metaclass=MetaModel): @api.model def _flush_search(self, domain, fields=None, order=None, seen=None): - """ Flush all the fields appearing in `domain`, `fields` and `order`. """ + """ Flush all the fields appearing in `domain`, `fields` and `order`. + + Note that ``order=None`` actually means no order, so if you expect some + fallback order, you have to provide it yourself. + """ if seen is None: seen = set() elif self._name in seen: @@ -4588,14 +4592,15 @@ class BaseModel(metaclass=MetaModel): collect_from_domain(self, domain) # flush the order fields - order_spec = order or self._order - for order_part in order_spec.split(','): - order_field = order_part.split()[0] - field = self._fields.get(order_field) - if field is not None: - to_flush[self._name].add(order_field) - if field.relational: - self.env[field.comodel_name]._flush_search([], seen=seen) + if order: + for order_part in order.split(','): + order_field = order_part.split()[0] + field = self._fields.get(order_field) + if field is not None: + to_flush[self._name].add(order_field) + if field.relational: + comodel = self.env[field.comodel_name] + comodel._flush_search([], order=comodel._order, seen=seen) if self._active_name: to_flush[self._name].add(self._active_name) @@ -4620,6 +4625,8 @@ class BaseModel(metaclass=MetaModel): by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules! This is ok at the security level because this method is private and not callable through XML-RPC. + No default order is applied when the method is invoked without parameter ``order``. + :param access_rights_uid: optional user ID to use when checking access rights (not for ir.rules, this is only for ir.model.access) :return: a collection of record ids (may be a ``Query`` object) @@ -4637,7 +4644,8 @@ class BaseModel(metaclass=MetaModel): query = self._where_calc(domain) self._apply_ir_rules(query, 'read') - query.order = self._generate_order_by(order, query).replace('ORDER BY ', '') + if order: + query.order = self._generate_order_by(order, query).replace('ORDER BY ', '') query.limit = limit query.offset = offset diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index d0944c46512..f80c514abee 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -555,7 +555,7 @@ class expression(object): records = records.search([(parent_name, 'in', records.ids)], order='id') domain = [('id', 'in', list(child_ids))] if prefix: - return [(left, 'in', left_model._search(domain, order='id'))] + return [(left, 'in', left_model._search(domain))] return domain def parent_of_domain(left, ids, left_model, parent=None, prefix=''): @@ -583,7 +583,7 @@ class expression(object): records = records[parent_name] domain = [('id', 'in', list(parent_ids))] if prefix: - return [(left, 'in', left_model._search(domain, order='id'))] + return [(left, 'in', left_model._search(domain))] return domain HIERARCHY_FUNCS = {'child_of': child_of_domain, @@ -707,12 +707,12 @@ class expression(object): raise NotImplementedError('auto_join attribute not supported on field %s' % field) elif len(path) > 1 and field.store and field.type == 'many2one': - right_ids = comodel.with_context(active_test=False)._search([(path[1], operator, right)], order='id') + right_ids = comodel.with_context(active_test=False)._search([(path[1], operator, right)]) push((path[0], 'in', right_ids), model, alias) # Making search easier when there is a left operand as one2many or many2many elif len(path) > 1 and field.store and field.type in ('many2many', 'one2many'): - right_ids = comodel.with_context(**field.context)._search([(path[1], operator, right)], order='id') + right_ids = comodel.with_context(**field.context)._search([(path[1], operator, right)]) push((path[0], 'in', right_ids), model, alias) elif not field.store: @@ -727,10 +727,10 @@ class expression(object): else: # Let the field generate a domain. if len(path) > 1: - right = comodel._search([(path[1], operator, right)], order='id') + right = comodel._search([(path[1], operator, right)]) operator = 'in' domain = field.determine_domain(model, operator, right) - model._flush_search(domain, order='id') + model._flush_search(domain) for elem in normalize_domain(domain): push(elem, model, alias, internal=True) @@ -766,7 +766,7 @@ class expression(object): else: ids2 = [right] if inverse_is_int and domain: - ids2 = comodel._search([('id', 'in', ids2)] + domain, order='id') + ids2 = comodel._search([('id', 'in', ids2)] + domain) if inverse_field.store: # In the condition, one must avoid subqueries to return @@ -817,7 +817,7 @@ class expression(object): # determine ids2 in comodel ids2 = to_ids(right, comodel, leaf) domain = HIERARCHY_FUNCS[operator]('id', ids2, comodel) - ids2 = comodel._search(domain, order='id') + ids2 = comodel._search(domain) # rewrite condition in terms of ids2 if comodel == model: diff --git a/odoo/tools/query.py b/odoo/tools/query.py index c3d27f5be53..b970e512097 100644 --- a/odoo/tools/query.py +++ b/odoo/tools/query.py @@ -223,20 +223,13 @@ class Query(object): return bool(self.get_result_ids()) def __len__(self): - if self._ids is None and not self.offset: - from_clause, where_clause, params = self.get_sql() - where_clause = where_clause or 'TRUE' - if self.limit: - # apply the limit, and wrap the query into a more efficient one - query_str = f''' - SELECT COUNT(*) FROM ( - SELECT FROM {from_clause} - WHERE {where_clause} - LIMIT {int(self.limit)} - ) t - ''' + if self._ids is None: + if self.limit or self.offset: + # optimization: generate a SELECT FROM, and then count the rows + query_str, params = self.select('') + query_str = f'SELECT COUNT(*) FROM ({query_str}) t' else: - query_str = f'SELECT COUNT(*) FROM {from_clause} WHERE {where_clause}' + query_str, params = self.select('COUNT(*)') self._cr.execute(query_str, params) return self._cr.fetchone()[0] return len(self.get_result_ids())