7 Odoo is mostly extended internally via modules, but much of its features and
8 all of its data is also available from the outside for external analysis or
9 integration with various tools. Part of the :ref:`reference/orm/model` API is
10 easily available over XML-RPC_ and accessible from a variety of languages.
12 .. Odoo XML-RPC idiosyncracies:
13 * uses multiple endpoint and a nested call syntax instead of a
14 "hierarchical" server structure (e.g. ``openerp.res.partner.read()``)
15 * uses its own own manual auth system instead of basic auth or sessions
16 (basic is directly supported the Python and Ruby stdlibs as well as
17 ws-xmlrpc, not sure about ripcord)
18 * own auth is inconvenient as (uid, password) have to be explicitly passed
19 into every call. Session would allow db to be stored as well
20 These issues are especially visible in Java, somewhat less so in PHP
22 Connection and authentication
23 =============================
25 .. kinda gross because it duplicates existing bits
27 .. rst-class:: setupcode hidden
29 .. code-block:: python
32 info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
33 url, db, username, password = \
34 info['host'], info['database'], info['user'], info['password']
35 common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
36 uid = common.authenticate(db, username, password, {})
37 models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
41 require "xmlrpc/client"
42 info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
43 url, db, username, password = \
44 info['host'], info['database'], info['user'], info['password']
45 common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
46 uid = common.authenticate(db, username, password, {})
47 models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
51 require_once('ripcord.php');
52 $info = ripcord::client('https://demo.odoo.com/start')->start();
53 list($url, $db, $username, $password) =
54 array($info['host'], $info['database'], $info['user'], $info['password']);
55 $common = ripcord::client("$url/xmlrpc/2/common");
56 $uid = $common->authenticate($db, $username, $password, array());
57 $models = ripcord::client("$url/xmlrpc/2/object");
61 final XmlRpcClient client = new XmlRpcClient();
63 final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
64 start_config.setServerURL(new URL("https://demo.odoo.com/start"));
65 final Map<String, String> info = (Map<String, String>)client.execute(
66 start_config, "start", Collections.emptyList());
68 final String url = info.get("host"),
69 db = info.get("database"),
70 username = info.get("user"),
71 password = info.get("password");
73 final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
74 common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
76 int uid = (int)client.execute(
77 common_config, "authenticate", Arrays.asList(
78 db, username, password, Collections.emptyMap()));
80 final XmlRpcClient models = new XmlRpcClient() {{
81 setConfig(new XmlRpcClientConfigImpl() {{
82 setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
89 If you already have an Odoo server installed, you can just use its
92 .. rst-class:: switchable setup
94 .. code-block:: python
96 url = <insert server URL>
97 db = <insert database name>
99 password = <insert password for your admin user (default: admin)>
103 url = <insert server URL>
104 db = <insert database name>
106 password = <insert password for your admin user (default: admin)>
110 $url = <insert server URL>;
111 $db = <insert database name>;
113 $password = <insert password for your admin user (default: admin)>;
117 final String url = <insert server URL>,
118 db = <insert database name>,
120 password = <insert password for your admin user (default: admin)>;
122 To make exploration simpler, you can also ask https://demo.odoo.com for a test
125 .. rst-class:: switchable setup
127 .. code-block:: python
130 info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
131 url, db, username, password = \
132 info['host'], info['database'], info['user'], info['password']
136 require "xmlrpc/client"
137 info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
138 url, db, username, password = \
139 info['host'], info['database'], info['user'], info['password']
143 require_once('ripcord.php');
144 $info = ripcord::client('https://demo.odoo.com/start')->start();
145 list($url, $db, $username, $password) =
146 array($info['host'], $info['database'], $info['user'], $info['password']);
150 final XmlRpcClient client = new XmlRpcClient();
152 final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
153 start_config.setServerURL(new URL("https://demo.odoo.com/start"));
154 final Map<String, String> info = (Map<String, String>)client.execute(
155 start_config, "start", Collections.emptyList());
157 final String url = info.get("host"),
158 db = info.get("database"),
159 username = info.get("user"),
160 password = info.get("password");
162 .. rst-class:: force-right
167 These examples use the `Ripcord <https://code.google.com/p/ripcord/>`_
168 library, which provides a simple XML-RPC API. Ripcord requires that
169 `XML-RPC support be enabled
170 <http://php.net/manual/en/xmlrpc.installation.php>`_ in your PHP
173 Since calls are performed over
174 `HTTPS <http://en.wikipedia.org/wiki/HTTP_Secure>`_, it also requires that
175 the `OpenSSL extension
176 <http://php.net/manual/en/openssl.installation.php>`_ be enabled.
181 These examples use the `Apache XML-RPC library
182 <https://ws.apache.org/xmlrpc/>`_
187 Odoo requires users of the API to be authenticated before being able to query
190 The ``xmlrpc/2/common`` endpoint provides meta-calls which don't require
191 authentication, such as the authentication itself or fetching version
192 information. To verify if the connection information is correct before trying
193 to authenticate, the simplest call is to ask for the server's version. The
194 authentication itself is done through the ``authenticate`` function and
195 returns a user identifier (``uid``) used in authenticated calls instead of
198 .. rst-class:: switchable setup
200 .. code-block:: python
202 common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
207 common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
208 common.call('version')
212 $common = ripcord::client("$url/xmlrpc/2/common");
217 final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
218 common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
219 client.execute(common_config, "version", Collections.emptyList());
224 "server_version": "8.0",
225 "server_version_info": [8, 0, 0, "final", 0],
226 "server_serie": "8.0",
227 "protocol_version": 1,
230 .. rst-class:: switchable setup
232 .. code-block:: python
234 uid = common.authenticate(db, username, password, {})
238 uid = common.call('authenticate', db, username, password, {})
242 $uid = $common->authenticate($db, $username, $password, array());
246 int uid = (int)client.execute(
247 common_config, "authenticate", Arrays.asList(
248 db, username, password, Collections.emptyMap()));
253 The second — and most generally useful — is ``xmlrpc/2/object`` which is used
254 to call methods of odoo models via the ``execute_kw`` RPC function.
256 Each call to ``execute_kw`` takes the following parameters:
258 * the database to use, a string
259 * the user id (retrieved through ``authenticate``), an integer
260 * the user's password, a string
261 * the model name, a string
262 * the method name, a string
263 * an array/list of parameters passed by position
264 * a mapping/dict of parameters to pass by keyword (optional)
266 .. rst-class:: force-right
268 For instance to see if we can read the ``res.partner`` model we can call
269 ``check_access_rights`` with ``operation`` passed by position and
270 ``raise_exception`` passed by keyword (in order to get a true/false result
271 rather than true/error):
273 .. rst-class:: switchable setup
275 .. code-block:: python
277 models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
278 models.execute_kw(db, uid, password,
279 'res.partner', 'check_access_rights',
280 ['read'], {'raise_exception': False})
284 models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
285 models.execute_kw(db, uid, password,
286 'res.partner', 'check_access_rights',
287 ['read'], {raise_exception: false})
291 $models = ripcord::client("$url/xmlrpc/2/object");
292 $models->execute_kw($db, $uid, $password,
293 'res.partner', 'check_access_rights',
294 array('read'), array('raise_exception' => false));
298 final XmlRpcClient models = new XmlRpcClient() {{
299 setConfig(new XmlRpcClientConfigImpl() {{
300 setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
303 models.execute("execute_kw", Arrays.asList(
305 "res.partner", "check_access_rights",
306 Arrays.asList("read"),
307 new HashMap() {{ put("raise_exception", false); }}
314 .. todo:: this should be runnable and checked
319 Records can be listed and filtered via :meth:`~openerp.models.Model.search`.
321 :meth:`~openerp.models.Model.search` takes a mandatory
322 :ref:`domain <reference/orm/domains>` filter (possibly empty), and returns the
323 database identifiers of all records matching the filter. To list customer
324 companies for instance:
326 .. rst-class:: switchable
328 .. code-block:: python
330 models.execute_kw(db, uid, password,
331 'res.partner', 'search',
332 [[['is_company', '=', True], ['customer', '=', True]]])
336 models.execute_kw(db, uid, password,
337 'res.partner', 'search',
338 [[['is_company', '=', true], ['customer', '=', true]]])
342 $domain = array(array('is_company', '=', true),
343 array('customer', '=', true));
344 $models->execute_kw($db, $uid, $password,
345 'res.partner', 'search', array($domain));
349 final List domain = Arrays.asList(
350 Arrays.asList("is_company", "=", true),
351 Arrays.asList("customer", "=", true));
352 Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
354 "res.partner", "search",
355 Arrays.asList(domain)
360 [7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
365 By default a research will return the ids of all records matching the
366 condition, which may be a huge number. ``offset`` and ``limit`` parameters are
367 available to only retrieve a subset of all matched records.
369 .. rst-class:: switchable
371 .. code-block:: python
373 models.execute_kw(db, uid, password,
374 'res.partner', 'search',
375 [[['is_company', '=', True], ['customer', '=', True]]],
376 {'offset': 10, 'limit': 5})
380 models.execute_kw(db, uid, password,
381 'res.partner', 'search',
382 [[['is_company', '=', true], ['customer', '=', true]]],
383 {offset: 10, limit: 5})
387 $models->execute_kw($db, $uid, $password,
388 'res.partner', 'search',
390 array('offset'=>10, 'limit'=>5));
394 Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
396 "res.partner", "search",
397 Arrays.asList(domain),
398 new HashMap() {{ put("offset", 10); put("limit", 5); }}
408 Rather than retrieve a possibly gigantic list of records and count them
409 afterwards, :meth:`~openerp.models.Model.search_count` can be used to retrieve
410 only the number of records matching the query. It takes the same
411 :ref:`domain <reference/orm/domains>` filter as
412 :meth:`~openerp.models.Model.search` and no other parameter.
414 .. rst-class:: switchable
416 .. code-block:: python
418 models.execute_kw(db, uid, password,
419 'res.partner', 'search_count',
420 [[['is_company', '=', True], ['customer', '=', True]]])
424 models.execute_kw(db, uid, password,
425 'res.partner', 'search_count',
426 [[['is_company', '=', true], ['customer', '=', true]]])
430 $models->execute_kw($db, $uid, $password,
431 'res.partner', 'search_count',
436 (Integer)models.execute("execute_kw", Arrays.asList(
438 "res.partner", "search_count",
439 Arrays.asList(domain)
448 calling ``search`` then ``search_count`` (or the other way around) may not
449 yield coherent results if other users are using the server: stored data
450 could have changed between the calls
455 Record data is accessible via the :meth:`~openerp.models.Model.read` method,
456 which takes a list of ids (as returned by
457 :meth:`~openerp.models.Model.search`) and optionally a list of fields to
458 fetch. By default, it will fetch all the fields the current user can read,
459 which tends to be a huge amount.
461 .. rst-class:: switchable
463 .. code-block:: python
465 ids = models.execute_kw(db, uid, password,
466 'res.partner', 'search',
467 [[['is_company', '=', True], ['customer', '=', True]]],
469 [record] = models.execute_kw(db, uid, password,
470 'res.partner', 'read', [ids])
471 # count the number of fields fetched by default
476 ids = models.execute_kw(db, uid, password,
477 'res.partner', 'search',
478 [[['is_company', '=', true], ['customer', '=', true]]],
480 record = models.execute_kw(db, uid, password,
481 'res.partner', 'read', [ids]).first
482 # count the number of fields fetched by default
487 $ids = $models->execute_kw($db, $uid, $password,
488 'res.partner', 'search',
491 $records = $models->execute_kw($db, $uid, $password,
492 'res.partner', 'read', array($ids));
493 // count the number of fields fetched by default
498 final List ids = Arrays.asList((Object[])models.execute(
499 "execute_kw", Arrays.asList(
501 "res.partner", "search",
502 Arrays.asList(domain),
503 new HashMap() {{ put("limit", 1); }})));
504 final Map record = (Map)((Object[])models.execute(
505 "execute_kw", Arrays.asList(
507 "res.partner", "read",
511 // count the number of fields fetched by default
518 Conversedly, picking only three fields deemed interesting.
520 .. rst-class:: switchable
522 .. code-block:: python
524 models.execute_kw(db, uid, password,
525 'res.partner', 'read',
526 [ids], {'fields': ['name', 'country_id', 'comment']})
530 models.execute_kw(db, uid, password,
531 'res.partner', 'read',
532 [ids], {fields: %w(name country_id comment)})
536 $models->execute_kw($db, $uid, $password,
537 'res.partner', 'read',
539 array('fields'=>array('name', 'country_id', 'comment')));
543 Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
545 "res.partner", "read",
548 put("fields", Arrays.asList("name", "country_id", "comment"));
554 [{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
556 .. note:: even if the ``id`` field is not requested, it is always returned
558 Listing record fields
559 ---------------------
561 :meth:`~openerp.models.Model.fields_get` can be used to inspect
562 a model's fields and check which ones seem to be of interest.
565 it returns a great amount of meta-information (it is also used by client
566 programs) it should be filtered before printing, the most interesting items
567 for a human user are ``string`` (the field's label), ``help`` (a help text if
568 available) and ``type`` (to know which values to expect, or to send when
571 .. rst-class:: switchable
573 .. code-block:: python
575 fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [])
576 # filter keys of field attributes for display
578 k: v for k, v in attributes.iteritems()
579 if k in ['string', 'help', 'type']
581 for field, attributes in fields.iteritems()}
585 fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [])
586 # filter keys of field attributes for display
588 fields[k] = v.keep_if {|kk, vv| %w(string help type).include? kk}
593 $fields_full = $models->execute_kw($db, $uid, $password,
594 'res.partner', 'fields_get', array());
595 // filter keys of field attributes for display
596 $allowed = array_flip(array('string', 'help', 'type'));
598 foreach($fields_full as $field => $attributes) {
599 $fields[$field] = array_intersect_key($attributes, $allowed);
604 final Map<String, Map<String, Object>> fields =
605 (Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList(
607 "res.partner", "fields_get",
608 Collections.emptyList()));
609 // filter keys of field attributes for display
610 final List<String> allowed = Arrays.asList("string", "help", "type");
611 new HashMap<String, Map<String, Object>>() {{
612 for(Entry<String, Map<String, Object>> item: fields.entrySet()) {
613 put(item.getKey(), new HashMap<String, Object>() {{
614 for(Entry<String, Object> it: item.getValue().entrySet()) {
615 if (allowed.contains(it.getKey())) {
616 put(it.getKey(), it.getValue());
631 "property_account_position": {
633 "help": "The fiscal position will determine taxes and accounts used for the partner.",
634 "string": "Fiscal Position"
639 "string": "Signup Token is Valid"
641 "date_localization": {
644 "string": "Geo Localization Date"
649 "string": "Companies that refers to partner"
651 "sale_order_count": {
654 "string": "# of Sales Order"
656 "purchase_order_count": {
659 "string": "# of Purchase Order"
665 Because that is a very common task, Odoo provides a
666 :meth:`~openerp.models.Model.search_read` shortcut which as its name notes is
667 equivalent to a :meth:`~openerp.models.Model.search` followed by a
668 :meth:`~openerp.models.Model.read`, but avoids having to perform two requests
669 and keep ids around. Its arguments are similar to
670 :meth:`~openerp.models.Model.search`'s, but it can also take a list of
671 ``fields`` (like :meth:`~openerp.models.Model.read`, if that list is not
672 provided it'll fetch all fields of matched records):
674 .. rst-class:: switchable
676 .. code-block:: python
678 models.execute_kw(db, uid, password,
679 'res.partner', 'search_read',
680 [[['is_company', '=', True], ['customer', '=', True]]],
681 {'fields': ['name', 'country_id', 'comment'], 'limit': 5})
685 models.execute_kw(db, uid, password,
686 'res.partner', 'search_read',
687 [[['is_company', '=', true], ['customer', '=', true]]],
688 {fields: %w(name country_id comment), limit: 5})
692 $models->execute_kw($db, $uid, $password,
693 'res.partner', 'search_read',
695 array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
699 Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
701 "res.partner", "search_read",
702 Arrays.asList(domain),
704 put("fields", Arrays.asList("name", "country_id", "comment"));
714 "country_id": [ 21, "Belgium" ],
720 "country_id": [ 76, "France" ],
726 "country_id": [ 233, "United Kingdom" ],
728 "name": "Bank Wealthy and sons"
732 "country_id": [ 105, "India" ],
734 "name": "Best Designers"
738 "country_id": [ 76, "France" ],
748 .. rst-class:: switchable
750 .. code-block:: python
752 id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
753 'name': "New Partner",
758 id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
764 $id = $models->execute_kw($db, $uid, $password,
765 'res.partner', 'create',
766 array(array('name'=>"New Partner")));
770 final Integer id = (Integer)models.execute("execute_kw", Arrays.asList(
772 "res.partner", "create",
773 Arrays.asList(new HashMap() {{ put("name", "New Partner"); }})
783 .. rst-class:: switchable
785 .. code-block:: python
787 models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
788 'name': "Newer partner"
790 # get record name after having changed it
791 models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
795 models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
796 name: "Newer partner"
798 # get record name after having changed it
799 models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
803 $models->execute_kw($db, $uid, $password, 'res.partner', 'write',
804 array(array($id), array('name'=>"Newer partner")));
805 // get record name after having changed it
806 $models->execute_kw($db, $uid, $password,
807 'res.partner', 'name_get', array(array($id)));
811 models.execute("execute_kw", Arrays.asList(
813 "res.partner", "write",
816 new HashMap() {{ put("name", "Newer Partner"); }}
819 // get record name after having changed it
820 Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
822 "res.partner", "name_get",
823 Arrays.asList(Arrays.asList(id))
828 [[78, "Newer partner"]]
833 .. rst-class:: switchable
835 .. code-block:: python
837 models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
838 # check if the deleted record is still in the database
839 models.execute_kw(db, uid, password,
840 'res.partner', 'search', [[['id', '=', id]]])
844 models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
845 # check if the deleted record is still in the database
846 models.execute_kw(db, uid, password,
847 'res.partner', 'search', [[['id', '=', id]]])
851 $models->execute_kw($db, $uid, $password,
852 'res.partner', 'unlink',
854 // check if the deleted record is still in the database
855 $models->execute_kw($db, $uid, $password,
856 'res.partner', 'search',
857 array(array(array('id', '=', $id))));
861 models.execute("execute_kw", Arrays.asList(
863 "res.partner", "unlink",
864 Arrays.asList(Arrays.asList(id))));
865 // check if the deleted record is still in the database
866 Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
868 "res.partner", "search",
869 Arrays.asList(Arrays.asList(Arrays.asList("id", "=", 78)))
876 .. _PostgreSQL: http://www.postgresql.org
877 .. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC