[IMP] core: search() and _search() no longer have parameter count

The parameter in search() is redundant with method search_count(), and
was making the calls less readable.

The method _search() is aimed at always returning a Query object.  The
method can therefore never return an integer, hence the removal of the
parameter.  This does not actually remove any functionality from the
method; counting result is simply given by using it differently.

Part-of: odoo/odoo#112126
This commit is contained in:
Raphael Collet
2023-03-05 15:12:54 +01:00
parent 789c643925
commit 7e6cff5479
22 changed files with 146 additions and 172 deletions
+5 -2
View File
@@ -79,8 +79,11 @@ class SetupBarBankConfigWizard(models.TransientModel):
num_journals_without_account = fields.Integer(default=lambda self: self._number_unlinked_journal())
def _number_unlinked_journal(self):
return self.env['account.journal'].search([('type', '=', 'bank'), ('bank_account_id', '=', False),
('id', '!=', self.default_linked_journal_id())], count=True)
return self.env['account.journal'].search_count([
('type', '=', 'bank'),
('bank_account_id', '=', False),
('id', '!=', self.default_linked_journal_id()),
])
@api.onchange('acc_number')
def _onchange_acc_number(self):
+7 -6
View File
@@ -774,7 +774,7 @@ class Lead(models.Model):
return result
@api.model
def search(self, domain, offset=0, limit=None, order=None, count=False):
def search(self, domain, offset=0, limit=None, order=None):
""" Override to support ordering on my_activity_date_deadline.
Ordering through web client calls search_read with an order parameter set.
@@ -804,8 +804,9 @@ class Lead(models.Model):
All other search and search_read are left untouched by this override to avoid
side effects. Search_count is not affected by this override.
"""
if count or not order or 'my_activity_date_deadline' not in order:
return super(Lead, self).search(domain, offset=offset, limit=limit, order=order, count=count)
if not order or 'my_activity_date_deadline' not in order:
return super().search(domain, offset, limit, order)
order_items = [order_item.strip().lower() for order_item in (order or self._order).split(',')]
# Perform a read_group on my activities to get a mapping lead_id / deadline
@@ -825,7 +826,7 @@ class Lead(models.Model):
# Search leads linked to those activities and order them. See docstring
# of this method for more details.
search_res = super(Lead, self).search(my_lead_domain, offset=0, limit=None, order=my_lead_order, count=count)
search_res = super().search(my_lead_domain, order=my_lead_order)
my_lead_ids_ordered = sorted(search_res.ids, key=lambda lead_id: my_lead_mapping[lead_id], reverse=not activity_asc)
# keep only requested window (offset + limit, or offset+)
my_lead_ids_keep = my_lead_ids_ordered[offset:(offset + limit)] if limit else my_lead_ids_ordered[offset:]
@@ -847,9 +848,9 @@ class Lead(models.Model):
lead_offset = 0
lead_order = ', '.join(item for item in order_items if 'my_activity_date_deadline' not in item)
other_lead_res = super(Lead, self).search(
other_lead_res = super().search(
expression.AND([[('id', 'not in', my_lead_ids_skip)], domain]),
offset=lead_offset, limit=lead_limit, order=lead_order, count=count
lead_offset, lead_limit, lead_order,
)
return self.browse(my_lead_ids_keep) + other_lead_res
+5 -5
View File
@@ -236,7 +236,7 @@ class HrEmployeePrivate(models.Model):
return self.env['hr.employee.public'].get_view(view_id, view_type, **options)
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
"""
We override the _search because it is the method that checks the access rights
This is correct to override the _search. That way we enforce the fact that calling
@@ -246,14 +246,14 @@ class HrEmployeePrivate(models.Model):
employees exactly match the ids of the related hr.employee.
"""
if self.check_access_rights('read', raise_exception=False):
return super(HrEmployeePrivate, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
return super()._search(domain, offset, limit, order, access_rights_uid)
try:
ids = self.env['hr.employee.public']._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
ids = self.env['hr.employee.public']._search(domain, offset, limit, order, access_rights_uid)
except ValueError:
raise AccessError(_('You do not have access to this document.'))
if not count and isinstance(ids, Query):
if isinstance(ids, Query):
# the result is expected from this table, so we should link tables
ids = super(HrEmployeePrivate, self.sudo())._search([('id', 'in', ids)])
ids = super(HrEmployeePrivate, self.sudo())._search([('id', 'in', ids)], order=order)
return ids
def get_formview_id(self, access_uid=None):
+8 -7
View File
@@ -553,7 +553,7 @@ class HolidaysType(models.Model):
return res
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
""" Override _search to order the results, according to some employee.
The order is the following
@@ -566,12 +566,13 @@ class HolidaysType(models.Model):
to the method.
"""
employee_id = self._get_contextual_employee_id()
post_sort = (not count and not order and employee_id)
leave_ids = super(HolidaysType, self)._search(args, offset=offset, limit=(None if post_sort else limit), order=order, count=count, access_rights_uid=access_rights_uid)
leaves = self.browse(leave_ids)
if post_sort:
return leaves.sorted(key=self._model_sorting_key, reverse=True).ids[:limit or None]
return leave_ids
if not 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)
leaves = leaves[offset:(offset + limit) if limit else None]
return leaves.ids
return super()._search(domain, offset, limit, order, access_rights_uid)
def action_see_days_allocated(self):
self.ensure_one()
+9 -18
View File
@@ -372,7 +372,7 @@ class MailActivity(models.Model):
return super(MailActivity, self).unlink()
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
""" Override that adds specific access rights of mail.activity, to remove
ids uid could not see according to our custom rules. Please refer to
_filter_access_rules_remaining for more details about those rules.
@@ -381,16 +381,10 @@ class MailActivity(models.Model):
# Rules do not apply to administrator
if self.env.is_superuser():
return super(MailActivity, self)._search(
args, offset=offset, limit=limit, order=order,
count=count, access_rights_uid=access_rights_uid)
# Perform a super with count as False, to have the ids, not a counter
ids = super(MailActivity, self)._search(
args, offset=offset, limit=limit, order=order,
count=False, access_rights_uid=access_rights_uid)
if not ids and count:
return 0
elif not ids:
return super()._search(domain, offset, limit, order, access_rights_uid)
ids = super()._search(domain, offset, limit, order, access_rights_uid)
if not ids:
return ids
# check read access rights before checking the actual rules on the given ids
@@ -426,12 +420,9 @@ class MailActivity(models.Model):
activity['id'] for activity in activities_to_check
if activity['res_model'] == doc_model and activity['res_id'] in valid_doc_ids)
if count:
return len(allowed_ids)
else:
# re-construct a list based on ids, because 'allowed_ids' does not keep the original order
id_list = [id for id in ids if id in allowed_ids]
return id_list
# re-construct a list based on ids, because 'allowed_ids' does not keep the original order
id_list = [id for id in ids if id in allowed_ids]
return id_list
@api.model
def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
@@ -447,7 +438,7 @@ class MailActivity(models.Model):
# Rules do not apply to administrator
if not self.env.is_superuser():
allowed_ids = self._search(domain, count=False)
allowed_ids = self._search(domain)
if allowed_ids:
domain = expression.AND([domain, [('id', 'in', allowed_ids)]])
else:
+10 -15
View File
@@ -51,23 +51,18 @@ class MailBlackList(models.Model):
values['email'] = tools.email_normalize(values['email'])
return super(MailBlackList, self).write(values)
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
""" Override _search in order to grep search on email field and make it
lower-case and sanitized """
if args:
new_args = []
for arg in args:
if isinstance(arg, (list, tuple)) and arg[0] == 'email' and isinstance(arg[2], str):
normalized = tools.email_normalize(arg[2])
if normalized:
new_args.append([arg[0], arg[1], normalized])
else:
new_args.append(arg)
else:
new_args.append(arg)
else:
new_args = args
return super(MailBlackList, self)._search(new_args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
def normalize(arg):
if isinstance(arg, (list, tuple)) and arg[0] == 'email' and isinstance(arg[2], str):
normalized = tools.email_normalize(arg[2])
if normalized:
return (arg[0], arg[1], normalized)
return arg
domain = [normalize(item) for item in domain]
return super()._search(domain, offset, limit, order, access_rights_uid)
def _add(self, email, message=None):
normalized = tools.email_normalize(email)
+8 -17
View File
@@ -256,7 +256,7 @@ class Message(models.Model):
self._cr.execute("""CREATE INDEX IF NOT EXISTS mail_message_model_res_id_id_idx ON mail_message (model, res_id, id)""")
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
""" Override that adds specific access rights of mail.message, to remove
ids uid could not see according to our custom rules. Please refer to
check_access_rule for more details about those rules.
@@ -273,19 +273,13 @@ class Message(models.Model):
"""
# Rules do not apply to administrator
if self.env.is_superuser():
return super(Message, self)._search(
args, offset=offset, limit=limit, order=order,
count=count, access_rights_uid=access_rights_uid)
return super()._search(domain, offset, limit, order, access_rights_uid)
# Non-employee see only messages with a subtype and not internal
if not self.env['res.users'].has_group('base.group_user'):
args = expression.AND([self._get_search_domain_share(), args])
domain = expression.AND([self._get_search_domain_share(), domain])
# Perform a super with count as False, to have the ids, not a counter
ids = super(Message, self)._search(
args, offset=offset, limit=limit, order=order,
count=False, access_rights_uid=access_rights_uid)
if not ids and count:
return 0
elif not ids:
ids = super()._search(domain, offset, limit, order, access_rights_uid)
if not ids:
return ids
pid = self.env.user.partner_id.id
@@ -319,12 +313,9 @@ class Message(models.Model):
final_ids = author_ids | partner_ids | allowed_ids
if count:
return len(final_ids)
else:
# re-construct a list based on ids, because set did not keep the original order
id_list = [id for id in ids if id in final_ids]
return id_list
# re-construct a list based on ids, because set did not keep the original order
id_list = [id for id in ids if id in final_ids]
return id_list
@api.model
def _find_allowed_model_wise(self, doc_model, doc_dict):
+1 -1
View File
@@ -219,7 +219,7 @@ class MrpBom(models.Model):
def check_kit_has_not_orderpoint(self):
product_ids = [pid for bom in self.filtered(lambda bom: bom.type == "phantom")
for pid in (bom.product_id.ids or bom.product_tmpl_id.product_variant_ids.ids)]
if self.env['stock.warehouse.orderpoint'].search([('product_id', 'in', product_ids)], count=True):
if self.env['stock.warehouse.orderpoint'].search_count([('product_id', 'in', product_ids)], limit=1):
raise ValidationError(_("You can not create a kit-type bill of materials for products that have at least one reordering rule."))
@api.ondelete(at_uninstall=False)
+7 -3
View File
@@ -303,9 +303,13 @@ class Orderpoint(models.Model):
@api.constrains('product_id')
def check_product_is_not_kit(self):
if self.env['mrp.bom'].search(['|', ('product_id', 'in', self.product_id.ids),
'&', ('product_id', '=', False), ('product_tmpl_id', 'in', self.product_id.product_tmpl_id.ids),
('type', '=', 'phantom')], count=True):
domain = [
'|', ('product_id', 'in', self.product_id.ids),
'&', ('product_id', '=', False),
('product_tmpl_id', 'in', self.product_id.product_tmpl_id.ids),
('type', '=', 'phantom'),
]
if self.env['mrp.bom'].search_count(domain, limit=1):
raise ValidationError(_("A product with a kit-type bill of materials can not have a reordering rule."))
def _get_orderpoint_products(self):
@@ -60,23 +60,18 @@ class PhoneBlackList(models.Model):
values['number'] = sanitized
return super(PhoneBlackList, self).write(values)
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
""" Override _search in order to grep search on sanitized number field """
if args:
new_args = []
for arg in args:
if isinstance(arg, (list, tuple)) and arg[0] == 'number' and isinstance(arg[2], str):
number = arg[2]
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
if sanitized:
new_args.append([arg[0], arg[1], sanitized])
else:
new_args.append(arg)
else:
new_args.append(arg)
else:
new_args = args
return super(PhoneBlackList, self)._search(new_args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
def sanitize_number(arg):
if isinstance(arg, (list, tuple)) and arg[0] == 'number' and isinstance(arg[2], str):
number = arg[2]
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
if sanitized:
return (arg[0], arg[1], sanitized)
return arg
domain = [sanitize_number(item) for item in domain]
return super()._search(domain, offset, limit, order, access_rights_uid)
def add(self, number, message=None):
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
+3 -3
View File
@@ -408,11 +408,11 @@ class ProductProduct(models.Model):
return template.product_variant_id or template._create_first_product_variant()
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
# TDE FIXME: strange
if self._context.get('search_default_categ_id'):
args.append((('categ_id', 'child_of', self._context['search_default_categ_id'])))
return super(ProductProduct, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
domain.append((('categ_id', 'child_of', self._context['search_default_categ_id'])))
return super()._search(domain, offset, limit, order, access_rights_uid)
@api.depends_context('display_default_code', 'seller_id')
def _compute_display_name(self):
+5 -5
View File
@@ -672,7 +672,7 @@ class Project(models.Model):
return result
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
new_order, item_index, desc = [], -1, False
for index, order_item in enumerate((order or self._order).split(',')):
order_item_list = order_item.strip().lower().split(' ')
@@ -681,7 +681,7 @@ class Project(models.Model):
desc = order_item_list[-1] == 'desc'
else:
new_order.append(order_item)
query = super()._search(args, offset, limit, ', '.join(new_order), count, access_rights_uid)
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.insert(item_index, f"""
@@ -1890,10 +1890,10 @@ class Task(models.Model):
return super(Task, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
fields_list = {term[0] for term in args if isinstance(term, (tuple, list)) and term not in [expression.TRUE_LEAF, expression.FALSE_LEAF]}
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
fields_list = {term[0] for term in domain if isinstance(term, (tuple, list)) and term not in [expression.TRUE_LEAF, expression.FALSE_LEAF]}
self._ensure_fields_are_accessible(fields_list)
return super(Task, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
return super()._search(domain, offset, limit, order, access_rights_uid)
def mapped(self, func):
# Note: This will protect the filtered method too
+2 -2
View File
@@ -82,13 +82,13 @@ class TestActivityRights(TestActivityCommon):
# can _search activities if access to the document
self.env['mail.activity'].with_user(self.user_employee)._search(
[('id', '=', test_activity.id)], count=False)
[('id', '=', test_activity.id)])
# cannot _search activities if no access to the document
with patch.object(MailTestActivity, 'check_access_rights', autospec=True, side_effect=_employee_crash):
with self.assertRaises(exceptions.AccessError):
searched_activity = self.env['mail.activity'].with_user(self.user_employee)._search(
[('id', '=', test_activity.id)], count=False)
[('id', '=', test_activity.id)])
# can read_group activities if access to the document
read_group_result = self.env['mail.activity'].with_user(self.user_employee).read_group(
+11 -11
View File
@@ -528,23 +528,22 @@ class IrAttachment(models.Model):
return super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
# add res_field=False in domain if not present; the arg[0] trick below
# works for domain items and '&'/'|'/'!' operators too
discard_binary_fields_attachments = False
if not any(arg[0] in ('id', 'res_field') for arg in args):
if not any(arg[0] in ('id', 'res_field') for arg in domain):
discard_binary_fields_attachments = True
args.insert(0, ('res_field', '=', False))
domain = [('res_field', '=', False)] + domain
ids = super(IrAttachment, self)._search(args, offset=offset, limit=limit, order=order,
count=False, access_rights_uid=access_rights_uid)
ids = super()._search(domain, offset, limit, order, access_rights_uid)
if self.env.is_superuser():
# rules do not apply for the superuser
return len(ids) if count else ids
return ids
if not ids:
return 0 if count else []
return []
# Work with a set, as list.remove() is prohibitive for large lists of documents
# (takes 20+ seconds on a db with 100k docs during search_count()!)
@@ -597,11 +596,12 @@ class IrAttachment(models.Model):
# expected records to retrieve
if len(orig_ids) == limit and len(result) < self._context.get('need', limit):
need = self._context.get('need', limit) - len(result)
result.extend(self.with_context(need=need)._search(args, offset=offset + len(orig_ids),
limit=limit, order=order, count=False,
access_rights_uid=access_rights_uid)[:limit - len(result)])
more_ids = self.with_context(need=need)._search(
domain, offset + len(orig_ids), limit, order, access_rights_uid,
)
result.extend(more_ids[:limit - len(result)])
return len(result) if count else list(result)
return list(result)
def _read(self, fields):
self.check('read')
+3 -3
View File
@@ -138,8 +138,8 @@ class IrUiMenu(models.Model):
return self.filtered(lambda menu: menu.id in visible_ids)
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
menu_ids = super(IrUiMenu, self)._search(args, offset=0, limit=None, order=order, count=False, access_rights_uid=access_rights_uid)
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
menu_ids = super()._search(domain, order=order, access_rights_uid=access_rights_uid)
menus = self.browse(menu_ids)
if menus:
# menu filtering is done only on main menu tree, not other menu lists
@@ -149,7 +149,7 @@ class IrUiMenu(models.Model):
menus = menus[offset:]
if limit:
menus = menus[:limit]
return len(menus) if count else menus.ids
return menus.ids
def name_get(self):
return [(menu.id, menu._get_full_name()) for menu in self]
+10 -11
View File
@@ -113,19 +113,18 @@ class ResPartnerBank(models.Model):
for acc in self]
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
pos = 0
while pos < len(args):
# DLE P14
if args[pos][0] == 'acc_number':
op = args[pos][1]
value = args[pos][2]
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
def sanitize(arg):
if isinstance(arg, (tuple, list)) and arg[0] == 'acc_number':
value = arg[2]
if not isinstance(value, str) and isinstance(value, Iterable):
value = [sanitize_account_number(i) for i in value]
else:
value = sanitize_account_number(value)
if 'like' in op:
if 'like' in arg[1]:
value = '%' + value + '%'
args[pos] = ('sanitized_acc_number', op, value)
pos += 1
return super(ResPartnerBank, self)._search(args, offset, limit, order, count=count, access_rights_uid=access_rights_uid)
return ('sanitized_acc_number', arg[1], value)
return arg
domain = [sanitize(item) for item in domain]
return super()._search(domain, offset, limit, order, access_rights_uid)
+4 -5
View File
@@ -886,15 +886,14 @@ class Partner(models.Model):
return partner.name_get()[0]
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
""" Override search() to always show inactive children when searching via ``child_of`` operator. The ORM will
always call search() with a simple domain of the form [('parent_id', 'in', [ids])]. """
# a special ``domain`` is set on the ``child_ids`` o2m to bypass this logic, as it uses similar domain expressions
if len(args) == 1 and len(args[0]) == 3 and args[0][:2] == ('parent_id','in') \
and args[0][2] != [False]:
if len(domain) == 1 and len(domain[0]) == 3 and domain[0][:2] == ('parent_id', 'in') \
and domain[0][2] != [False]:
self = self.with_context(active_test=False)
return super(Partner, self)._search(args, offset=offset, limit=limit, order=order,
count=count, access_rights_uid=access_rights_uid)
return super()._search(domain, offset, limit, order, access_rights_uid)
@api.model
@api.returns('self', lambda value: value.id)
+8 -9
View File
@@ -188,14 +188,14 @@ class Groups(models.Model):
return where
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
# add explicit ordering if search is sorted on full_name
if order and order.startswith('full_name'):
groups = super(Groups, self).search(args)
groups = super().search(domain)
groups = groups.sorted('full_name', reverse=order.endswith('DESC'))
groups = groups[offset:offset+limit] if limit else groups[offset:]
return len(groups) if count else groups.ids
return super(Groups, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
return groups.ids
return super()._search(domain, offset, limit, order, access_rights_uid)
def copy(self, default=None):
self.ensure_one()
@@ -561,13 +561,12 @@ class Users(models.Model):
return super(Users, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
if not self.env.su and args:
domain_fields = {term[0] for term in args if isinstance(term, (tuple, list))}
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
if not self.env.su and domain:
domain_fields = {term[0] for term in domain if isinstance(term, (tuple, list))}
if domain_fields.intersection(USER_PRIVATE_FIELDS):
raise AccessError(_('Invalid search criterion'))
return super(Users, self)._search(args, offset=offset, limit=limit, order=order, count=count,
access_rights_uid=access_rights_uid)
return super()._search(domain, offset, limit, order, access_rights_uid)
@api.model_create_multi
def create(self, vals_list):
+2 -2
View File
@@ -70,10 +70,10 @@ class TestAPI(SavepointCaseWithUserDemo):
@mute_logger('odoo.models')
def test_04_query_count(self):
""" Test the search method with count=True. """
""" Test the search_count method. """
self.cr.execute("SELECT COUNT(*) FROM res_partner WHERE active")
count1 = self.cr.fetchone()[0]
count2 = self.env['res.partner'].search([], count=True)
count2 = self.env['res.partner'].search_count([])
self.assertIsInstance(count1, int)
self.assertIsInstance(count2, int)
self.assertEqual(count1, count2)
-3
View File
@@ -203,7 +203,4 @@ class test_search(TransactionCase):
{'name': 'runbot'},
])
self.assertEqual(len(partners) + count_partner_before, Partner.search_count([]))
self.assertEqual(len(partners) + count_partner_before, Partner.search([], count=True))
self.assertEqual(3, Partner.search_count([], limit=3))
self.assertEqual(3, Partner.search([], count=True, limit=3))
+11 -28
View File
@@ -1469,15 +1469,13 @@ class BaseModel(metaclass=MetaModel):
list to match all records.
:param limit: maximum number of record to count (upperbound) (default: all)
"""
res = self.search(domain, limit=limit, count=True)
return res if isinstance(res, int) else len(res)
query = self._search(domain, limit=limit, order='id')
return len(query)
@api.model
@api.returns('self',
upgrade=lambda self, value, domain, offset=0, limit=None, order=None, count=False: value if count else self.browse(value),
downgrade=lambda self, value, domain, offset=0, limit=None, order=None, count=False: value if count else value.ids)
def search(self, domain, offset=0, limit=None, order=None, count=False):
""" search(domain[, offset=0][, limit=None][, order=None][, count=False])
@api.returns('self')
def search(self, domain, offset=0, limit=None, order=None):
""" search(domain[, offset=0][, limit=None][, order=None])
Searches for records based on the ``domain``
:ref:`search domain <reference/orm/domains>`.
@@ -1487,12 +1485,11 @@ class BaseModel(metaclass=MetaModel):
:param int offset: number of results to ignore (default: none)
:param int limit: maximum number of records to return (default: all)
:param str order: sort string
:param bool count: if True, only counts and returns the number of matching records (default: False)
:returns: at most ``limit`` records matching the search criteria
:raise AccessError: if user is not allowed to access requested information
"""
res = self._search(domain, offset=offset, limit=limit, order=order, count=count)
return res if count else self.browse(res)
query = self._search(domain, offset=offset, limit=limit, order=order)
return self.browse(query)
#
# display_name, name_get, name_create, name_search
@@ -4616,7 +4613,7 @@ class BaseModel(metaclass=MetaModel):
self.env[model_name].flush_model(field_names)
@api.model
def _search(self, domain, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
"""
Private implementation of search() method, allowing specifying the uid to use for the access right check.
This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
@@ -4625,37 +4622,23 @@ class BaseModel(metaclass=MetaModel):
: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 list of record ids or an integer (if count is True)
:return: a collection of record ids (may be a ``Query`` object)
"""
model = self.with_user(access_rights_uid) if access_rights_uid else self
model.check_access_rights('read')
if expression.is_false(self, domain):
# optimization: no need to query, as no record satisfies the domain
return 0 if count else []
return ()
# the flush must be done before the _where_calc(), as the latter can do some selects
self._flush_search(domain, order=order)
query = self._where_calc(domain)
self._apply_ir_rules(query, 'read')
query.limit = limit
if count:
# Ignore order and offset when just counting, they don't make sense and could
# hurt performance
if limit:
# Special case to avoid counting every record in DB (which can be really slow).
# The result will be between 0 and limit.
query_str, params = query.select("") # generates a `SELECT FROM` (faster)
query_str = f"SELECT COUNT(*) FROM ({query_str}) t"
else:
query_str, params = query.select("COUNT(*)")
self._cr.execute(query_str, params)
return self._cr.fetchone()[0]
query.order = self._generate_order_by(order, query).replace('ORDER BY ', '')
query.limit = limit
query.offset = offset
return query
+16
View File
@@ -223,6 +223,22 @@ 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
'''
else:
query_str = f'SELECT COUNT(*) FROM {from_clause} WHERE {where_clause}'
self._cr.execute(query_str, params)
return self._cr.fetchone()[0]
return len(self.get_result_ids())
def __iter__(self):