Las operaciones básicas del ORM de Odoo con xmlrpc

Gustavo Orrillo
- 05/12/2022 - 6 min. de lectura

Primeros pasos

Como empezar a hacer scripts en Python que interactuan mediante xmlrpc con Odoo? Es muy sencillo, primero hagamos un simple "Hola mundo" con Python. En un script de python que puede ser invocado desde la línea de comandos:

#!/usr/bin/python3
print("Hola Mundo")

La primer línea invoca el interprete de Python. La segunda imprime "Hola Mundo". Luego en la línea de comandos tenemos que cambiar los permisos del script para que sea ejecutable

chmod +x hola_mundo.py

Y luego procedemos a ejecutarlo desde la línea de comandos

./hola_mundo.py


De la misma manera que imprimimos un sencillo "Hola Mundo" podemos hacer cosas más complicadas.

Conectandose y autenticandose con Odoo

El primer problema a resolver (y no menor) es la conección con Odoo. Para ello debemos importar la biblioteca xmlrpc y proveer la URL del sistema Odoo con el cual queremos conectarmos. Por ejemplo, supongamos que tenemos corriendo un Odoo en un server local y queremos conocer su versión, debemos hacer

#!/usr/bin/python3
from xmlrpc import client
url = 'http://localhost:8069'
common = client.ServerProxy('{}/xmlrpc/2/common'.format(url))
res = common.version()
print(res)

 Esto imprimirá la versión del Odoo con el que se conectó en formato json

{'server_version': '15.0', 'server_version_info': [15, 0, 0, 'final', 0, ''], 'server_serie': '15.0', 'protocol_version': 1}

En este caso nos estamos conectando con un end-point para el cual no se requiere autenticación. Por lo general uno lo usa para testear la conección con el sistema con el cual va a interactuar. El paso siguiente es autenticarse con Odoo, para ello debemos informar la base de datos, el usuario y el password. 

dbname = 'odoo15ar-v6'
user = 'admin'
pwd = 'admin'
uid = common.authenticate(dbname, user, pwd, {})
print(uid)

Si uno se pudo autenticar con Odoo, el mismo responderá con el ID del usuario autenticado. Caso contrario, devolverá un False.



Invocando los métodos básicos del ORM: búsqueda, lectura, creación, escritura, borrado

Por lo pronto el ORM implementa al menos cuatro operaciones básicas, con las cuales ya se pueden hacer muchas cosas. Búsqueda, creación, escritura y borrado de registros. Parece poco, pero la realidad es que permite hacer muchas cosas. Por ejemplo permite exportar datos de Odoo y ademas importar datos en Odoo. Hay otras operaciones que se pueden realizar, como por ejemplo search_count que devuelve la cantidad de registros que cumplen con una condición. Pero no son tan comunes (y muchas veces requieren conocer el funcionamiento interno de Odoo). Igual en el último apartado de este post vamos a hablar de como invocar métodos de los objetos mediante xmlrpc.

Para realizar operaciones con los registros es necesario utilizar el end-point xmlrpc/2/object. Este end-point es necesario para ejecutar el método remoto execute_kw que utilizaremos para invocar los métodos en Odoo.

models = client.ServerProxy('{}/xmlrpc/2/object'.format(url))

Y cada llamada a execute_kw requiere los siguientes parámetros

  • base de datos

  • el user_id que uno obtiene mediante la función authenticate

  • el password del usuario

  • el nombre del modelo

  • el nombre del método

  • una lista de parámetros que pueden ser ids o condiciones (dependiendo del método)

  • un diccionario opcional de parámetros

Ahora vamos a mostrar como se usan los diferentes métodos para la búsqueda, lectura, creación, borrado y escritura. Para ello vamos a chequear que en el modelo de paises (res.country) exista un país llamado Odoolandia. Si no existe, crearlo. Luego actualizar su moneda a USD (quieren ahorrarse un banco central), listarlo y por último borrarlo. 

Búsqueda de registros

La búsqueda de registros se hace invocando el método search proveyendo como parámetro una lista con los dominios de búsqueda. Por ejemplo, en este caso vamos a buscar

# search
country_ids = models.execute_kw(dbname, uid, pwd, 'res.country', 'search', [[['name', '=', 'Odoolandia']]])
if country_ids:
    print('Existe Odoolandia')
else:
    print('No existe Odoolandia')

El método search siempre devuelve una lista con los IDs (hablamos de ellos anteriormente en este post) que cumplen con la condición de búsqueda. Si la operación search no encuentra ningún registro que satisfaga la condición de búsqueda, retorna una lista vacía.

Creación de registros

Para crear un registro se debe invocar el método create. A dicho método hay que pasarle como parámetro un diccionario de valores con los que se creará el nuevo registro. El método create devuelve del registro creado (o lista de IDs de registros creados). En el caso de error se creará una excepción de Python.

# create
vals = {
    'name': 'Odoolandia',
    'code': 'OD',
}
return_id = models.execute_kw(dbname,uid, pwd, 'res.country', 'create', [vals])
print('Registro creado, asignado el ID ',return_id) 

Al menos a partir de la versión 13 se puede pasar también una lista de diccionarios con los registros que se van a crear. Esto tiene la ventaja de ser más rápido, ya que por cada llamada a execute_kw hay implícita una transacción de PostgreSQL. Y pasando como parámetro una lista de diccionarios, todos esos registros se crearán en una sola transacción. Lo cual es más rápido.

Actualización de registros

Para actualizar los registros se deberá invocar el método 'write', al cual se le deben pasar como parámetro una lista con dos elementos; el primero es una lista de IDs a actualizar y el segundo es un diccionario de valores que van a actualizar el registro. Supongamos que vamos a actualizar el país Odoolandia y queremos asignarle la moneda dolar (USD) y el código O1.

# write
# busqueda de moneda USD
currency_usd = models.execute_kw(dbname,uid,pwd,'res.currency','search',[[['name','=','USD']]])
vals = {
    'currency_id': currency_usd[0],
    'code': 'O1'
}
res = models.execute_kw(dbname,uid,pwd,'res.country','write',[[return_id],vals])
print('Resultado escritura',res)

El método devuelve dos valores. True o False. True si puede actualizar el registro y False si se presentó algún error. Tengan en cuenta que los campos de tipo many2one requieren que se los actualice con el ID del registro relacionado (es por eso que en el ejemplo se realiza la búsqueda del ID de la moneda dolar).

Lectura de registros

Para leer los registros, se debe invocar el método 'read' pasandole como parámetros una lista con dos valores: el primero es una lista de IDs de registros a leer y el segundo elemento es opcional con una lista de los campos a listar. Por ejemplo, si queremos listar el campo code, name y currency_id del país que creamos anteriormente, debemos hacer

# read
country_data = models.execute_kw(dbname,uid,pwd,'res.country','read',[[return_id],['name','code','currency_id']])
print(country_data)

el cual va a tener como retorno una lista con diccionarios de los campos leidos.

[{'id': 263, 'name': 'Odoolandia', 'code': 'O1', 'currency_id': [2, 'USD']}]

Tengan en cuenta que los campos many2one devuelven una lista con dos elementos, el primero es el ID del registro relacionado y el segundo es el valor del campo display_name.

Borrado de registros

Supongamos que queremos borrar el registro. Para ello debemos invocar el método unlink pasando como parámetro la lista de registros a borrar.

# unlink
res = models.execute_kw(dbname,uid,pwd,'res.country','unlink',[[return_id]])
print(res)

El método retorna True si el borrado fue exitoso y False si no se pudo hacerlo.

Código final

Este es el código final de lo que estuvimos hablando hasta ahora

#!/usr/bin/python3
from xmlrpc import client
url = 'http://localhost:8069'
common = client.ServerProxy('{}/xmlrpc/2/common'.format(url))
res = common.version()
dbname = 'mrputilsv1'
user = 'admin'
pwd = 'admin'
uid = common.authenticate(dbname, user, pwd, {})
print(res)
print(uid)
models = client.ServerProxy('{}/xmlrpc/2/object'.format(url))
# search
country_ids = models.execute_kw(dbname, uid, pwd, 'res.country', 'search', [[['name', '=', 'Odoolandia']]])
if country_ids:
    print('Existe Odoolandia')
else:
    print('No existe Odoolandia')
# create
vals = {
    'name': 'Odoolandia'
}
return_id = models.execute_kw(dbname,uid, pwd, 'res.country', 'create', [vals])
print('Registro creado, asignado el ID ',return_id)
# write
# busqueda de moneda USD
currency_usd = models.execute_kw(dbname,uid,pwd,'res.currency','search',[[['name','=','USD']]])
vals = {
    'currency_id': currency_usd[0],
    'code': 'O1'
}
res = models.execute_kw(dbname,uid,pwd,'res.country','write',[[return_id],vals])
print('Resultado escritura',res)
# read
country_data = models.execute_kw(dbname,uid,pwd,'res.country','read',[[return_id],['name','code','currency_id']])
print(country_data)
# unlink
res = models.execute_kw(dbname,uid,pwd,'res.country','unlink',[[return_id]])
print(res)

Contexto e invocando otros métodos

Muchas veces es necesario invocar los métodos con el contexto (por ejemplo, para la creación de líneas de facturas). Para ello como último parámetro un diccionario donde cada uno de los elementos es context. Por ejemplo,

account_move_id = models.execute_kw(db, uid, pwd, 'account.move', 'create', [vals], {'context' :{'check_move_validity': False}})
Por último, tambien se pueden invocar métodos en cada uno de los modelos. Como por ejemplo action_confirm o action_post. Pero ya no es tan común.
Acerca de:

Gustavo Orrillo

Passionate about programming, he has implemented Odoo for different types of businesses since 2010. In Moldeo Interactive he is a founding Partner and Programmer; In addition to writing on the Blog about different topics related to the developments he makes.