classes: | stripe |
---|
Odoo is mostly extended internally via modules, but much of its features and all of its data is also available from the outside for external analysis or integration with various tools. Part of the :ref:`reference/orm/model` API is easily available over XML-RPC and accessible from a variety of languages.
If you already have an Odoo server installed, you can just use its parameters
.. rst-class:: switchable .. code-block:: python url = <insert server URL> db = <insert database name> username = 'admin' password = <insert password for your admin user (default: admin)> .. code-block:: ruby url = <insert server URL> db = <insert database name> username = "admin" password = <insert password for your admin user (default: admin)> .. code-block:: php $url = <insert server URL>; $db = <insert database name>; $username = "admin"; $password = <insert password for your admin user (default: admin)>; .. code-block:: java final String url = <insert server URL>, db = <insert database name>, username = "admin", password = <insert password for your admin user (default: admin)>;
To make exploration simpler, you can also ask https://demo.odoo.com for a test database:
.. rst-class:: switchable .. code-block:: python import xmlrpclib info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start() url, db, username, password = \ info['host'], info['database'], info['user'], info['password'] .. code-block:: ruby require "xmlrpc/client" info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start') url, db, username, password = \ info['host'], info['database'], info['user'], info['password'] .. code-block:: php require_once('ripcord.php'); $info = ripcord::client('https://demo.odoo.com/start')->start(); list($url, $db, $username, $password) = array($info['host'], $info['database'], $info['user'], $info['password']); .. code-block:: java final XmlRpcClient client = new XmlRpcClient(); final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); start_config.setServerURL(new URL("https://demo.odoo.com/start")); final Map<String, String> info = (Map<String, String>)client.execute( start_config, "start", Collections.emptyList()); final String url = info.get("host"), db = info.get("database"), username = info.get("user"), password = info.get("password");
.. rst-class:: force-right .. note:: :class: only-php These examples use the `Ripcord <https://code.google.com/p/ripcord/>`_ library, which provides a simple XML-RPC API. Ripcord requires that `XML-RPC support be enabled <http://php.net/manual/en/xmlrpc.installation.php>`_ in your PHP installation. Since calls are performed over `HTTPS <http://en.wikipedia.org/wiki/HTTP_Secure>`_, it also requires that the `OpenSSL extension <http://php.net/manual/en/openssl.installation.php>`_ be enabled. .. note:: :class: only-java These examples use the `Apache XML-RPC library <https://ws.apache.org/xmlrpc/>`_
Odoo requires users of the API to be authenticated before being able to query much data.
The xmlrpc/2/common
endpoint provides meta-calls which don't require
authentication, such as the authentication itself or fetching version
information. To verify if the connection information is correct before trying
to authenticate, the simplest call is to ask for the server's version. The
authentication itself is done through the authenticate
function and
returns a user identifier (uid
) used in authenticated calls instead of
the login.
.. rst-class:: switchable .. code-block:: python common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url)) common.version() .. code-block:: ruby common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common") common.call('version') .. code-block:: php $common = ripcord::client("$url/xmlrpc/2/common"); $common->version(); .. code-block:: java final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl(); common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url))); client.execute(common_config, "version", Collections.emptyList());
{
"server_version": "8.0",
"server_version_info": [8, 0, 0, "final", 0],
"server_serie": "8.0",
"protocol_version": 1,
}
.. rst-class:: switchable .. code-block:: python uid = common.authenticate(db, username, password, {}) .. code-block:: ruby uid = common.call('authenticate', db, username, password, {}) .. code-block:: php $uid = $common->authenticate($db, $username, $password, array()); .. code-block:: java int uid = (int)client.execute( common_config, "authenticate", Arrays.asList( db, username, password, Collections.emptyMap()));
The second — and most generally useful — is xmlrpc/2/object
which is used
to call methods of odoo models via the execute_kw
RPC function.
Each call to execute_kw
takes the following parameters:
- the database to use, a string
- the user id (retrieved through
authenticate
), an integer - the user's password, a string
- the model name, a string
- the method name, a string
- an array/list of parameters passed by position
- a mapping/dict of parameters to pass by keyword (optional)
.. rst-class:: force-right
For instance to see if we can read the res.partner
model we can call
check_access_rights
with operation
passed by position and
raise_exception
passed by keyword (in order to get a true/false result
rather than true/error):
.. rst-class:: switchable .. code-block:: python models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url)) models.execute_kw(db, uid, password, 'res.partner', 'check_access_rights', ['read'], {'raise_exception': False}) .. code-block:: ruby models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy models.execute_kw(db, uid, password, 'res.partner', 'check_access_rights', ['read'], {raise_exception: false}) .. code-block:: php $models = ripcord::client("$url/xmlrpc/2/object"); $models->execute_kw($db, $uid, $password, 'res.partner', 'check_access_rights', array('read'), array('raise_exception' => false)); .. code-block:: java final XmlRpcClient models = new XmlRpcClient() {{ setConfig(new XmlRpcClientConfigImpl() {{ setServerURL(new URL(String.format("%s/xmlrpc/2/object", url))); }}); }}; models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "check_access_rights", Arrays.asList("read"), new HashMap() {{ put("raise_exception", false); }} ));
true
.. todo:: this should be runnable and checked
Records can be listed and filtered via :meth:`~openerp.models.Model.search`.
:meth:`~openerp.models.Model.search` takes a mandatory :ref:`domain <reference/orm/domains>` filter (possibly empty), and returns the database identifiers of all records matching the filter. To list customer companies for instance:
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True], ['customer', '=', True]]]) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true], ['customer', '=', true]]]) .. code-block:: php $domain = array(array('is_company', '=', true), array('customer', '=', true)); $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array($domain)); .. code-block:: java final List domain = Arrays.asList( Arrays.asList("is_company", "=", true), Arrays.asList("customer", "=", true)); Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "search", Arrays.asList(domain) )));
[7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
By default a research will return the ids of all records matching the
condition, which may be a huge number. offset
and limit
parameters are
available to only retrieve a subset of all matched records.
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True], ['customer', '=', True]]], {'offset': 10, 'limit': 5}) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true], ['customer', '=', true]]], {offset: 10, limit: 5}) .. code-block:: php $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array($domain), array('offset'=>10, 'limit'=>5)); .. code-block:: java Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "search", Arrays.asList(domain), new HashMap() {{ put("offset", 10); put("limit", 5); }} )));
[13, 20, 30, 22, 29]
Rather than retrieve a possibly gigantic list of records and count them afterwards, :meth:`~openerp.models.Model.search_count` can be used to retrieve only the number of records matching the query. It takes the same :ref:`domain <reference/orm/domains>` filter as :meth:`~openerp.models.Model.search` and no other parameter.
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', True], ['customer', '=', True]]]) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', true], ['customer', '=', true]]]) .. code-block:: php $models->execute_kw($db, $uid, $password, 'res.partner', 'search_count', array($domain)); .. code-block:: java (Integer)models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "search_count", Arrays.asList(domain) ));
19
Warning
calling search
then search_count
(or the other way around) may not
yield coherent results if other users are using the server: stored data
could have changed between the calls
Record data is accessible via the :meth:`~openerp.models.Model.read` method, which takes a list of ids (as returned by :meth:`~openerp.models.Model.search`) and optionally a list of fields to fetch. By default, it will fetch all the fields the current user can read, which tends to be a huge amount.
.. rst-class:: switchable .. code-block:: python ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True], ['customer', '=', True]]], {'limit': 1}) [record] = models.execute_kw(db, uid, password, 'res.partner', 'read', [ids]) # count the number of fields fetched by default len(record) .. code-block:: ruby ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true], ['customer', '=', true]]], {limit: 1}) record = models.execute_kw(db, uid, password, 'res.partner', 'read', [ids]).first # count the number of fields fetched by default record.length .. code-block:: php $ids = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array($domain), array('limit'=>1)); $records = $models->execute_kw($db, $uid, $password, 'res.partner', 'read', array($ids)); // count the number of fields fetched by default count($records[0]); .. code-block:: java final List ids = Arrays.asList((Object[])models.execute( "execute_kw", Arrays.asList( db, uid, password, "res.partner", "search", Arrays.asList(domain), new HashMap() {{ put("limit", 1); }}))); final Map record = (Map)((Object[])models.execute( "execute_kw", Arrays.asList( db, uid, password, "res.partner", "read", Arrays.asList(ids) ) ))[0]; // count the number of fields fetched by default record.size();
121
Conversedly, picking only three fields deemed interesting.
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {'fields': ['name', 'country_id', 'comment']}) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {fields: %w(name country_id comment)}) .. code-block:: php $models->execute_kw($db, $uid, $password, 'res.partner', 'read', array($ids), array('fields'=>array('name', 'country_id', 'comment'))); .. code-block:: java Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "read", Arrays.asList(ids), new HashMap() {{ put("fields", Arrays.asList("name", "country_id", "comment")); }} )));
[{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
Note
even if the id
field is not requested, it is always returned
:meth:`~openerp.models.Model.fields_get` can be used to inspect a model's fields and check which ones seem to be of interest.
Because
it returns a great amount of meta-information (it is also used by client
programs) it should be filtered before printing, the most interesting items
for a human user are string
(the field's label), help
(a help text if
available) and type
(to know which values to expect, or to send when
updating a record):
.. rst-class:: switchable .. code-block:: python fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', []) # filter keys of field attributes for display {field: { k: v for k, v in attributes.iteritems() if k in ['string', 'help', 'type'] } for field, attributes in fields.iteritems()} .. code-block:: ruby fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', []) # filter keys of field attributes for display fields.each {|k, v| fields[k] = v.keep_if {|kk, vv| %w(string help type).include? kk} } .. code-block:: php $fields_full = $models->execute_kw($db, $uid, $password, 'res.partner', 'fields_get', array()); // filter keys of field attributes for display $allowed = array_flip(array('string', 'help', 'type')); $fields = array(); foreach($fields_full as $field => $attributes) { $fields[$field] = array_intersect_key($attributes, $allowed); } .. code-block:: java final Map<String, Map<String, Object>> fields = (Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "fields_get", Collections.emptyList())); // filter keys of field attributes for display final List<String> allowed = Arrays.asList("string", "help", "type"); new HashMap<String, Map<String, Object>>() {{ for(Entry<String, Map<String, Object>> item: fields.entrySet()) { put(item.getKey(), new HashMap<String, Object>() {{ for(Entry<String, Object> it: item.getValue().entrySet()) { if (allowed.contains(it.getKey())) { put(it.getKey(), it.getValue()); } } }}); } }};
{
"ean13": {
"type": "char",
"help": "BarCode",
"string": "EAN13"
},
"property_account_position": {
"type": "many2one",
"help": "The fiscal position will determine taxes and accounts used for the partner.",
"string": "Fiscal Position"
},
"signup_valid": {
"type": "boolean",
"help": "",
"string": "Signup Token is Valid"
},
"date_localization": {
"type": "date",
"help": "",
"string": "Geo Localization Date"
},
"ref_companies": {
"type": "one2many",
"help": "",
"string": "Companies that refers to partner"
},
"sale_order_count": {
"type": "integer",
"help": "",
"string": "# of Sales Order"
},
"purchase_order_count": {
"type": "integer",
"help": "",
"string": "# of Purchase Order"
},
Because that is a very common task, Odoo provides a
:meth:`~openerp.models.Model.search_read` shortcut which as its name notes is
equivalent to a :meth:`~openerp.models.Model.search` followed by a
:meth:`~openerp.models.Model.read`, but avoids having to perform two requests
and keep ids around. Its arguments are similar to
:meth:`~openerp.models.Model.search`'s, but it can also take a list of
fields
(like :meth:`~openerp.models.Model.read`, if that list is not
provided it'll fetch all fields of matched records):
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', True], ['customer', '=', True]]], {'fields': ['name', 'country_id', 'comment'], 'limit': 5}) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', true], ['customer', '=', true]]], {fields: %w(name country_id comment), limit: 5}) .. code-block:: php $models->execute_kw($db, $uid, $password, 'res.partner', 'search_read', array($domain), array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5)); .. code-block:: java Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "search_read", Arrays.asList(domain), new HashMap() {{ put("fields", Arrays.asList("name", "country_id", "comment")); put("limit", 5); }} )));
[
{
"comment": false,
"country_id": [ 21, "Belgium" ],
"id": 7,
"name": "Agrolait"
},
{
"comment": false,
"country_id": [ 76, "France" ],
"id": 18,
"name": "Axelor"
},
{
"comment": false,
"country_id": [ 233, "United Kingdom" ],
"id": 12,
"name": "Bank Wealthy and sons"
},
{
"comment": false,
"country_id": [ 105, "India" ],
"id": 14,
"name": "Best Designers"
},
{
"comment": false,
"country_id": [ 76, "France" ],
"id": 17,
"name": "Camptocamp"
}
]
.. rst-class:: switchable .. code-block:: python id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{ 'name': "New Partner", }]) .. code-block:: ruby id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{ name: "New Partner", }]) .. code-block:: php $id = $models->execute_kw($db, $uid, $password, 'res.partner', 'create', array(array('name'=>"New Partner"))); .. code-block:: java final Integer id = (Integer)models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "create", Arrays.asList(new HashMap() {{ put("name", "New Partner"); }}) ));
78
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], { 'name': "Newer partner" }]) # get record name after having changed it models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]]) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], { name: "Newer partner" }]) # get record name after having changed it models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]]) .. code-block:: php $models->execute_kw($db, $uid, $password, 'res.partner', 'write', array(array($id), array('name'=>"Newer partner"))); // get record name after having changed it $models->execute_kw($db, $uid, $password, 'res.partner', 'name_get', array(array($id))); .. code-block:: java models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "write", Arrays.asList( Arrays.asList(id), new HashMap() {{ put("name", "Newer Partner"); }} ) )); // get record name after having changed it Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "name_get", Arrays.asList(Arrays.asList(id)) )));
[[78, "Newer partner"]]
.. rst-class:: switchable .. code-block:: python models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]]) # check if the deleted record is still in the database models.execute_kw(db, uid, password, 'res.partner', 'search', [[['id', '=', id]]]) .. code-block:: ruby models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]]) # check if the deleted record is still in the database models.execute_kw(db, uid, password, 'res.partner', 'search', [[['id', '=', id]]]) .. code-block:: php $models->execute_kw($db, $uid, $password, 'res.partner', 'unlink', array(array($id))); // check if the deleted record is still in the database $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('id', '=', $id)))); .. code-block:: java models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "unlink", Arrays.asList(Arrays.asList(id)))); // check if the deleted record is still in the database Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( db, uid, password, "res.partner", "search", Arrays.asList(Arrays.asList(Arrays.asList("id", "=", 78))) )));
[]