[ADD] ws doc: introspection, reports and workflows

* use static imports in java examples to make them terser
* inline ``domain`` in java and php example to make examples more
  self-contained
* try to extend/improve Model.write's docstring
* add convenience kwarg to fields_get, mostly for user-driven
  introspection

Closes #3689
This commit is contained in:
Xavier Morel 2014-11-24 08:52:38 +01:00
parent 467968b79a
commit ec7736a051
5 changed files with 731 additions and 133 deletions

View File

@ -61,11 +61,10 @@ Connection and authentication
.. 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());
start_config, "start", emptyList());
final String url = info.get("host"),
db = info.get("database"),
@ -77,7 +76,7 @@ Connection and authentication
int uid = (int)client.execute(
common_config, "authenticate", Arrays.asList(
db, username, password, Collections.emptyMap()));
db, username, password, emptyMap()));
final XmlRpcClient models = new XmlRpcClient() {{
setConfig(new XmlRpcClientConfigImpl() {{
@ -154,7 +153,7 @@ database:
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());
start_config, "start", emptyList());
final String url = info.get("host"),
db = info.get("database"),
@ -183,6 +182,9 @@ database:
These examples use the `Apache XML-RPC library
<https://ws.apache.org/xmlrpc/>`_
The examples do not include imports as these imports couldn't be
pasted in the code.
Logging in
----------
@ -217,8 +219,9 @@ the login.
.. 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());
common_config.setServerURL(
new URL(String.format("%s/xmlrpc/2/common", url)));
client.execute(common_config, "version", emptyList());
.. code-block:: json
@ -246,8 +249,8 @@ the login.
.. code-block:: java
int uid = (int)client.execute(
common_config, "authenticate", Arrays.asList(
db, username, password, Collections.emptyMap()));
common_config, "authenticate", asList(
db, username, password, emptyMap()));
Calling methods
===============
@ -302,10 +305,10 @@ rather than true/error):
setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
}});
}};
models.execute("execute_kw", Arrays.asList(
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "check_access_rights",
Arrays.asList("read"),
asList("read"),
new HashMap() {{ put("raise_exception", false); }}
));
@ -341,20 +344,19 @@ companies for instance:
.. code-block:: php
$domain = array(array('is_company', '=', true),
array('customer', '=', true));
$models->execute_kw($db, $uid, $password,
'res.partner', 'search', array($domain));
'res.partner', 'search', array(
array(array('is_company', '=', true),
array('customer', '=', true))));
.. 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(
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
Arrays.asList(domain)
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true)))
)));
.. code-block:: json
@ -388,15 +390,18 @@ available to only retrieve a subset of all matched records.
$models->execute_kw($db, $uid, $password,
'res.partner', 'search',
array($domain),
array(array(array('is_company', '=', true),
array('customer', '=', true))),
array('offset'=>10, 'limit'=>5));
.. code-block:: java
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
Arrays.asList(domain),
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true))),
new HashMap() {{ put("offset", 10); put("limit", 5); }}
)));
@ -431,14 +436,17 @@ only the number of records matching the query. It takes the same
$models->execute_kw($db, $uid, $password,
'res.partner', 'search_count',
array($domain));
array(array(array('is_company', '=', true),
array('customer', '=', true))));
.. code-block:: java
(Integer)models.execute("execute_kw", Arrays.asList(
(Integer)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_count",
Arrays.asList(domain)
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true)))
));
.. code-block:: json
@ -488,7 +496,8 @@ which tends to be a huge amount.
$ids = $models->execute_kw($db, $uid, $password,
'res.partner', 'search',
array($domain),
array(array(array('is_company', '=', true),
array('customer', '=', true))),
array('limit'=>1));
$records = $models->execute_kw($db, $uid, $password,
'res.partner', 'read', array($ids));
@ -497,17 +506,19 @@ which tends to be a huge amount.
.. code-block:: java
final List ids = Arrays.asList((Object[])models.execute(
"execute_kw", Arrays.asList(
final List ids = asList((Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"res.partner", "search",
Arrays.asList(domain),
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true))),
new HashMap() {{ put("limit", 1); }})));
final Map record = (Map)((Object[])models.execute(
"execute_kw", Arrays.asList(
"execute_kw", asList(
db, uid, password,
"res.partner", "read",
Arrays.asList(ids)
asList(ids)
)
))[0];
// count the number of fields fetched by default
@ -542,12 +553,12 @@ Conversedly, picking only three fields deemed interesting.
.. code-block:: java
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "read",
Arrays.asList(ids),
asList(ids),
new HashMap() {{
put("fields", Arrays.asList("name", "country_id", "comment"));
put("fields", asList("name", "country_id", "comment"));
}}
)));
@ -574,53 +585,32 @@ updating a record):
.. 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()}
models.execute_kw(
db, uid, password, 'res.partner', 'fields_get',
[], {'attributes': ['string', 'help', 'type']})
.. 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}
}
models.execute_kw(
db, uid, password, 'res.partner', 'fields_get',
[], {attributes: %w(string help type)})
.. 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);
}
$models->execute_kw($db, $uid, $password,
'res.partner', 'fields_get',
array(), array('attributes' => array('string', 'help', 'type')));
.. 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());
}
}
}});
}
}};
(Map<String, Map<String, Object>>)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "fields_get",
emptyList(),
new HashMap() {{
put("attributes", asList("string", "help", "type"));
}}
));
.. code-block:: json
@ -668,10 +658,11 @@ 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):
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
@ -693,17 +684,20 @@ provided it'll fetch all fields of matched records):
$models->execute_kw($db, $uid, $password,
'res.partner', 'search_read',
array($domain),
array(array(array('is_company', '=', true),
array('customer', '=', true))),
array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
.. code-block:: java
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_read",
Arrays.asList(domain),
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true))),
new HashMap() {{
put("fields", Arrays.asList("name", "country_id", "comment"));
put("fields", asList("name", "country_id", "comment"));
put("limit", 5);
}}
)));
@ -747,6 +741,13 @@ provided it'll fetch all fields of matched records):
Create records
--------------
Records of a model are created using :meth:`~openerp.models.Model.create`. The
method will create a single record and return its database identifier.
:meth:`~openerp.models.Model.create` takes a mapping of fields to values, used
to initialize the record. For any field which has a default value and is not
set through the mapping argument, the default value will be used.
.. rst-class:: switchable
.. code-block:: python
@ -769,19 +770,40 @@ Create records
.. code-block:: java
final Integer id = (Integer)models.execute("execute_kw", Arrays.asList(
final Integer id = (Integer)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "create",
Arrays.asList(new HashMap() {{ put("name", "New Partner"); }})
asList(new HashMap() {{ put("name", "New Partner"); }})
));
.. code-block:: json
78
.. warning::
while most value types are what would be expected (integer for
:class:`~openerp.fields.Integer`, string for :class:`~openerp.fields.Char`
or :class:`~openerp.fields.Text`),
* :class:`~openerp.fields.Date`, :class:`~openerp.fields.Datetime` and
:class:`~openerp.fields.Binary` fields use string values
* :class:`~openerp.fields.One2many` and :class:`~openerp.fields.Many2many`
use a special command protocol detailed in :meth:`the documentation to
the write method <openerp.models.Model.write>`.
Update records
--------------
Reccords can be updated using :meth:`~openerp.models.Model.write`, it takes
a list of records to update and a mapping of updated fields to values similar
to :meth:`~openerp.models.Model.create`.
Multiple records can be updated simultanously, but they will all get the same
values for the fields being set. It is not currently possible to perform
"computed" updates (where the value being set depends on an existing value of
a record).
.. rst-class:: switchable
.. code-block:: python
@ -810,19 +832,19 @@ Update records
.. code-block:: java
models.execute("execute_kw", Arrays.asList(
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "write",
Arrays.asList(
Arrays.asList(id),
asList(
asList(id),
new HashMap() {{ put("name", "Newer Partner"); }}
)
));
// get record name after having changed it
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "name_get",
Arrays.asList(Arrays.asList(id))
asList(asList(id))
)));
.. code-block:: json
@ -832,6 +854,9 @@ Update records
Delete records
--------------
Records can be deleted in bulk by providing the ids of all records to remove
to :meth:`~openerp.models.Model.unlink`.
.. rst-class:: switchable
.. code-block:: python
@ -860,20 +885,561 @@ Delete records
.. code-block:: java
models.execute("execute_kw", Arrays.asList(
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "unlink",
Arrays.asList(Arrays.asList(id))));
asList(asList(id))));
// check if the deleted record is still in the database
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
Arrays.asList(Arrays.asList(Arrays.asList("id", "=", 78)))
asList(asList(asList("id", "=", 78)))
)));
.. code-block:: json
[]
Inspection and introspection
----------------------------
.. todo:: ``get_external_id`` is kinda crap and may not return an id: it just
gets a random existing xid but won't generate one if there is no
xid currently associated with the record. And operating with xids
isn't exactly fun in RPC.
While we previously used :meth:`~openerp.models.Model.fields_get` to query a
model's and have been using an arbitrary model from the start, Odoo stores
most model metadata inside a few meta-models which allow both querying the
system and altering models and fields (with some limitations) on the fly over
XML-RPC.
.. _reference/webservice/inspection/models:
``ir.model``
''''''''''''
Provides informations about Odoo models themselves via its various fields
``name``
a human-readable description of the model
``model``
the name of each model in the system
``state``
whether the model was generated in Python code (``base``) or by creating
an ``ir.model`` record (``manual``)
``field_id``
list of the model's fields through a :class:`~openerp.fields.One2many` to
:ref:`reference/webservice/inspection/fields`
``view_ids``
:class:`~openerp.fields.One2many` to the :ref:`reference/views` defined
for the model
``access_ids``
:class:`~openerp.fields.One2many` relation to the
:ref:`reference/security/acl` set on the model
``ir.model`` can be used to
* query the system for installed models (as a precondition to operations
on the model or to explore the system's content)
* get information about a specific model (generally by listing the fields
associated with it)
* create new models dynamically over RPC
.. warning::
* "custom" model names must start with ``x_``
* the ``state`` must be provided and ``manual``, otherwise the model will
not be loaded
* it is not possible to add new *methods* to a custom model, only fields
.. rst-class:: force-right
a custom model will initially contain only the "built-in" fields available
on all models:
.. rst-class:: switchable
.. code-block:: python
models.execute_kw(db, uid, password, 'ir.model', 'create', [{
'name': "Custom Model",
'model': "x_custom_model",
'state': 'manual',
}])
models.execute_kw(
db, uid, password, 'x_custom_model', 'fields_get',
[], {'attributes': ['string', 'help', 'type']})
.. code-block:: php
$models->execute_kw(
$db, $uid, $password,
'ir.model', 'create', array(array(
'name' => "Custom Model",
'model' => 'x_custom_model',
'state' => 'manual'
))
);
$models->execute_kw(
$db, $uid, $password,
'x_custom_model', 'fields_get',
array(),
array('attributes' => array('string', 'help', 'type'))
);
.. code-block:: ruby
models.execute_kw(
db, uid, password,
'ir.model', 'create', [{
name: "Custom Model",
model: 'x_custom_model',
state: 'manual'
}])
fields = models.execute_kw(
db, uid, password, 'x_custom_model', 'fields_get',
[], {attributes: %w(string help type)})
.. code-block:: java
models.execute(
"execute_kw", asList(
db, uid, password,
"ir.model", "create",
asList(new HashMap<String, Object>() {{
put("name", "Custom Model");
put("model", "x_custom_model");
put("state", "manual");
}})
));
final Object fields = models.execute(
"execute_kw", asList(
db, uid, password,
"x_custom_model", "fields_get",
emptyList(),
new HashMap<String, Object> () {{
put("attributes", asList(
"string",
"help",
"type"));
}}
));
.. code-block:: json
{
"create_uid": {
"type": "many2one",
"string": "Created by"
},
"create_date": {
"type": "datetime",
"string": "Created on"
},
"__last_update": {
"type": "datetime",
"string": "Last Modified on"
},
"write_uid": {
"type": "many2one",
"string": "Last Updated by"
},
"write_date": {
"type": "datetime",
"string": "Last Updated on"
},
"display_name": {
"type": "char",
"string": "Display Name"
},
"id": {
"type": "integer",
"string": "Id"
}
}
.. _reference/webservice/inspection/fields:
``ir.model.fields``
'''''''''''''''''''
Provides informations about the fields of Odoo models and allows adding
custom fields without using Python code
``model_id``
:class:`~openerp.fields.Many2one` to
:ref:`reference/webservice/inspection/models` to which the field belongs
``name``
the field's technical name (used in ``read`` or ``write``)
``field_description``
the field's user-readable label (e.g. ``string`` in ``fields_get``)
``ttype``
the :ref:`type <reference/orm/fields>` of field to create
``state``
whether the field was created via Python code (``base``) or via
``ir.model.fields`` (``manual``)
``required``, ``readonly``, ``translate``
enables the corresponding flag on the field
``groups``
:ref:`field-level access control <reference/security/fields>`, a
:class:`~openerp.fields.Many2many` to ``res.groups``
``selection``, ``size``, ``on_delete``, ``relation``, ``relation_field``, ``domain``
type-specific properties and customizations, see :ref:`the fields
documentation <reference/orm/fields>` for details
Like custom models, only new fields created with ``state="manual"`` are
activated as actual fields on the model.
.. warning:: computed fields can not be added via ``ir.model.fields``, some
field meta-information (defaults, onchange) can not be set either
.. todo:: maybe new-API fields could store constant ``default`` in a new
column, maybe JSON-encoded?
.. rst-class:: switchable
.. code-block:: python
id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{
'name': "Custom Model",
'model': "x_custom",
'state': 'manual',
}])
models.execute_kw(
db, uid, password,
'ir.model.fields', 'create', [{
'model_id': id,
'name': 'x_name',
'ttype': 'char',
'state': 'manual',
'required': True,
}])
record_id = models.execute_kw(
db, uid, password,
'x_custom', 'create', [{
'x_name': "test record",
}])
models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]])
.. code-block:: php
$id = $models->execute_kw(
$db, $uid, $password,
'ir.model', 'create', array(array(
'name' => "Custom Model",
'model' => 'x_custom',
'state' => 'manual'
))
);
$models->execute_kw(
$db, $uid, $password,
'ir.model.fields', 'create', array(array(
'model_id' => $id,
'name' => 'x_name',
'ttype' => 'char',
'state' => 'manual',
'required' => true
))
);
$record_id = $models->execute_kw(
$db, $uid, $password,
'x_custom', 'create', array(array(
'x_name' => "test record"
))
);
$models->execute_kw(
$db, $uid, $password,
'x_custom', 'read',
array(array($record_id)));
.. code-block:: ruby
id = models.execute_kw(
db, uid, password,
'ir.model', 'create', [{
name: "Custom Model",
model: "x_custom",
state: 'manual'
}])
models.execute_kw(
db, uid, password,
'ir.model.fields', 'create', [{
model_id: id,
name: "x_name",
ttype: "char",
state: "manual",
required: true
}])
record_id = models.execute_kw(
db, uid, password,
'x_custom', 'create', [{
x_name: "test record"
}])
models.execute_kw(
db, uid, password,
'x_custom', 'read', [[record_id]])
.. code-block:: java
final Integer id = (Integer)models.execute(
"execute_kw", asList(
db, uid, password,
"ir.model", "create",
asList(new HashMap<String, Object>() {{
put("name", "Custom Model");
put("model", "x_custom");
put("state", "manual");
}})
));
models.execute(
"execute_kw", asList(
db, uid, password,
"ir.model.fields", "create",
asList(new HashMap<String, Object>() {{
put("model_id", id);
put("name", "x_name");
put("ttype", "char");
put("state", "manual");
put("required", true);
}})
));
final Integer record_id = (Integer)models.execute(
"execute_kw", asList(
db, uid, password,
"x_custom", "create",
asList(new HashMap<String, Object>() {{
put("x_name", "test record");
}})
));
client.execute(
"execute_kw", asList(
db, uid, password,
"x_custom", "read",
asList(asList(record_id))
));
.. code-block:: json
[
{
"create_uid": [1, "Administrator"],
"x_name": "test record",
"__last_update": "2014-11-12 16:32:13",
"write_uid": [1, "Administrator"],
"write_date": "2014-11-12 16:32:13",
"create_date": "2014-11-12 16:32:13",
"id": 1,
"display_name": "test record"
}
]
Workflow manipulations
----------------------
:ref:`reference/workflows` can be moved along by sending them *signals*.
Instead of using the top-level ``execute_kw``, signals are sent using
``exec_workflow``.
Signals are sent to a specific record, and possibly trigger a transition on
the workflow instance associated with the record.
.. warning:: requires that the ``account`` module be installed
:class: force-right
.. rst-class:: switchable
.. code-block:: python
client = models.execute_kw(
db, uid, password,
'res.partner', 'search_read',
[[('customer', '=', True)]],
{'limit': 1, 'fields': [
'property_account_receivable',
'property_payment_term',
'property_account_position']
})[0]
invoice_id = models.execute_kw(
db, uid, password,
'account.invoice', 'create', [{
'partner_id': client['id'],
'account_id': client['property_account_receivable'][0],
'invoice_line': [(0, False, {'name': "AAA"})]
}])
models.exec_workflow(
db, uid, password, 'account.invoice', 'invoice_open', invoice_id)
.. code-block:: php
$client = $models->execute_kw(
$db, $uid, $password,
'res.partner', 'search_read',
array(array(array('customer', '=', true))),
array(
'limit' => 1,
'fields' => array(
'property_account_receivable',
'property_payment_term',
'property_account_position'
)))[0];
$invoice_id = $models->execute_kw(
$db, $uid, $password,
'account.invoice', 'create', array(array(
'partner_id' => $client['id'],
'account_id' => $client['property_account_receivable'][0],
'invoice_line' => array(array(0, false, array('name' => "AAA")))
)));
$models->exec_workflow(
$db, $uid, $password,
'account.invoice', 'invoice_open',
$invoice_id);
.. code-block:: ruby
client = models.execute_kw(
db, uid, password,
'res.partner', 'search_read',
[[['customer', '=', true]]],
{limit: 1, fields: %w(property_account_receivable property_payment_term property_account_position)}
)[0]
invoice_id = models.execute_kw(
db, uid, password,
'account.invoice', 'create', [{
partner_id: client['id'],
account_id: client['property_account_receivable'][0],
invoice_line: [[0, false, {name: "AAA"}]]
}])
models.exec_workflow(
db, uid, password,
'account.invoice', 'invoice_open', invoice_id)
.. code-block:: java
final Map<String, Object> c = (Map<String, Object>)
((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_read",
asList(
asList(
asList("customer", "=", true))),
new HashMap<String, Object>() {{
put("limit", 1);
put("fields", asList(
"property_account_receivable",
"property_payment_term",
"property_account_position"
));
}}
)))[0];
final Integer invoice_id = (Integer)models.execute(
"execute_kw", asList(
db, uid, password,
"account.invoice", "create",
asList(new HashMap<String, Object>() {{
put("partner_id", c.get("id"));
put("account_id", ((Object[])c.get("property_account_receivable"))[0]);
put("invoice_line", asList(
asList(0, false, new HashMap<String, Object>() {{
put("name", "AAA");
}})
));
}})
));
models.execute(
"exec_workflow", asList(
db, uid, password,
"account.invoice", "invoice_open", invoice_id));
Report printing
---------------
Available reports can be listed by searching the ``ir.actions.report.xml``
model, fields of interest being
``model``
the model on which the report applies, can be used to look for available
reports on a specific model
``name``
human-readable report name
``report_name``
the technical name of the report, used to print it
Reports can be printed over RPC with the following information:
* the name of the report (``report_name``)
* the ids of the records to include in the report
.. rst-class:: switchable
.. code-block:: python
invoice_ids = models.execute_kw(
db, uid, password, 'account.invoice', 'search',
[[('type', '=', 'out_invoice'), ('state', '=', 'open')]])
report = xmlrpclib.ServerProxy('{}/xmlrpc/2/report'.format(url))
result = report.render_report(
db, uid, password, 'account.report_invoice', invoice_ids)
report_data = result['result'].decode('base64')
.. code-block:: php
$invoice_ids = $models->execute_kw(
$db, $uid, $password,
'account.invoice', 'search',
array(array(array('type', '=', 'out_invoice'),
array('state', '=', 'open'))));
$report = ripcord::client("$url/xmlrpc/2/report");
$result = $report->render_report(
$db, $uid, $password,
'account.report_invoice', $invoice_ids);
$report_data = base64_decode($result['result']);
.. code-block:: ruby
require 'base64'
invoice_ids = models.execute_kw(
db, uid, password,
'account.invoice', 'search',
[[['type', '=', 'out_invoice'], ['state', '=', 'open']]])
report = XMLRPC::Client.new2("#{url}/xmlrpc/2/report").proxy
result = report.render_report(
db, uid, password,
'account.report_invoice', invoice_ids)
report_data = Base64.decode64(result['result'])
.. code-block:: java
final Object[] invoice_ids = (Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"account.invoice", "search",
asList(asList(
asList("type", "=", "out_invoice"),
asList("state", "=", "open")))
));
final XmlRpcClientConfigImpl report_config = new XmlRpcClientConfigImpl();
report_config.setServerURL(
new URL(String.format("%s/xmlrpc/2/report", url)));
final Map<String, Object> result = (Map<String, Object>)client.execute(
report_config, "render_report", asList(
db, uid, password,
"account.report_invoice",
invoice_ids));
final byte[] report_data = DatatypeConverter.parseBase64Binary(
(String)result.get("result"));
.. note::
:class: force-right
the report is sent as PDF binary data encoded in base64_, it must be
decoded and may need to be saved to disk before use
.. _PostgreSQL: http://www.postgresql.org
.. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC
.. _base64: http://en.wikipedia.org/wiki/Base64

View File

@ -71,6 +71,8 @@ This means the first *group rule* restricts access, but any further
although access rules do
.. _reference/security/fields:
Field Access
============

View File

@ -284,6 +284,8 @@ defined (in addition to the Odoo ``safe_eval`` environment):
- all the model column names, and
- all the browse record's attributes.
.. _reference/workflows/signals:
Signals
'''''''

View File

@ -1057,8 +1057,8 @@ class Char(_String):
return ustr(value)[:self.size]
class Text(_String):
""" Text field. Very similar to :class:`~.Char` but used for longer
contents and displayed as a multiline text box
""" Very similar to :class:`~.Char` but used for longer contents, does not
have a size and usually displayed as a multiline text box.
:param translate: whether the value of this field can be translated
"""

View File

@ -2994,8 +2994,8 @@ class BaseModel(object):
elif 'x_name' in cls._fields:
cls._rec_name = 'x_name'
def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
""" fields_get([fields])
def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None):
""" fields_get([fields][, attributes])
Return the definition of each field.
@ -3003,16 +3003,14 @@ class BaseModel(object):
dictionaries. The _inherits'd fields are included. The string, help,
and selection (if present) attributes are translated.
:param cr: database cursor
:param user: current user id
:param allfields: list of fields
:param context: context arguments, like lang, time zone
:return: dictionary of field dictionaries, each one describing a field of the business object
:raise AccessError: * if user has no create/write rights on the requested object
:param allfields: list of fields to document, all if empty or not provided
:param attributes: list of description attributes to return for each field, all if empty or not provided
"""
recs = self.browse(cr, user, [], context)
has_access = functools.partial(recs.check_access_rights, raise_exception=False)
readonly = not (has_access('write') or has_access('create'))
res = {}
for fname, field in self._fields.iteritems():
if allfields and fname not in allfields:
@ -3021,14 +3019,15 @@ class BaseModel(object):
continue
if field.groups and not recs.user_has_groups(field.groups):
continue
res[fname] = field.get_description(recs.env)
# if user cannot create or modify records, make all fields readonly
has_access = functools.partial(recs.check_access_rights, raise_exception=False)
if not (has_access('write') or has_access('create')):
for description in res.itervalues():
description = field.get_description(recs.env)
if readonly:
description['readonly'] = True
description['states'] = {}
if attributes:
description = {k: v for k, v in description.iteritems()
if k in attributes}
res[fname] = description
return res
@ -3604,38 +3603,67 @@ class BaseModel(object):
:raise ValidateError: if user tries to enter invalid value for a field that is not in selection
:raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
.. _openerp/models/relationals/format:
* For numeric fields (:class:`~openerp.fields.Integer`,
:class:`~openerp.fields.Float`) the value should be of the
corresponding type
* For :class:`~openerp.fields.Boolean`, the value should be a
:class:`python:bool`
* For :class:`~openerp.fields.Selection`, the value should match the
selection values (generally :class:`python:str`, sometimes
:class:`python:int`)
* For :class:`~openerp.fields.Many2one`, the value should be the
database identifier of the record to set
* Other non-relational fields use a string for value
.. note:: Relational fields use a special "commands" format to manipulate their values
.. danger::
This format is a list of command triplets executed sequentially,
possible command triplets are:
for historical and compatibility reasons,
:class:`~openerp.fields.Date` and
:class:`~openerp.fields.Datetime` fields use strings as values
(written and read) rather than :class:`~python:datetime.date` or
:class:`~python:datetime.datetime`. These date strings are
UTC-only and formatted according to
:const:`openerp.tools.misc.DEFAULT_SERVER_DATE_FORMAT` and
:const:`openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT`
* .. _openerp/models/relationals/format:
``(0, _, values: dict)``
links to a new record created from the provided values
``(1, id, values: dict)``
updates the already-linked record of id ``id`` with the
provided ``values``
``(2, id, _)``
unlinks and deletes the linked record of id ``id``
``(3, id, _)``
unlinks the linked record of id ``id`` without deleting it
``(4, id, _)``
links to an existing record of id ``id``
``(5, _, _)``
unlinks all records in the relation, equivalent to using
the command ``3`` on every linked record
``(6, _, ids)``
replaces the existing list of linked records by the provoded
ones, equivalent to using ``5`` then ``4`` for each id in
``ids``)
:class:`~openerp.fields.One2many` and
:class:`~openerp.fields.Many2many` use a special "commands" format to
manipulate the set of records stored in/associated with the field.
(in command triplets, ``_`` values are ignored and can be
anything, generally ``0`` or ``False``)
This format is a list of triplets executed sequentially, where each
triplet is a command to execute on the set of records. Not all
commands apply in all situations. Possible commands are:
Any command can be used on :class:`~openerp.fields.Many2many`,
only ``0``, ``1`` and ``2`` can be used on
:class:`~openerp.fields.One2many`.
``(0, _, values)``
adds a new record created from the provided ``value`` dict.
``(1, id, values)``
updates an existing record of id ``id`` with the values in
``values``. Can not be used in :meth:`~.create`.
``(2, id, _)``
removes the record of id ``id`` from the set, then deletes it
(from the database). Can not be used in :meth:`~.create`.
``(3, id, _)``
removes the record of id ``id`` from the set, but does not
delete it. Can not be used on
:class:`~openerp.fields.One2many`. Can not be used in
:meth:`~.create`.
``(4, id, _)``
adds an existing record of id ``id`` to the set. Can not be
used on :class:`~openerp.fields.One2many`.
``(5, _, _)``
removes all records from the set, equivalent to using the
command ``3`` on every record explicitly. Can not be used on
:class:`~openerp.fields.One2many`. Can not be used in
:meth:`~.create`.
``(6, _, ids)``
replaces all existing records in the set by the ``ids`` list,
equivalent to using the command ``5`` followed by a command
``4`` for each ``id`` in ``ids``. Can not be used on
:class:`~openerp.fields.One2many`.
.. note:: Values marked as ``_`` in the list above are ignored and
can be anything, generally ``0`` or ``False``.
"""
if not self:
return True