外部 RPC API¶
危险
19.0 版后已移除.
位于 /xmlrpc、/xmlrpc/2 和 /jsonrpc 端点的 XML-RPC 与 JSON-RPC API 均已弃用。这两个 RPC API 都提供相同的三个服务:common、db(数据库)和 object。db 服务已在 Odoo 20(2026 年秋季)和 Online 19.1(2025 年冬季)中移除。common 和 object 服务计划在 Odoo 22(2028 年秋季)和 Online 21.1(2027 年冬季)中移除。外部 JSON-2 API 可作为替代方案。
其他 @route(type='jsonrpc') 控制器(在 Odoo 18 之前称为 type='json')不受此弃用通知的影响。
Odoo 通常通过模块在内部进行扩展,但它的许多功能以及全部数据也可从外部获取,用于外部分析或与各种工具集成。部分 ORM 模型 API 可通过 XML-RPC 轻松使用,并可从多种语言访问。
从 PHP8 开始,XML-RPC 扩展可能默认不可用。安装步骤请参阅 手册。
通过外部 API 访问数据仅在 Custom Odoo 定价计划中可用。外部 API 访问在 One App Free 或 Standard 计划中不可用。更多信息请访问 Odoo 定价页面 或联系您的客户成功经理。
连接¶
配置¶
如果已经安装了 Odoo 服务器,直接使用其参数即可。
重要
对于 Odoo Online 实例(<domain>.odoo.com),创建用户时不会设置 本地 密码(因为你是通过 Odoo Online 的身份验证系统登录的,而不是由实例本身验证)。要在 Odoo Online 实例上使用 XML-RPC,需要为你想使用的用户账户设置密码:
使用管理员账户登录你的实例。
转到 。
点击要用于 XML-RPC 访问的那个用户。
点击 操作 并选择 修改密码。
设置 新密码 的值,然后点击 修改密码。
服务器 URL 是实例的域名(例如 https://mycompany.odoo.com),数据库名称 是实例的名称(例如 mycompany)。用户名 是已配置用户的登录名,即 修改密码 界面中显示的内容。
url = <insert server URL>
db = <insert database name>
username = 'admin'
password = <insert password for your admin user (default: admin)>
url = <insert server URL>
db = <insert database name>
username = "admin"
password = <insert password for your admin user (default: admin)>
$url = <insert server URL>;
$db = <insert database name>;
$username = "admin";
$password = <insert password for your admin user (default: admin)>;
final String url = <insert server URL>,
db = <insert database name>,
username = "admin",
password = <insert password for your admin user (default: admin)>;
var (
url = <insert server URL>
db = <insert database name>
username = "admin"
password = <insert password for your admin user (default: admin)>
)
API 密钥¶
14.0 新版功能.
Odoo 支持 API 密钥,并且(取决于模块或设置)可能 要求 使用这些密钥来执行 Web 服务操作。
在脚本中使用 API 密钥的方法很简单:用密钥替换你的 密码 即可。登录名保持不变。你应当像保管密码一样谨慎地保管 API 密钥,因为它们本质上提供对用户账户的相同访问权限(尽管它们不能用于通过界面登录)。
要为账户添加密钥,只需转到 首选项 或 我的个人资料:
然后打开 账户安全 选项卡,并点击 新建 API 密钥:
输入密钥的描述,该描述应尽可能清晰、完整:这是你日后识别密钥、并判断是删除还是保留它们的唯一途径。
点击 生成密钥,然后复制提供的密钥。请妥善保存此密钥:它等同于你的密码,正如密码一样,系统之后无法再检索或显示该密钥。如果丢失了这个密钥,你将不得不创建一个新密钥(并且很可能需要删除丢失的那个)。
一旦在账户上配置了密钥,它们就会显示在 新建 API 密钥 按钮上方,并且你可以将其删除:
已删除的 API 密钥无法恢复或重新设置。你需要生成一个新密钥,并更新所有使用过旧密钥的位置。
测试数据库¶
为简化探索过程,你也可以向 https://demo.odoo.com 申请一个测试数据库:
import xmlrpc.client
info = xmlrpc.client.ServerProxy('https://demo.odoo.com/start').start()
url, db, username, password = info['host'], info['database'], info['user'], info['password']
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']
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']);
注解
这些示例使用 Ripcord 库,它提供了一个简单的 XML-RPC API。Ripcord 要求在你的 PHP 安装中 启用 XML-RPC 支持。
由于调用通过 HTTPS 执行,因此还要求启用 OpenSSL 扩展。
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", emptyList());
final String url = info.get("host"),
db = info.get("database"),
username = info.get("user"),
password = info.get("password");
client, err := xmlrpc.NewClient("https://demo.odoo.com/start", nil)
if err != nil {
log.Fatal(err)
}
info := map[string]string{}
client.Call("start", nil, &info)
url = info["host"].(string)
db = info["database"].(string)
username = info["user"].(string)
password = info["password"].(string)
正在登录¶
Odoo 要求 API 用户先通过身份验证,然后才能查询大部分数据。
xmlrpc/2/common 端点提供无需身份验证的元调用,例如身份验证本身或获取版本信息。要在尝试身份验证之前确认连接信息是否正确,最简单的调用是查询服务器版本。身份验证本身通过 authenticate 函数完成,并返回一个用户标识符(uid),在已通过身份验证的调用中用它代替登录名。
common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))
common.version()
common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
common.call('version')
$common = ripcord::client("$url/xmlrpc/2/common");
$common->version();
final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
client.execute(common_config, "version", emptyList());
client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil)
if err != nil {
log.Fatal(err)
}
common := map[string]any{}
if err := client.Call("version", nil, &common); err != nil {
log.Fatal(err)
}
结果:
{
"server_version": "13.0",
"server_version_info": [13, 0, 0, "final", 0],
"server_serie": "13.0",
"protocol_version": 1,
}
uid = common.authenticate(db, username, password, {})
uid = common.call('authenticate', db, username, password, {})
$uid = $common->authenticate($db, $username, $password, array());
int uid = (int)client.execute(common_config, "authenticate", asList(db, username, password, emptyMap()));
var uid int64
if err := client.Call("authenticate", []any{
db, username, password,
map[string]any{},
}, &uid); err != nil {
log.Fatal(err)
}
调用方法¶
第二个端点是 xmlrpc/2/object。它用于通过 execute_kw RPC 函数调用 Odoo 模型的方法。
每次调用 execute_kw 都需要以下参数:
要使用的数据库,字符串
用户 ID(通过
authenticate获取),整数用户的密码,字符串
模型名称,字符串
方法名称,字符串
按位置传递的参数数组/列表
按关键字传递的参数映射/字典(可选)
示例
例如,要在 res.partner 模型中搜索记录,我们可以调用 name_search,将 name 按位置传递、limit 按关键字传递(以便最多获得 10 条结果):
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url))
models.execute_kw(db, uid, password, 'res.partner', 'name_search', ['foo'], {'limit': 10})
models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
models.execute_kw(db, uid, password, 'res.partner', 'name_search', ['foo'], {limit: 10})
$models = ripcord::client("$url/xmlrpc/2/object");
$models->execute_kw($db, $uid, $password, 'res.partner', 'name_search', array('foo'), array('limit' => 10));
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", "name_search",
asList("foo"),
new HashMap() {{ put("limit", 10); }}
));
models, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil)
if err != nil {
log.Fatal(err)
}
var result bool
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "name_search",
[]string{"foo"},
map[string]bool{"limit": 10},
}, &result); err != nil {
log.Fatal(err)
}
结果:
true
列出记录¶
可以通过 search() 列出并筛选记录。
search() 接受一个必需的 域 筛选条件(可以为空),并返回所有匹配该筛选条件的记录的数据库标识符。
示例
例如,要列出客户公司:
models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]])
models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true]]])
$models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('is_company', '=', true))));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(
asList("is_company", "=", true)))
)));
var records []int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "search",
[]any{[]any{
[]any{"is_company", "=", true},
}},
}, &records); err != nil {
log.Fatal(err)
}
结果:
[7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
分页¶
默认情况下,搜索会返回所有匹配条件的记录 ID,其数量可能非常庞大。可以使用 offset 和 limit 参数来仅检索所有匹配记录中的一部分。
示例
models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]], {'offset': 10, 'limit': 5})
models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true]]], {offset: 10, limit: 5})
$models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('is_company', '=', true))), array('offset'=>10, 'limit'=>5));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(
asList("is_company", "=", true))),
new HashMap() {{ put("offset", 10); put("limit", 5); }}
)));
var records []int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "search",
[]any{[]any{
[]any{"is_company", "=", true},
}},
map[string]int64{"offset": 10, "limit": 5},
}, &records); err != nil {
log.Fatal(err)
}
结果:
[13, 20, 30, 22, 29]
统计记录数¶
与其检索一份可能极为庞大的记录列表再计数,不如使用 search_count() 仅获取匹配查询的记录数量。它接受与 search() 相同的 域 筛选条件,且不接受其他参数。
示例
models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', True]]])
models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', true]]])
$models->execute_kw($db, $uid, $password, 'res.partner', 'search_count', array(array(array('is_company', '=', true))));
(Integer)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_count",
asList(asList(
asList("is_company", "=", true)))
));
var counter int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "search_count",
[]any{[]any{
[]any{"is_company", "=", true},
}},
}, &counter); err != nil {
log.Fatal(err)
}
结果:
19
注解
如果其他用户也在使用服务器,先调用 search 再调用 search_count (或反之)可能得不到一致的结果:两次调用之间存储的数据可能已发生变化。
读取记录¶
记录数据可通过 read() 方法访问,该方法接受一个 ID 列表(由 search() 返回),以及可选的待获取字段列表。默认情况下,它会获取当前用户可读取的所有字段,数量通常非常大。
示例
ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]], {'limit': 1})
[record] = models.execute_kw(db, uid, password, 'res.partner', 'read', [ids])
# count the number of fields fetched by default
len(record)
ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', 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
$ids = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('is_company', '=', true))), 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]);
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 partner = (Map)((Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"res.partner", "read",
asList(ids)
)
))[0];
// count the number of fields fetched by default
partner.size();
var ids []int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "search",
[]any{[]any{
[]any{"is_company", "=", true},
}},
map[string]int64{"limit": 1},
}, &ids); err != nil {
log.Fatal(err)
}
var records []any
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "read",
ids,
}, &records); err != nil {
log.Fatal(err)
}
// count the number of fields fetched by default
count := len(records)
结果:
121
反过来,只挑选三个认为值得关注的字段。
models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {'fields': ['name', 'country_id', 'comment']})
models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {fields: %w(name country_id comment)})
$models->execute_kw($db, $uid, $password, 'res.partner', 'read', array($ids), array('fields'=>array('name', 'country_id', 'comment')));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "read",
asList(ids),
new HashMap() {{
put("fields", asList("name", "country_id", "comment"));
}}
)));
var recordFields []map[string]any
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "read",
ids,
map[string][]string{
"fields": {"name", "country_id", "comment"},
},
}, &recordFields); err != nil {
log.Fatal(err)
}
结果:
[{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
注解
即使未请求 id 字段,它也始终会被返回。
列出记录的字段¶
可以使用 fields_get() 检查模型的字段,并查看哪些字段值得关注。
由于它返回大量元信息(客户端程序也会使用它),因此在打印之前应先进行筛选;对人类用户最有用的项是 string (字段标签)、help (帮助文本,如果有)和 type (用于了解应预期哪些值,或在更新记录时应发送哪些值)。
示例
models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [], {'attributes': ['string', 'help', 'type']})
models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [], {attributes: %w(string help type)})
$models->execute_kw($db, $uid, $password, 'res.partner', 'fields_get', array(), array('attributes' => array('string', 'help', 'type')));
(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"));
}}
));
recordFields := map[string]string{}
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "fields_get",
[]any{},
map[string][]string{
"attributes": {"string", "help", "type"},
},
}, &recordFields); err != nil {
log.Fatal(err)
}
结果:
{
"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"
},
搜索并读取¶
由于这是非常常见的任务,Odoo 提供了一个 search_read() 快捷方式,顾名思义,它等同于先执行 search() 再执行 read(),但无需执行两次请求并保留 ID。
它的参数与 search() 的参数类似,但还可以接受一个 fields 列表(与 read() 一样,如果未提供该列表,将获取匹配记录的所有字段)。
示例
models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', True]]], {'fields': ['name', 'country_id', 'comment'], 'limit': 5})
models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', true]]], {fields: %w(name country_id comment), limit: 5})
$models->execute_kw($db, $uid, $password, 'res.partner', 'search_read', array(array(array('is_company', '=', true))), array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_read",
asList(asList(
asList("is_company", "=", true))),
new HashMap() {{
put("fields", asList("name", "country_id", "comment"));
put("limit", 5);
}}
)));
var recordFields []map[string]any
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "search_read",
[]any{[]any{
[]any{"is_company", "=", true},
}},
map[string]any{
"fields": []string{"name", "country_id", "comment"},
"limit": 5,
},
}, &recordFields); err != nil {
log.Fatal(err)
}
结果:
[
{
"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() 创建。该方法创建单条记录并返回其数据库标识符。
create() 接受一个字段到值的映射,用于初始化记录。对于任何具有默认值且未通过映射参数设置的字段,将使用默认值。
示例
id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{'name': "New Partner"}])
id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{name: "New Partner"}])
$id = $models->execute_kw($db, $uid, $password, 'res.partner', 'create', array(array('name'=>"New Partner")));
final Integer id = (Integer)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "create",
asList(new HashMap() {{ put("name", "New Partner"); }})
));
var id int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "create",
[]map[string]string{
{"name": "New Partner"},
},
}, &id); err != nil {
log.Fatal(err)
}
结果:
78
警告
虽然大多数值类型都在意料之中(Integer 用整数,Char 或 Text 用字符串),
Date、Datetime和Binary字段使用字符串值One2many和Many2many使用一种特殊的命令协议,详情见write 方法的文档。
更新记录¶
可以使用 write() 更新记录。它接受要更新的记录列表,以及与 create() 类似、由更新字段到值的映射。
可以同时更新多条记录,但它们在被设置的字段上都会得到相同的值。无法执行“计算式”更新(即所设置的值依赖于记录的现有值)。
示例
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', 'read', [[id], ['display_name']])
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', 'read', [[id], ['display_name']])
$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', 'read', array(array($id), array('display_name')));
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", "read",
asList(asList(id), asList("display_name"))
)));
var result bool
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "write",
[]any{
[]int64{id},
map[string]string{"name": "Newer partner"},
},
}, &result); err != nil {
log.Fatal(err)
}
// get record name after having changed it
var record []any
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "name_get",
[]any{
[]int64{id},
},
}, &record); err != nil {
log.Fatal(err)
}
结果:
[[78, "Newer partner"]]
删除记录¶
通过将记录 ID 提供给 unlink(),可以批量删除记录。
示例
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]]])
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]]])
$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)))
);
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "unlink",
asList(asList(id))));
// check if the deleted record is still in the database
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(asList("id", "=", 78)))
)));
var result bool
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "unlink",
[]any{
[]int64{id},
},
}, &result); err != nil {
log.Fatal(err)
}
// check if the deleted record is still in the database
var record []any
if err := models.Call("execute_kw", []any{
db, uid, password,
"res.partner", "search",
[]any{[]any{
[]any{"id", "=", id},
}},
}, &record); err != nil {
log.Fatal(err)
}
结果:
[]
检查与内省¶
虽然我们之前使用 fields_get() 查询模型,并且从一开始就使用某个任意模型,但 Odoo 将大部分模型元数据存储在少数几个元模型中,这些元模型既允许查询系统,也允许通过 XML-RPC 动态地修改模型和字段(有一定限制)。
ir.model¶
通过其各种字段提供有关 Odoo 模型的信息。
name模型的人类可读描述
model系统中每个模型的名称
state模型是由 Python 代码生成(
base),还是通过创建ir.model记录生成(manual)field_id通过指向 ir.model.fields 的
One2many列出模型的字段view_ids指向为该模型定义的 视图架构 的
One2manyaccess_ids指向模型上设置的 访问权限 的
One2many关联关系
ir.model 可用于
查询系统中已安装的模型(作为对模型执行操作或探索系统内容的前提条件)。
获取特定模型的信息(通常通过列出与其关联的字段来实现)。
通过 RPC 动态创建新模型。
重要
自定义模型名称必须以
x_开头。必须提供
state并将其设置为manual,否则该模型不会被加载。无法向自定义模型添加新的 方法,只能添加字段。
示例
自定义模型最初只包含所有模型都可用的“内置”字段:
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']})
$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')));
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)})
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"));
}}
));
var id int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"ir.model", "create",
[]map[string]string{
{
"name": "Custom Model",
"model": "x_custom_model",
"state": "manual",
},
},
}, &id); err != nil {
log.Fatal(err)
}
recordFields := map[string]string{}
if err := models.Call("execute_kw", []any{
db, uid, password,
"x_custom_model", "fields_get",
[]any{},
map[string][]string{
"attributes": {"string", "help", "type"},
},
}, &recordFields); err != nil {
log.Fatal(err)
}
结果:
{
"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¶
提供有关 Odoo 模型字段的信息,并允许在不使用 Python 代码的情况下添加自定义字段。
model_id指向字段所属 ir.model 的
Many2onename字段的技术名称(在
read或write中使用)field_description字段的用户可读标签(例如
fields_get中的string)ttype要创建的字段的 类型
state字段是通过 Python 代码创建(
base),还是通过ir.model.fields创建(manual)required、readonly、translate在字段上启用相应的标志
groups字段级访问控制,即指向
res.groups的Many2manyselection、size、on_delete、relation、relation_field、domain类型特定的属性和自定义设置,详见 字段文档
重要
与自定义模型一样,只有使用
state="manual"创建的新字段才会作为模型上的实际字段被激活。无法通过
ir.model.fields添加计算字段,某些字段元信息(默认值、onchange)也无法设置。
示例
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)));
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]])
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))
));
var id int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"ir.model", "create",
[]map[string]string{
{
"name": "Custom Model",
"model": "x_custom",
"state": "manual",
},
},
}, &id); err != nil {
log.Fatal(err)
}
var fieldId int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"ir.model.fields", "create",
[]map[string]any{
{
"model_id": id,
"name": "x_name",
"ttype": "char",
"state": "manual",
"required": true,
},
},
}, &fieldId); err != nil {
log.Fatal(err)
}
var recordId int64
if err := models.Call("execute_kw", []any{
db, uid, password,
"x_custom", "create",
[]map[string]string{
{"x_name": "test record"},
},
}, &recordId); err != nil {
log.Fatal(err)
}
var recordFields []map[string]any
if err := models.Call("execute_kw", []any{
db, uid, password,
"x_custom", "read",
[][]int64{{recordId}},
}, recordFields); err != nil {
log.Fatal(err)
}
结果:
[
{
"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"
}
]