外部 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)>

API 密钥

14.0 新版功能.

Odoo 支持 API 密钥,并且(取决于模块或设置)可能 要求 使用这些密钥来执行 Web 服务操作。

在脚本中使用 API 密钥的方法很简单:用密钥替换你的 密码 即可。登录名保持不变。你应当像保管密码一样谨慎地保管 API 密钥,因为它们本质上提供对用户账户的相同访问权限(尽管它们不能用于通过界面登录)。

要为账户添加密钥,只需转到 首选项 或 我的个人资料:

../../_images/preferences.png

然后打开 账户安全 选项卡,并点击 新建 API 密钥:

../../_images/account-security1.png

输入密钥的描述,该描述应尽可能清晰、完整:这是你日后识别密钥、并判断是删除还是保留它们的唯一途径。

点击 生成密钥,然后复制提供的密钥。请妥善保存此密钥:它等同于你的密码,正如密码一样,系统之后无法再检索或显示该密钥。如果丢失了这个密钥,你将不得不创建一个新密钥(并且很可能需要删除丢失的那个)。

一旦在账户上配置了密钥,它们就会显示在 新建 API 密钥 按钮上方,并且你可以将其删除:

../../_images/delete-key.png

已删除的 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']

正在登录

Odoo 要求 API 用户先通过身份验证,然后才能查询大部分数据。

xmlrpc/2/common 端点提供无需身份验证的元调用,例如身份验证本身或获取版本信息。要在尝试身份验证之前确认连接信息是否正确,最简单的调用是查询服务器版本。身份验证本身通过 authenticate 函数完成,并返回一个用户标识符(uid),在已通过身份验证的调用中用它代替登录名。

common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))
common.version()

结果:

{
    "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, {})

调用方法

第二个端点是 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})

结果:

true

列出记录

可以通过 search() 列出并筛选记录。

search() 接受一个必需的 域 筛选条件(可以为空),并返回所有匹配该筛选条件的记录的数据库标识符。

示例

例如,要列出客户公司:

models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]])

结果:

[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})

结果:

[13, 20, 30, 22, 29]

统计记录数

与其检索一份可能极为庞大的记录列表再计数,不如使用 search_count() 仅获取匹配查询的记录数量。它接受与 search() 相同的 域 筛选条件,且不接受其他参数。

示例

models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', True]]])

结果:

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)

结果:

121

反过来,只挑选三个认为值得关注的字段。

models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {'fields': ['name', 'country_id', 'comment']})

结果:

[{"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']})

结果:

{
    "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})

结果:

[
    {
        "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"}])

结果:

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']])

结果:

[[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]]])

结果:

[]

检查与内省

虽然我们之前使用 fields_get() 查询模型,并且从一开始就使用某个任意模型,但 Odoo 将大部分模型元数据存储在少数几个元模型中,这些元模型既允许查询系统,也允许通过 XML-RPC 动态地修改模型和字段(有一定限制)。

ir.model

通过其各种字段提供有关 Odoo 模型的信息。

name

模型的人类可读描述

model

系统中每个模型的名称

state

模型是由 Python 代码生成(base),还是通过创建 ir.model 记录生成(manual)

field_id

通过指向 ir.model.fields 的 One2many 列出模型的字段

view_ids

指向为该模型定义的 视图架构 的 One2many

access_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']})

结果:

{
    "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 的 Many2one

name

字段的技术名称(在 read 或 write 中使用)

field_description

字段的用户可读标签(例如 fields_get 中的 string)

ttype

要创建的字段的 类型

state

字段是通过 Python 代码创建(base),还是通过 ir.model.fields 创建(manual)

required、readonly、translate

在字段上启用相应的标志

groups

字段级访问控制,即指向 res.groups 的 Many2many

selection、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]])

结果:

[
    {
        "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"
    }
]