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]