第四十七章 常见应用场景
篇别:第十五篇 场景与实践
本章学习目标
- 在多公司部署中落实 ACL + 记录规则 + 代码路径 的 隔离闭环,并识别 性能热点(规则复杂度、索引、
sudo)。 - 能为
company_id高频过滤 等查询设计 合理索引,理解 多公司 与 部分索引 的取舍。 - 说清 库存批次/序列号 在 Odoo 中的 产品配置要点 与 业务流位置(不替代库存用户手册)。
- 正确使用
context的传递、合并与危险键(active_test、sudo、语言、默认值)。 - 掌握 批量写入 的 ORM 批量 API、分批提交、避免 N+1 等习惯。
- 能对照本章 扩展场景 做 上线前检查清单。
导读:场景章与全书的关系
本章是 跨章节综合:多公司与安全见 第四十五章、第四十六章、第三十章;ORM 与 context 见 第四章;性能见 第十四章;索引与 SQL 见 ORM 文档与 PostgreSQL 实践。实施时以 官方 Multi-company Guidelines 与 Performance 为权威补充。
47.1 多公司下数据安全隔离与性能
47.1.1 知识要点
安全隔离(三层缺一不可)
- 访问控制(ACL):
ir.model.access决定 能否 CRUD;无权限则 记录规则不执行到 或 直接拒访。 - 记录规则(
ir.rule):用 domain 限制 可见行;多公司典型模式:['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
错误:漏写company_id条件、或sudo()后未再约束公司 → 串数据。 - 业务代码:
create/write时 显式或默认 写入company_id;跨公司报表用with_company()而非 随意sudo。
性能要点
- 规则越多、domain 越复杂,
search生成的 WHERE 越长;合并同类规则、避免 重复子查询式 domain(能用单表条件就不用嵌套)。 company_ids在规则中求值 是常规路径;确保res.users与 公司关联 无异常膨胀(测试用户挂十几家公司会放大 IN 列表)。- 缓存:勿 在循环里
sudo().env['res.company'].browse(...)做可缓存的读取;公司相关计算 注意@api.depends是否包含company_id。 - 测试:同一用户 切换当前公司 对 同一菜单 做
search_count,断言差集符合预期(与 46.1 一致)。
47.1.2 案例(检查清单)
| 检查项 | 说明 |
|---|---|
自定义模型有 company_id |
布尔/多公司字段与规则一致 |
| 规则 domain 与 ACL 同测 | Portal / 内部用户各测 |
无「全局 sudo + 忘记公司」 |
审计 grep -R "sudo()" custom_addons/ |
| 共享主数据策略明确 | 产品/伙伴 company_id 为空时的规则文档化 |
47.1.3 本节练习
- 简答:仅依赖 界面切换公司 而不写 记录规则,为何仍可能 不安全?
- 判断:
sudo()后env.company仍为用户当前公司,故数据一定隔离。( )
参考答案提示:1. API/RPC、定时任务、服务器动作 可能 不经过同一套 UI 假设。2. 错;sudo 绕过权限,必须 代码或规则层 再约束。
47.2 多公司环境下的数据库索引与查询性能
47.2.1 知识要点
为何多公司更「吃索引」
- 列表、看板、报表常在 domain 中固定
('company_id', '=', user.company_id.id)或 规则隐式过滤 →company_id与状态、日期 组合出现频率高。 - PostgreSQL 对
WHERE company_id = ? AND state = ?类查询,适合 复合索引(company_id, state)或(company_id, date DESC)(按实际 SQL 为准,用 EXPLAIN ANALYZE 验证)。
实践建议
- 不要盲目给每个字段加索引:写放大、规划器选择变差;以慢查询日志 / ORM 日志为准。
- 外键与
company_id:业务表company_id索引 常与parent_id、partner_id等 组合 更有效。 - 部分索引(partial index):若 绝大多数查询 只关心
state != 'cancel'等,可与 DBA 评估WHERE state <> 'cancel'部分索引(迁移脚本 维护,见 第四十四章)。 - Odoo 侧:
index=True在 字段定义 上声明;复合索引 往往需init钩子或post_init_hook执行CREATE INDEX CONCURRENTLY(注意升级窗口)。
47.2.2 案例(示意)
某 sale.order 列表极慢,EXPLAIN 显示 Seq Scan。在确认 domain 主要为 本公司 + 未取消 后,评估:
- 字段级
index=True于company_id;或 - 复合索引
(company_id, state)(与团队 DBA 评审)。
47.2.3 截图占位

47.2.4 本节练习
- 简答:单字段索引
company_id与 复合(company_id, state)各适合什么 WHERE 形态? - 实操:在 测试库 对一条慢
search_read打开 SQL 日志,记录 是否用到索引。
参考答案提示:1. 仅按公司过滤 → 单字段可能够;同时按状态/日期排序过滤 → 复合更常见。
47.3 产品批次管理(简述)
47.3.1 知识要点
Odoo 库存应用 中,追溯 依赖 跟踪方式:
- 按批次(Lots):适合 食品、化工 等 同 SKU 多批次、需 保质期/召回。
- 按序列号(Serial Numbers):一物一码(设备、高价值单品)。
- 不跟踪:不启用 lot/serial,库存只到 数量 + 库位。
配置链(概念)
- 产品类别或产品 上设置 追踪:
none/lot/serial(具体字段名以版本为准,见 库存模块)。 - 入库/出库/调拨 行上指定 批次或序列号;制造 与 委外 可在 完工 时 生成或消耗 批次。
- 报表:库存估值、追溯 可 从单据反查到批次,再反查 供应商批次(若维护)。
开发扩展时注意
- 业务代码常通过
stock.move.line与lot_id/lot_name交互;不要 绕过库存移动 直接改 quant(破坏一致性)。 - 多公司:批次主数据 是否共享 依 产品与公司策略;与 47.1 规则一致。
- 性能:大批量扫码 场景关注 移动行数量 与 条码模块;用户手册见 Inventory 用户文档。
47.3.2 本节练习
- 简答:序列号 与 批次 在 数据结构 上的典型差异是什么(粒度)?
- 判断:启用批次后,销售订单 可不指定批次直接发货。( )
参考答案提示:1. 序列号 通常 一条移动行对应唯一 SN;批次 多数量共享同一 lot。2. 依 流程配置;常 在拣货/发货环节 指定,不一定在订单行——以你们实施为准。
47.4 context 在 Odoo 中的使用方式
47.4.1 知识要点
context 是什么
self.env.context:当前环境的 字典,影响 ORM 行为、默认值、部分方法分支、翻译语言 等。- 不可变假设:应使用
with_context(...)生成 新环境,避免 原地修改 引发 诡异副作用。
常见键(示例)
| 键 | 作用 | 注意 |
|---|---|---|
lang |
翻译与 用户可见字符串 | RPC 默认常带用户语言 |
tz |
时区相关 日期边界 | 报表与日切 |
allowed_company_ids |
多公司 可见范围 | 与 当前公司 配合 |
active_id / active_ids |
向导、按钮 默认关联 | 服务端 勿盲信,需 校验权限 |
active_test |
active=False 记录是否参与 search |
默认 True;归档排查时改为 False 要谨慎 |
tracking_disable / mail_create_nosubscribe 等 |
mail 行为 | 批量导入时常用,见官方 mail 文档 |
prefetch_fields / bin_size 等 |
读取优化 | 按版本文档使用 |
危险习惯
sudo不是 context 键,是env替换;with_user/with_company同理。context里塞「权限」:不可靠;权限必须是 ACL + 规则。- 前端
context与后端合并:action、view、field 可传 context;服务端 对 敏感操作 必须 再次校验。
47.4.2 案例
# 推荐:链式合并,不修改原 env
self.env['my.model'].with_context(my_flag=True).do_something()
# 仅本次调用需要英文标签(示例)
labels = self.with_context(lang='en_US').mapped('name')
47.4.3 本节练习
- 简答:为何
active_test=False要 慎用? - 实操:在 某 onchange 中打印
self.env.context.keys(),区分 来自 action 与来自默认值 的键。
参考答案提示:1. 易暴露已归档/历史数据,且 若配合 sudo 风险更大。
47.5 高性能批量写入数据
47.5.1 知识要点
ORM 层
create([{...}, {...}]):单次调用 多行 优于 循环create。write合并:同一值 更新 多条记录 → 一次recordset.write({...})。create_multi:在 自定义模型 上若 有复杂默认值,可重写create仍调super或 实现官方推荐的批量入口(以 ORM 文档 为准)。- 跳过重计算/消息:批量导入时
context中 关闭 mail tracking、自动订阅 等(务必在事务结束后 恢复一致的业务通知策略)。
分批与事务
- 上万行:按 批次 commit(Shell/脚本 中)或
cr.commit()(慎用,破坏 请求内原子性;优先用队列 job)。 - Odoo 多 worker:长事务 锁行;批量导入 用 专用 cron / queue_job 更稳。
SQL 直连(最后手段)
env.cr.execute+INSERT ... SELECT:绕过 ORM,需 自己处理 约束、翻译、bus、mail;升级脚本 比 业务代码 更合适。- 官方 Performance — Good practices:批量 ORM、减少循环中的 search。
47.5.2 反模式清单
| 反模式 | 替代 |
|---|---|
for r in recs: r.write({...}) |
recs.write({...}) |
循环内 search 同条件 |
一次 search,字典映射 |
每条 message_post |
批量后 一条汇总 或 关闭 tracking 段 |
巨型单事务 create 十万行 |
分批 + 监控锁 |
47.5.3 本节练习
- 实操:将 1000 条 测试数据 分两路 创建:循环
createvs 单次create(list),对比 日志 SQL 条数或耗时。 - 简答:为何 业务接口 中 随意
cr.commit()往往 不推荐?
参考答案提示:2. 破坏请求事务边界、与 HTTP 重试/并发 难协调,调试困难。
47.6 扩展场景一:大列表、导出与「不要一次读爆内存」
要点:search_read 指定 fields;导出 十万级用 分批 search + 流式 或 标准导出限制;read_group 做汇总而非 拉全表到 Python。
关联:第十四章 性能、报表 章节。
47.7 扩展场景二:Webhook / 外部回调的幂等与并发
要点:HTTP 控制器 快速 200;重活丢队列;用 外部唯一键(如电商订单号)做 sql_constraint 或唯一索引 防重复建单。
关联:第三十六章、第三十八章。
47.8 扩展场景三:附件与二进制字段
要点:大文件走 filestore;不要在 fields.Binary 上建业务索引;列表 勿默认加载 大 binary,用 bin_size 等策略。
关联:存储与备份(安装与运维章)。
47.9 扩展场景四:定时任务(Cron)与长任务拆分
要点:单次 cron 处理有限条数,游标或状态字段 记录进度;避免 与业务高峰抢锁;ir.cron 超时 与 worker 配置 协调。
关联:第九章 Actions、第四十三章。
47.10 扩展场景五:Portal 仅看「自己的」数据
要点:Portal 用户使用 专用组 + 记录规则(partner_id = user.partner_id 等);控制器 request.env.user 勿混用 内部用户 逻辑。
关联:第三十章、第三十六章。
本章综合练习
- 画出 多公司请求 从 HTTP → env.company → ir.rule → SQL 的 简图(5 个框以内)。
- 为 一批量导入接口 写 3 条 性能与安全 约束(context、分批、权限 各至少一点)。
- 结合 47.3,用 一句话 向 仓库主管 说明 批次 与 序列号 选型差异。
本章场景可与 官方文档对照与延伸阅读 中 Multi-company、Performance、Controllers 等条目交叉阅读。内置 AI 模块 的工程细节见 第四十八章。本机三系统装 Odoo 见 第四十九章。