The simplest method to access the server is using XML-RPC. We can use the xmlrpclib
library from Python's standard library for this. Remember that we are programming a client in order to connect to a server, so we need an Odoo server instance running to connect to. In our examples, we will assume that an Odoo server instance is running on the same machine (localhost
), but you can use any IP address or server name, if the server is running on another machine.
Let's get a fist contact with the external API. Start a Python console and type the following:
>>> import xmlrpclib >>> srv, db = 'http://localhost:8069', 'v8dev' >>> user, pwd = 'admin', 'admin' >>> common = xmlrpclib.ServerProxy('%s/xmlrpc/2/common' % srv) >>> common.version() {'server_version_info': [8, 0, 0, 'final', 0], 'server_serie': '8.0', 'server_version': '8.0', 'protocol_version': 1}
Here, we import the xmlrpclib
library and then set up some variables with the information for the server location and connection credentials. Feel free to adapt these to your specific setup.
Next, we set up access to the server's public services (not requiring a login), exposed at the /xmlrpc/2/common
endpoint. One of the methods that are available is version()
, which inspects the server version. We use it to confirm that we can communicate with the server.
Another public method is authenticate()
. In fact, this does not create a session, as you might be led to believe. This method just confirms that the username and password are accepted and returns the user ID that should be used in requests instead of the username, as shown here:
>>> uid = common.authenticate(db, user, pwd, {}) >>> print uid 1
With XML-RPC, no session is maintained and the authentication credentials are sent with every request. This adds some overhead to the protocol, but makes it simpler to use.
Next, we set up access to the server methods that need a login to be accessed. These are exposed at the /xmlrpc/2/object
endpoint, as shown in the following:
>>> api = xmlrpclib.ServerProxy('%s/xmlrpc/2/object' % srv) >>> api.execute_kw(db, uid, pwd, 'res.partner', 'search_count', [[]]) 70
Here, we are doing our first access to the server API, performing a count on the Partner records. Methods are called using the execute_kw()
method that takes the following arguments:
The preceding example calls the search_count
method of the res.partner
model with one positional argument, []
, and no keyword arguments. The positional argument is a search domain; since we are providing an empty list, it counts all the Partners.
Frequent actions are search
and read
. When called from the RPC, the search
method returns a list of IDs matching a domain. The browse
method is not available from the RPC, and read
should be used in its place to, given a list of record IDs, retrieve their data, as shown in the following code:
>>> api.execute_kw(db, uid, pwd, 'res.partner', 'search', [[('country_id', '=', 'be'), ('parent_id', '!=', False)]]) [43, 42] >>> api.execute_kw(db, uid, pwd, 'res.partner', 'read', [[43]], {'fields': ['id', 'name', 'parent_id']}) [{'parent_id': [7, 'Agrolait'], 'id': 43, 'name': 'Michel Fletcher'}]
Note that for the read method, we are using one positional argument for the list of IDs, [43]
, and one keyword argument, fields
. We can also notice that relational fields are retrieved as a pair, with the related record's ID and display name. That's something to keep in mind when processing the data in your code.
The search
and read
combination is so frequent that a search_read
method is provided to perform both operations in a single step. The same result as the previous two steps can be obtained with the following:
>>> api.execute_kw(db, uid, pwd, 'res.partner', 'search_read', [[('country_id', '=', 'be'), ('parent_id', '!=', False)]], {'fields': ['id', 'name', 'parent_id']})
The search_read
method behaves like read
, but expects as first positional argument a domain instead of a list of IDs. It's worth mentioning that the field
argument on read
and search_read
is not mandatory. If not provided, all fields will be retrieved.
The remaining model methods are all exposed through RPC, except for those starting with "_" that are considered private. This means that we can use create
, write
, and unlink
to modify data on the server as follows:
>>> api.execute_kw(db, uid, pwd, 'res.partner', 'create', [{'name': 'Packt'}]) 75 >>> api.execute_kw(db, uid, pwd, 'res.partner', 'write', [[75], {'name': 'Packt Pub'}]) True >>> api.execute_kw(db, uid, pwd, 'res.partner', 'read', [[75], ['id', 'name']]) [{'id': 75, 'name': 'Packt Pub'}] >>> api.execute_kw(db, uid, pwd, 'res.partner', 'unlink', [[75]]) True
One limitation of the XML-RPC protocol is that it does not support None
values. The implication is that methods that don't return anything won't be usable through XML-RPC, since they are implicitly returning None
. This is why methods should always finish with at least a return True
statement.