diff --git a/doc/_themes/odoodoc/__init__.py b/doc/_themes/odoodoc/__init__.py
index f646839d95b..38ee122a8e1 100644
--- a/doc/_themes/odoodoc/__init__.py
+++ b/doc/_themes/odoodoc/__init__.py
@@ -25,3 +25,8 @@ class Exercise(admonitions.BaseAdmonition):
from sphinx.locale import admonitionlabels, l_
admonitionlabels['exercise'] = l_('Exercise')
+
+# monkeypatch PHP lexer to not require
+
{{ super() }}
{%- endblock -%}
diff --git a/doc/_themes/odoodoc/static/style.css b/doc/_themes/odoodoc/static/style.css
index 9b5468f1bdd..3ea44b5e02d 100644
--- a/doc/_themes/odoodoc/static/style.css
+++ b/doc/_themes/odoodoc/static/style.css
@@ -6174,7 +6174,11 @@ button.close {
display: none !important;
}
}
+* {
+ box-sizing: border-box;
+}
body {
+ overflow: auto;
position: relative;
}
.document-super {
@@ -6643,20 +6647,11 @@ div.section > h2 {
*
* Generated via Pygments
*/
-.highlight {
- padding: 9px 14px;
- margin-bottom: 14px;
- background-color: #f7f7f9 !important;
- border: 1px solid #e1e1e8;
- border-radius: 4px;
-}
.highlight pre {
- color: #333;
- padding: 0 45px 0 0;
- margin-top: 0;
- margin-bottom: 0;
- background-color: transparent;
- border: 0;
+ padding: 4px;
+ font-size: 75%;
+ word-break: normal;
+ word-wrap: normal;
}
/*
* ZeroClipboard styles
@@ -6665,6 +6660,11 @@ div.section > h2 {
position: relative;
display: none;
}
+@media (min-width: 768px) {
+ .zero-clipboard {
+ display: block;
+ }
+}
.btn-clipboard {
position: absolute;
top: 0;
@@ -6684,11 +6684,6 @@ div.section > h2 {
background-color: #a24689;
border-color: #a24689;
}
-@media (min-width: 768px) {
- .zero-clipboard {
- display: block;
- }
-}
img.align-center {
display: block;
margin: 0 auto;
@@ -6710,10 +6705,63 @@ td.field-body > ul {
margin: 0;
padding: 0;
}
-pre {
- word-break: normal;
- word-wrap: normal;
-}
.descclassname {
opacity: 0.5;
}
+.stripe .section {
+ margin-bottom: 2em;
+}
+@media (min-width: 992px) {
+ .stripe .section > *,
+ .stripe .section > .force-left {
+ width: 49%;
+ float: left;
+ clear: left;
+ }
+ .stripe .section > .force-right,
+ .stripe .section > [class*=highlight] {
+ float: none;
+ clear: none;
+ margin-left: 51%;
+ }
+ .stripe .section > h1,
+ .stripe .section > h2,
+ .stripe .section > h3,
+ .stripe .section > h4,
+ .stripe .section > h5,
+ .stripe .section > h6 {
+ background-color: rgba(255, 255, 255, 0.7);
+ }
+ .stripe .section > h1,
+ .stripe .section > h2,
+ .stripe .section > h3,
+ .stripe .section > h4,
+ .stripe .section > h5,
+ .stripe .section > h6,
+ .stripe .section > .section {
+ position: relative;
+ width: auto;
+ float: none;
+ clear: both;
+ }
+ .stripe .bodywrapper {
+ position: relative;
+ }
+ .stripe .bodywrapper:before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 50%;
+ content: "";
+ width: 0;
+ border-left: 1px solid #777777;
+ }
+}
+.stripe .switchable > .highlight,
+.stripe [class*=only-] {
+ display: none;
+}
+.stripe .only-python,
+.stripe .highlight-python > .highlight {
+ display: block;
+}
diff --git a/doc/_themes/odoodoc/static/style.less b/doc/_themes/odoodoc/static/style.less
index ca8be90d4c2..182b81d25d7 100644
--- a/doc/_themes/odoodoc/static/style.less
+++ b/doc/_themes/odoodoc/static/style.less
@@ -17,7 +17,11 @@
// indent level for various items list e.g. dl, fields lists, ...
@item-indent: 30px;
+* {
+ box-sizing: border-box;
+}
body {
+ overflow: auto;
position: relative;
}
@@ -461,21 +465,13 @@ div.section > h2 {
*
* Generated via Pygments
*/
-
-.highlight {
- padding: 9px 14px;
- margin-bottom: 14px;
- background-color: #f7f7f9 !important;
- border: 1px solid #e1e1e8;
- border-radius: 4px;
-}
.highlight pre {
- color: #333;
- padding: 0 45px 0 0;
- margin-top: 0;
- margin-bottom: 0;
- background-color: transparent;
- border: 0;
+ padding: 4px;
+
+ font-size: 75%;
+ // code block lines should not wrap
+ word-break: normal;
+ word-wrap: normal;
}
/*
@@ -485,6 +481,9 @@ div.section > h2 {
.zero-clipboard {
position: relative;
display: none;
+ @media (min-width: @screen-sm-min) {
+ display: block;
+ }
}
.btn-clipboard {
position: absolute;
@@ -506,12 +505,6 @@ div.section > h2 {
border-color: @brand-primary;
}
-@media (min-width: 768px) {
- .zero-clipboard {
- display: block;
- }
-}
-
// rST styles
img.align-center {
display: block;
@@ -546,13 +539,67 @@ td.field-body {
padding: 0;
}
-// code block lines should not wrap
-pre {
- word-break: normal;
- word-wrap: normal;
-}
-
// lighten js namespace/class name
.descclassname {
opacity: 0.5;
}
+
+// STRIPE-STYLE PAGES
+.stripe {
+ .section {
+ margin-bottom: 2em;
+ }
+
+ // === columning only on medium+ ===
+ @media (min-width: @screen-md-min) {
+ // column 1
+ .section > *,
+ .section > .force-left {
+ width: 49%;
+ float: left;
+ clear: left;
+ }
+ // column 2
+ .section > .force-right,
+ .section > [class*=highlight] {
+ float: none;
+ clear: none;
+ margin-left: 51%;
+ }
+ // fullwidth elements
+ .section > h1, .section > h2, .section > h3, .section > h4, .section > h5,
+ .section > h6 {
+ background-color: fadeout(@body-bg, 30%);
+ }
+ .section > h1, .section > h2, .section > h3, .section > h4, .section > h5,
+ .section > h6, .section > .section {
+ position: relative;
+ width: auto;
+ float: none;
+ clear: both;
+ }
+
+ .bodywrapper {
+ position: relative;
+ // middle separator
+ &:before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 50%;
+ content: "";
+ width: 0;
+ border-left: 1px solid @gray-light;
+ }
+ }
+ }
+ // === show/hide code snippets ===
+ .switchable > .highlight,
+ [class*=only-] {
+ display: none;
+ }
+ // must be final rule of page
+ .only-python, .highlight-python > .highlight {
+ display: block;
+ }
+}
diff --git a/doc/modules.rst b/doc/modules.rst
index 16133d13413..cb5ed8d6ca1 100644
--- a/doc/modules.rst
+++ b/doc/modules.rst
@@ -5,3 +5,4 @@ Module Objects
.. toctree::
:titlesonly:
+ modules/api_integration
diff --git a/doc/modules/api_integration.rst b/doc/modules/api_integration.rst
new file mode 100644
index 00000000000..f6a51f0f9b8
--- /dev/null
+++ b/doc/modules/api_integration.rst
@@ -0,0 +1,816 @@
+:classes: stripe
+
+===========
+Odoo as API
+===========
+
+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.
+
+.. Odoo XML-RPC idiosyncracies:
+ * uses multiple endpoint and a nested call syntax instead of a
+ "hierarchical" server structure (e.g. ``openerp.res.partner.read()``)
+ * uses its own own manual auth system instead of basic auth or sessions
+ (basic is directly supported the Python and Ruby stdlibs as well as
+ ws-xmlrpc, not sure about ripcord)
+ * own auth is inconvenient as (uid, password) have to be explicitly passed
+ into every call. Session would allow db to be stored as well
+ These issues are especially visible in Java, somewhat less so in PHP
+
+Connection and authentication
+=============================
+
+Configuration
+-------------
+
+If you already have an Odoo server installed, you can just use its
+parameters
+
+.. rst-class:: switchable
+
+ .. code-block:: python
+
+ url =
+ db =
+ username = 'admin'
+ password =
+
+ .. code-block:: ruby
+
+ url =
+ db =
+ username = "admin"
+ password =
+
+ .. code-block:: php
+
+ $url = ;
+ $db = ;
+ $username = "admin";
+ $password = ;
+
+ .. code-block:: java
+
+ final String url = ,
+ db = ,
+ username = "admin",
+ password = ;
+
+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 info = (Map)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 `_
+ library, which provides a simple XML-RPC API. Ripcord requires that
+ `XML-RPC support be enabled
+ `_ in your PHP
+ installation.
+
+ Since calls are performed over
+ `HTTPS `_, it also requires that
+ the `OpenSSL extension
+ `_ be enabled.
+
+ .. note::
+ :class: only-java
+
+ These examples use the `Apache XML-RPC library
+ `_
+
+Logging in
+----------
+
+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());
+
+.. code-block:: json
+
+ {
+ "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()));
+
+Calling methods
+===============
+
+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); }}
+ ));
+
+.. code-block:: json
+
+ true
+
+.. todo:: this should be runnable and checked
+
+List records
+------------
+
+Records can be listed and filtered via :meth:`~openerp.models.Model.search`.
+
+:meth:`~openerp.models.Model.search` takes a mandatory
+:ref:`domain ` 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)
+ )));
+
+.. code-block:: json
+
+ [7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
+
+Pagination
+''''''''''
+
+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); }}
+ )));
+
+.. code-block:: json
+
+ [13, 20, 30, 22, 29]
+
+Count records
+-------------
+
+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 ` 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)
+ ));
+
+.. code-block:: json
+
+ 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
+
+Read records
+------------
+
+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();
+
+.. code-block:: json
+
+ 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"));
+ }}
+ )));
+
+.. code-block:: json
+
+ [{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
+
+.. note:: even if the ``id`` field is not requested, it is always returned
+
+Listing record fields
+---------------------
+
+: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> fields =
+ (Map>)models.execute("execute_kw", Arrays.asList(
+ db, uid, password,
+ "res.partner", "fields_get",
+ Collections.emptyList()));
+ // filter keys of field attributes for display
+ final List allowed = Arrays.asList("string", "help", "type");
+ new HashMap>() {{
+ for(Entry> item: fields.entrySet()) {
+ put(item.getKey(), new HashMap() {{
+ for(Entry it: item.getValue().entrySet()) {
+ if (allowed.contains(it.getKey())) {
+ put(it.getKey(), it.getValue());
+ }
+ }
+ }});
+ }
+ }};
+
+.. code-block:: json
+
+ {
+ "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"
+ },
+
+Search and read
+---------------
+
+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);
+ }}
+ )));
+
+.. code-block:: json
+
+ [
+ {
+ "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"
+ }
+ ]
+
+
+Create records
+--------------
+
+.. 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"); }})
+ ));
+
+.. code-block:: json
+
+ 78
+
+Update records
+--------------
+
+.. 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))
+ )));
+
+.. code-block:: json
+
+ [[78, "Newer partner"]]
+
+Delete records
+--------------
+
+.. 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)))
+ )));
+
+.. code-block:: json
+
+ []
+
+.. _PostgreSQL: http://www.postgresql.org
+.. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 69099cf096e..f41d5e33703 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -781,7 +781,7 @@ class ir_model_access(osv.osv):
_logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
msg = '%s %s' % (msg_heads[mode], msg_tail)
raise openerp.exceptions.AccessError(msg % msg_params)
- return r or False
+ return bool(r)
__cache_clearing_methods = []
diff --git a/openerp/models.py b/openerp/models.py
index 7c87d153069..143ac53ced3 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -1654,7 +1654,7 @@ class BaseModel(object):
@api.returns('self')
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
- """ search(args[, offset=0][, limit=None][, order=None][, count=False])
+ """ search(args[, offset=0][, limit=None][, order=None])
Searches for records based on the ``args``
:ref:`search domain `.
@@ -1664,9 +1664,6 @@ class BaseModel(object):
:param int offset: number of results to ignore (default: none)
:param int limit: maximum number of records to return (default: all)
:param str order: sort string
- :param bool count: if ``True``, the call should return the number of
- records matching ``args`` rather than the records
- themselves.
:returns: at most ``limit`` records matching the search criteria
:raise AccessError: * if user tries to bypass access rules for read on the requested object.
diff --git a/openerp/service/common.py b/openerp/service/common.py
index 97f36b7f964..16cbb5d2aa2 100644
--- a/openerp/service/common.py
+++ b/openerp/service/common.py
@@ -18,14 +18,8 @@ RPC_VERSION_1 = {
}
def dispatch(method, params):
- if method in ['login', 'about', 'timezone_get',
- 'version', 'authenticate']:
- pass
- elif method in ['set_loglevel']:
- passwd = params[0]
- params = params[1:]
- security.check_super(passwd)
- else:
+ if method not in ['login', 'about', 'timezone_get',
+ 'version', 'authenticate', 'set_loglevel']:
raise Exception("Method not found: %s" % method)
fn = globals()['exp_' + method]