[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:
parent
467968b79a
commit
ec7736a051
|
@ -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(
|
||||
(Map<String, Map<String, Object>>)models.execute("execute_kw", 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());
|
||||
}
|
||||
}
|
||||
}});
|
||||
}
|
||||
}};
|
||||
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
|
||||
|
|
|
@ -71,6 +71,8 @@ This means the first *group rule* restricts access, but any further
|
|||
|
||||
although access rules do
|
||||
|
||||
.. _reference/security/fields:
|
||||
|
||||
Field Access
|
||||
============
|
||||
|
||||
|
|
|
@ -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
|
||||
'''''''
|
||||
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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``
|
||||
: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.
|
||||
|
||||
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:
|
||||
|
||||
``(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, _)``
|
||||
unlinks and deletes the linked record of id ``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, _)``
|
||||
unlinks the linked record of id ``id`` without deleting it
|
||||
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, _)``
|
||||
links to an existing record of id ``id``
|
||||
adds an existing record of id ``id`` to the set. Can not be
|
||||
used on :class:`~openerp.fields.One2many`.
|
||||
``(5, _, _)``
|
||||
unlinks all records in the relation, equivalent to using
|
||||
the command ``3`` on every linked record
|
||||
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 the existing list of linked records by the provoded
|
||||
ones, equivalent to using ``5`` then ``4`` for each id in
|
||||
``ids``)
|
||||
|
||||
(in command triplets, ``_`` values are ignored and can be
|
||||
anything, generally ``0`` or ``False``)
|
||||
|
||||
Any command can be used on :class:`~openerp.fields.Many2many`,
|
||||
only ``0``, ``1`` and ``2`` can be used on
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue