Lire des données
Une seule méthode couvre 90 % des besoins :
search_read. Elle cherche et renvoie les champs demandés en un
aller-retour. Les autres méthodes de lecture existent pour les 10 %
restants.
01search_read, le bon réflexe
clients = models.execute_kw(DB, uid, CLE, 'res.partner', 'search_read',
[[['customer_rank', '>', 0]]], # domaine
{'fields': ['name', 'email', 'phone', 'city'], # champs voulus
'limit': 10,
'order': 'create_date desc'})
Sans fields, l’espace
renvoie tous les champs de chaque ligne, calculs compris.
Sur account.move, cela multiplie la taille de la réponse et le
temps de l’appel par dix. Demandez ce dont vous avez besoin, rien de
plus.
02Les domaines
Un domaine est une liste de conditions. Chaque condition est un triplet
[champ, opérateur, valeur]. Mises côte à côte, elles se
combinent avec un ET implicite.
# factures clients comptabilisees de plus de 500 000 FCFA [['move_type', '=', 'out_invoice'], ['state', '=', 'posted'], ['amount_total', '>', 500000]]
Pour un OU, ou une négation, l’opérateur se place
avant les conditions qu’il gouverne, et il en prend deux
(une seule pour !).
# name = "Kouassi" OU email contient "kouassi" ['|', ['name', '=', 'Kouassi'], ['email', 'ilike', 'kouassi']] # trois branches : un OU par condition supplementaire ['|', '|', ['a', '=', 1], ['b', '=', 2], ['c', '=', 3]] # tout sauf les brouillons ['!', ['state', '=', 'draft']] # clients d'Abidjan OU de Bouake, et actifs ['&', ['active', '=', True], '|', ['city', '=', 'Abidjan'], ['city', '=', 'Bouake']]
L’opérateur annonce ce qui suit : « OU de (ceci) et (cela) ». En le lisant de gauche à droite comme une phrase, les domaines imbriqués cessent d’être illisibles. Et quand un OU en gouverne trois, il en faut deux à la suite.
03Opérateurs
| Opérateur | Effet |
|---|---|
= · != | Égal, différent. |
> · >= · < · <= | Comparaisons, y compris sur les dates. |
in · not in | Appartenance à une liste. ['state', 'in', ['draft', 'sent']]. |
like · not like | Contient, sensible à la casse. |
ilike · not ilike | Contient, insensible à la casse. Le bon choix pour une recherche textuelle. |
=like · =ilike | Motif complet, avec % et _ à votre charge. |
child_of | L’enregistrement et ses descendants : un contact et ses contacts rattachés, un emplacement et ses sous-emplacements. |
parent_of | L’enregistrement et ses ascendants. |
Sur un champ many2one, une comparaison directe accepte
l’id (['partner_id', '=', 42]) ; un
ilike travaille sur le libellé
(['partner_id', 'ilike', 'Kouassi']). Et un point traverse la
relation : ['partner_id.city', '=', 'Abidjan'].
04Les autres méthodes de lecture
| Méthode | Quand |
|---|---|
search | Vous ne voulez que les id, pour les passer à une action. |
read | Vous avez déjà les id et voulez des champs. read([[1, 2, 3]], {'fields': [...]}). |
search_count | Vous ne voulez que le nombre. Ne ramenez pas mille lignes pour en compter mille. |
name_search | Vous cherchez comme le fait un champ de saisie : name_search('kou', limit=8) renvoie des paires [id, libellé]. |
read_group | Vous voulez des totaux, pas des lignes. |
fields_get | Vous voulez savoir ce que le modèle contient. Voir Modèles & champs. |
05Regrouper et totaliser
read_group fait le calcul du côté du serveur. C’est la
différence entre une réponse en une seconde et un export de trente mille
lignes à additionner chez vous.
totaux = models.execute_kw(DB, uid, CLE, 'account.move', 'read_group',
[[['move_type', '=', 'out_invoice'],
['state', '=', 'posted'],
['invoice_date', '>=', '2026-01-01']],
['amount_total_signed'], # champs agreges
['partner_id']], # regroupement
{'lazy': False, 'orderby': 'amount_total_signed desc', 'limit': 20})
for ligne in totaux:
print(ligne['partner_id'][1], ligne['amount_total_signed'], ligne['__count'])
Chaque ligne du résultat porte la valeur de regroupement, les agrégats
demandés, un __count, et un __domain prêt à être
renvoyé à search_read pour obtenir le détail du groupe.
Ajoutez la granularité au champ de
regroupement : ['invoice_date:month'], ou :week,
:quarter, :year. Et passez plusieurs champs pour
croiser deux axes, par exemple ['partner_id', 'invoice_date:month']
avec lazy: False.
06Pagination & tri
limit et offset découpent, order
trie avec la syntaxe SQL habituelle.
PAGE = 500
offset = 0
while True:
lot = models.execute_kw(DB, uid, CLE, 'account.move.line', 'search_read',
[[['parent_state', '=', 'posted']]],
{'fields': ['date', 'account_id', 'debit', 'credit'],
'limit': PAGE, 'offset': offset, 'order': 'id'})
if not lot:
break
traiter(lot)
offset += PAGE
Paginer sans order,
ou en triant sur une date qui bouge, fait sauter ou répéter des lignes
entre deux pages. order: 'id' est ennuyeux et correct.
07Le contexte
Le contexte est un dictionnaire passé à côté des arguments. Il ne change pas ce que vous demandez, il change la façon dont l’espace y répond.
| Clé | Effet |
|---|---|
lang | La langue des libellés renvoyés : fr_FR, en_US. Les valeurs techniques, elles, ne changent jamais. |
tz | Le fuseau utilisé par les champs calculés qui en dépendent. Les datetime restent en UTC dans la réponse. |
active_test | False pour voir aussi les enregistrements archivés, que l’espace masque par défaut. |
allowed_company_ids | Le périmètre de sociétés de l’appel. Voir multi-société. |
tous = models.execute_kw(DB, uid, CLE, 'product.product', 'search_read',
[[]],
{'fields': ['name', 'active'],
'context': {'active_test': False, 'lang': 'en_US'}})
08Images & pièces jointes
Les champs binaires reviennent encodés en base64. Une image de produit se lit comme n’importe quel champ.
import base64
[article] = models.execute_kw(DB, uid, CLE, 'product.product', 'read',
[[512]], {'fields': ['name', 'image_1920']})
if article['image_1920']:
with open('article.png', 'wb') as f:
f.write(base64.b64decode(article['image_1920']))
Les documents joints vivent dans ir.attachment : filtrez sur
res_model et res_id pour retrouver ceux d’un
enregistrement, puis lisez datas. Attention au volume : ne
demandez datas que pour les pièces que vous allez réellement
télécharger.