MADATA / DEV documentation développeur English madata.africa

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

pythonles dix derniers clients crees
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'})
Toujours préciser fields

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.

pythonET 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 !).

pythonOU et negation
# 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']]
Lire un domaine à voix haute

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érateurEffet
= · !=Égal, différent.
> · >= · < · <=Comparaisons, y compris sur les dates.
in · not inAppartenance à une liste. ['state', 'in', ['draft', 'sent']].
like · not likeContient, sensible à la casse.
ilike · not ilikeContient, insensible à la casse. Le bon choix pour une recherche textuelle.
=like · =ilikeMotif complet, avec % et _ à votre charge.
child_ofL’enregistrement et ses descendants : un contact et ses contacts rattachés, un emplacement et ses sous-emplacements.
parent_ofL’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éthodeQuand
searchVous ne voulez que les id, pour les passer à une action.
readVous avez déjà les id et voulez des champs. read([[1, 2, 3]], {'fields': [...]}).
search_countVous ne voulez que le nombre. Ne ramenez pas mille lignes pour en compter mille.
name_searchVous cherchez comme le fait un champ de saisie : name_search('kou', limit=8) renvoie des paires [id, libellé].
read_groupVous voulez des totaux, pas des lignes.
fields_getVous 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.

pythonchiffre d’affaires par client, sur l’annee
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.

Regrouper par mois

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.

pythonparcourir un gros modele sans le charger d’un bloc
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
Triez sur un champ stable

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
langLa langue des libellés renvoyés : fr_FR, en_US. Les valeurs techniques, elles, ne changent jamais.
tzLe fuseau utilisé par les champs calculés qui en dépendent. Les datetime restent en UTC dans la réponse.
active_testFalse pour voir aussi les enregistrements archivés, que l’espace masque par défaut.
allowed_company_idsLe périmètre de sociétés de l’appel. Voir multi-société.
pythoncontexte : archives inclus, libelles en anglais
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.

pythonrecuperer l’image d’un article
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.