[ADD] Stripe-style APIDoc: content
This commit is contained in:
parent
eb8aad865e
commit
4d2b24adcb
|
@ -25,3 +25,8 @@ class Exercise(admonitions.BaseAdmonition):
|
|||
|
||||
from sphinx.locale import admonitionlabels, l_
|
||||
admonitionlabels['exercise'] = l_('Exercise')
|
||||
|
||||
# monkeypatch PHP lexer to not require <?php
|
||||
from sphinx.highlighting import lexers
|
||||
from pygments.lexers.web import PhpLexer
|
||||
lexers['php'] = PhpLexer(startinline=True)
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{%- endblock -%}
|
||||
|
||||
{%- block content -%}
|
||||
<div class="document-super">
|
||||
<div class="document-super {% if meta is defined %}{{ meta.classes }}{% endif %}">
|
||||
{{ super() }}
|
||||
</div>
|
||||
{%- endblock -%}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@ Module Objects
|
|||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
modules/api_integration
|
||||
|
|
|
@ -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 = <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/>`_
|
||||
|
||||
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 <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)
|
||||
)));
|
||||
|
||||
.. 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 <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)
|
||||
));
|
||||
|
||||
.. 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<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());
|
||||
}
|
||||
}
|
||||
}});
|
||||
}
|
||||
}};
|
||||
|
||||
.. 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
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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 <reference/orm/domains>`.
|
||||
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue