External API
Introduction¶
PerfectWORK is usually extended internally via modules, but many of its features and all of its data are also available from the outside for external analysis or integration with various tools. Part of the Models API is easily available over XML-RPC and accessible from a variety of languages.
Connection¶
Configuration¶
If you already have an PerfectWORK server installed, you can just use its parameters
Info
To make exploration simpler, you can also ask https://demo.perfectwork.app for a test database.
final XmlRpcClient client = new XmlRpcClient();
final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
start_config.setServerURL(new URL("https://demo.perfectwork.app/start"));
final Map<String, String> info = (Map<String, String>)client.execute(
start_config, "start", emptyList());
final String url = info.get("host"),
db = info.get("database"),
username = info.get("user"),
password = info.get("password");
Note
These examples use the Apache XML-RPC library.
The examples do not include imports as these imports couldn’t be pasted in the code.
Logging in¶
PerfectWORK requires users of the API to be authenticated before they can query most 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.
Result:
{ "server_version": "13.0", "server_version_info": [13, 0, 0, "final", 0], "server_serie": "13.0", "protocol_version": 1, } `
Calling Methods¶
The second endpoint is xmlrpc/2/object. It is used to call methods of PerfectWORK data 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)
Example
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):
final XmlRpcClient models = new XmlRpcClient() {
setConfig(new XmlRpcClientConfigImpl() {
setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
});
};
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "check_access_rights",
asList("read"),
new HashMap() { put("raise_exception", false); }
));
Result:
List Records¶
Records can be listed and filtered via search().
search() takes a mandatory domain filter (possibly empty), and returns the database identifiers of all records matching the filter.
Example
To list customer companies, for instance:
Result:
Pagination¶
By default a search 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.
Example
To list customer companies, for instance:
Result:
Count records¶
Rather than retrieve a possibly gigantic list of records and count them, search_count() can be used to retrieve only the number of records matching the query. It takes the same domain filter as search() and no other parameter.
Example
To list customer companies, for instance:
Result:
Note
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 are accessible via the read() method, which takes a list of ids (as returned by search()), and optionally a list of fields to fetch. By default, it fetches all the fields the current user can read, which tends to be a huge amount.
Example
final List ids = asList((Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(
asList("is_company", "=", true))),
new HashMap() { put("limit", 1); })));
final Map record = (Map)((Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"res.partner", "read",
asList(ids)
)
))[0];
// count the number of fields fetched by default
record.size();
Result:
Conversely, picking only three fields deemed interesting.Result:
Note
Even if the id field is not requested, it is always returned.
List record fields¶
fields_get() can be used to inspect a model’s fields and check which ones seem to be of interest.
Because it returns a large 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).
Example
Result:
{
"ean13": {
"type": "char",
"help": "BarCode",
"string": "EAN13"
},
"property_account_position_id": {
"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_company_ids": {
"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 it is a very common task, PerfectWORK provides a search_read() shortcut which, as its name suggests, is equivalent to a search() followed by a read(), but avoids having to perform two requests and keep ids around.
Its arguments are similar to search()’s, but it can also take a list of fields (like read(), if that list is not provided it will fetch all fields of matched records).
Example
Result:
Conversely, picking only three fields deemed interesting.Result:
[
{
"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¶
Records of a model are created using create(). The method creates a single record and returns its database identifier.
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.
Example
Result:
Conversely, picking only three fields deemed interesting.Result:
Warning
While most value types are what would expect (integer for Integer, string for Char or Text)
- Date, Datetime and Binary fields use string values
- One2many and Many2many use a special command protocol detailed in the documentation to the write method.
Update records¶
Records can be updated using write(). It takes a list of records to update and a mapping of updated fields to values similar to create().
Multiple records can be updated simultaneously, but they will all get the same values for the fields being set. It is not possible to perform “computed” updates (where the value being set depends on an existing value of a record).
Example
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "write",
asList(
asList(id),
new HashMap() { put("name", "Newer Partner"); }
)
));
// get record name after having changed it
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "name_get",
asList(asList(id))
)));
Result:
Delete records¶
Records can be deleted in bulk by providing their ids to unlink().
Example
Result:
Inspection and introspection¶
While we previously used fields_get() to query a model and have been using an arbitrary model from the start, PerfectWORK 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.
-
ir.model
Provides information about PerfectWORK models 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 One2many to ir.model.fields -
view_ids
One2many to the Views defined for the model -
access_ids
One2many relation to the Access Rights 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.
Important
- Custom model names must start with x_.
- The state must be provided and set to manual, otherwise the model will not be loaded.
- It is not possible to add new methods to a custom model, only fields.
Example
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"));
}
));
Result:
{
"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"
}
}
-
ir.model.fields
Provides information about the fields of PerfectWORK data models and allows adding custom fields without using Python code. -
model_id
Many2one to ir.model 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 type 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
field-level access control, a Many2many to res.groups -
selection, size, on_delete, relation, relation_field, domain
type-specific properties and customizations, see the fields documentation for details
Important
- Like custom models, only new fields created with state="manual" are activated as actual fields on the model.
- Computed fields can not be added via ir.model.fields, some field meta-information (defaults, onchange) can not be set either.
Example
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]])
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]])
$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)));
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))
));
Result: