Get length of a many2many or one2many ids

Asked by Yann Papouin

I'm not able to get the length of a *_ids fields with len()
TypeError: object of type 'BrowseRecordIterator' has no len()

Is there a workaround ?

Question information

English Edit question
OERPLib Edit question
No assignee Edit question
Solved by:
Sébastien Alix
Last query:
Last reply:
Revision history for this message
Best Sébastien Alix (sebastien-alix) said :

Hi Yann,

Indeed, BrowseRecordIterator is a generator, to iterate on records one by one (= making a RPC request by record iterated):

>>> import oerplib
>>> oerp = oerplib.OERP(...)
>>> user = oerp.login(...)
>>> user.groups_id
<oerplib.service.osv.browse.BrowseRecordIterator object at 0x136ad90>
>>> for group in user.groups_id: # 'group' record data is requested to OpenERP on demand, one by one
... pass

If you want to request all records at first, and then iterate on them, just use the standard 'list()' Python type:

>>> for group in list(user.groups_id): # All groups are requested before iterating on them
.... pass

In your case, to obtain the length:

>>> len(list(user.groups_id))

But, if you just need the number of records in a 2many field, I would suggest you to use the 'read()' method to obtain better performances:

>>> user_obj = oerp.get('res.users')
>>> data =[USER_ID], ['groups_id'])[0] # Only one RPC request is made to get the list of IDs
>>> len(data['groups_id'])

In this last example, no extra request are made on the OpenERP server. This is the drawback of early examples: with a list of browse records, there will be as many RPC requests as there are records in the "*2many" relation.

I hope this will help :)


Revision history for this message
Yann Papouin (yann-papouin) said :

Wow thank you ! that was fast, this is very clear for me now.

So if I use list(myobject.item_ids), does that mean that only one SELECT for all IDs is made or it only means that all SELECT are made in one pass ? (note that when I write one SELECT, I know that there are a lot of more SELECT behind this for translations, access rules, etc. )

About the read method, maybe a workaround could be added to the browse method with two parameters:
- a field restriction array (also used for 2many fields)
- a depth level (to restrict 2many fields level )

for eg:
   sale = sale_order.browse([1], ['name', 'partner_id', 'order_line_ids','product_id'], 2][0]
could return an object where only name, partner_id and order_line_ids could be accessed by dot notation.
product_id would be also be available via dot notation on order_line_ids too
but not sale_order.order_line_ids.product_id.seller_ids because of the depth level (and also because it does not exists in the field restriction list)

Revision history for this message
Sébastien Alix (sebastien-alix) said :

Hum, no. If you have 100 records/IDs in your "myobject.item_ids" relation field, then behind the scene a "list(myobject.item_ids)" statement will generate 100 calls to the 'read()' RPC method, one by record (and then construct a list from these results). That is not optimal :)

There is no way to know if a "list()" is used upon "myobject.item_ids" or not. The iteration of a "_ids" relation has been made this way to begin to iterate a large collection of records without having to wait that all records are fetched in local, we just request them one by one (it is often more confortable).

But the call to the 'read()' method below generates only one RPC request:

[5, 6, 74, 12, 6]
>>> len([myobject_id])[0]['item_ids'])

Or, if you prefer, from your 'myobject' record:

>>> myobject.__data__['raw_data']['item_ids'] # __data__['raw_data'] contains all data requested during the instantiation of 'myobject'
[5, 6, 74, 12, 6]
>>> len(myobject.__data__['raw_data']['item_ids'])

About the parameters added to 'browse()', unfortunatly this will not solve the number of RPC requests made. OERPLib already avoid to auto-fetch sub-relations of a record, it only fetch them when the user accesses the relation field (the list of IDs behind your 'item_ids' field is fetch from OpenERP when you access this field on the first time):

>>> object_obj = oerp.get('my.object')
>>> myobject = object_obj.browse(1) # Only 'char', 'float' and other normal fields are fetched here. No relational field values
>>> list(myobject.item_ids) # IDs contains in 'item_ids' are requested to OpenERP. This allow an unprivileged user to browse 'myobject', and receive an exception if it access a relation such as 'item_ids' on which he has no access right

But yes, there are surely some improvements to do about browsable records in OERPLib, maybe an option in OERP.config to change the default behaviour:

>>> object_obj = oerp.get('my.object')
>>> object_obj.browse(IDS) # Like now, returns a generator to iterate records one by one => [read(ID) for ID in IDS]
>>> oerp.config['browse_all_at_once'] = True
>>> object_obj.browse(IDS) # Do only one call to the 'read()' method => read(IDS)

Revision history for this message
Sébastien Alix (sebastien-alix) said :

(about a new parameter on "browse()", I just avoid to differ too much from the standard method signatures of OpenERP, but I may be wrong, don't know :)