Écrire des données
Écrire tient en trois méthodes. Toute la difficulté est ailleurs : dans les champs relationnels, dans les actions métier qui font avancer un document, et dans le fait de ne pas créer deux fois la même chose.
01create, write, unlink
# creer : renvoie l'id cree
partner_id = models.execute_kw(DB, uid, CLE, 'res.partner', 'create', [{
'name': 'Ets Kouassi & Freres',
'email': '[email protected]',
'phone': '+225 07 00 00 00 00',
'city': 'Abidjan',
'customer_rank': 1,
}])
# modifier : renvoie True, et accepte une liste d'ids
models.execute_kw(DB, uid, CLE, 'res.partner', 'write',
[[partner_id], {'city': 'Bouake'}])
# supprimer
models.execute_kw(DB, uid, CLE, 'res.partner', 'unlink', [[partner_id]])
create accepte une liste de
dictionnaires et renvoie la liste des id créés, dans l’ordre.
Cent contacts en un appel au lieu de cent appels : c’est le même code, en
dix fois plus rapide.
Un
enregistrement référencé ailleurs refuse d’être supprimé : une facture
comptabilisée, un article déjà vendu. L’espace protège la cohérence de vos
comptes. Le bon geste est l’archivage :
write(ids, {'active': False}), qui masque sans rompre
l’historique.
02Champs relationnels
Un many2one s’écrit avec un id. Les
one2many et many2many, eux, s’écrivent par
commandes : des triplets qui disent quoi faire de la
liste.
| Commande | Effet |
|---|---|
(0, 0, {valeurs}) | Créer une ligne et la rattacher. La forme la plus fréquente. |
(1, id, {valeurs}) | Modifier une ligne existante. |
(2, id, 0) | Détacher la ligne et la supprimer. |
(3, id, 0) | Détacher sans supprimer. |
(4, id, 0) | Rattacher un enregistrement existant. |
(5, 0, 0) | Tout détacher. |
(6, 0, [ids]) | Remplacer la liste entière par ces identifiants. |
order_id = models.execute_kw(DB, uid, CLE, 'sale.order', 'create', [{
'partner_id': partner_id,
'order_line': [
(0, 0, {'product_id': 512, 'product_uom_qty': 3, 'price_unit': 15000}),
(0, 0, {'product_id': 731, 'product_uom_qty': 1}),
],
}])
Une ligne créée sans price_unit prend le prix de la liste de
prix applicable au client. C’est presque toujours ce que vous voulez : ne
forcez le prix que lorsque vous en avez une raison.
models.execute_kw(DB, uid, CLE, 'res.partner', 'write',
[[partner_id], {'category_id': [(6, 0, [3, 7])]}])
03Valeurs par défaut
default_get donne les valeurs que l’interface proposerait à
la création. Utile quand un modèle a des champs obligatoires dont vous
ignorez la valeur attendue.
defauts = models.execute_kw(DB, uid, CLE, 'sale.order', 'default_get',
[['company_id', 'currency_id', 'pricelist_id', 'warehouse_id']])
Les recalculs déclenchés
par une saisie à l’écran ne s’appliquent pas automatiquement à un
create par API. Renseignez explicitement ce dont vous avez
besoin, ou relisez l’enregistrement après création pour constater ce que
l’espace a calculé de lui-même.
04Les actions métier
Un document n’avance pas en écrivant son state. Chaque
bouton de l’interface correspond à une méthode, qui fait
tout ce qu’il faut : numérotation, écritures comptables, mouvements de
stock, contrôles. Appelez la méthode, jamais le champ.
| Geste | Modèle | Méthode |
|---|---|---|
| Confirmer un devis | sale.order | action_confirm |
| Facturer une commande | sale.order | _create_invoices |
| Comptabiliser une facture | account.move | action_post |
| Annuler une pièce comptable | account.move | button_draft puis button_cancel |
| Confirmer une commande d’achat | purchase.order | button_confirm |
| Valider un transfert de stock | stock.picking | button_validate |
write(id, {'state':
'posted'}) semble marcher : le document change d’état à l’écran. Mais
aucune écriture n’a été générée, aucun numéro attribué, aucun stock bougé.
Vous obtenez une facture comptabilisée qui n’existe pas en comptabilité, et
le déséquilibre ne se voit qu’au bilan.
05Un parcours complet
Du devis au paiement encaissé, en cinq appels.
# 1. le devis
order_id = models.execute_kw(DB, uid, CLE, 'sale.order', 'create', [{
'partner_id': partner_id,
'order_line': [(0, 0, {'product_id': 512, 'product_uom_qty': 3})],
}])
# 2. confirmation : le devis devient bon de commande
models.execute_kw(DB, uid, CLE, 'sale.order', 'action_confirm', [[order_id]])
# 3. facturation
models.execute_kw(DB, uid, CLE, 'sale.order', '_create_invoices', [[order_id]])
[commande] = models.execute_kw(DB, uid, CLE, 'sale.order', 'read',
[[order_id]], {'fields': ['invoice_ids']})
facture_id = commande['invoice_ids'][0]
# 4. comptabilisation : numerotation + ecritures
models.execute_kw(DB, uid, CLE, 'account.move', 'action_post', [[facture_id]])
# 5. reglement, par l'assistant d'enregistrement de paiement
wizard_id = models.execute_kw(DB, uid, CLE, 'account.payment.register', 'create',
[{'payment_date': '2026-09-17', 'journal_id': 7}],
{'context': {'active_model': 'account.move', 'active_ids': [facture_id]}})
models.execute_kw(DB, uid, CLE, 'account.payment.register',
'action_create_payments', [[wizard_id]],
{'context': {'active_model': 'account.move', 'active_ids': [facture_id]}})
Une fenêtre qui
s’ouvre par-dessus un écran est un modèle comme un autre : on le crée avec
ses valeurs, puis on appelle sa méthode de validation. Le
context lui dit sur quoi travailler : active_model
et active_ids. C’est le même mécanisme pour la facturation
groupée, les avoirs ou les relances.
06Ne pas créer deux fois
Un programme qui tourne toutes les heures finit par retomber sur une ligne déjà traitée : réseau coupé, relance manuelle, redémarrage. Sans précaution, chaque reprise duplique.
ref = f"BOUTIQUE-{commande_externe['id']}"
existant = models.execute_kw(DB, uid, CLE, 'sale.order', 'search',
[[['client_order_ref', '=', ref]]], {'limit': 1})
if existant:
order_id = existant[0]
else:
order_id = models.execute_kw(DB, uid, CLE, 'sale.order', 'create',
[{'partner_id': partner_id, 'client_order_ref': ref,
'order_line': lignes}])
Portez la référence du système d’origine dans un champ que vous pouvez
chercher : client_order_ref sur une commande,
ref sur un contact ou une pièce comptable. Ou passez par un
identifiant externe, qui sert
exactement à cela.
07Ce que l’API ne fera pas pour vous
- Pas de transaction sur plusieurs appels. Chaque appel est validé seul. Une suite d’appels interrompue laisse un état intermédiaire : prévoyez la reprise plutôt que d’espérer l’atomicité.
- Pas d’écriture sur les ressources facturées. Utilisateurs, sociétés, applications et abonnement se gèrent dans le portail.
- Pas de contournement des droits. Un utilisateur sans droit d’écriture sur un modèle ne le forcera pas par l’API.