先说结论
很多人第一次碰到 Odoo 归档时,都会问同一个问题:
“我明明没有删数据,为什么搜索结果里不见了?”
答案通常不是数据消失了,而是 active_test 把它过滤掉了。
在 Odoo 里,归档不是删除。它只是把记录的 active 标志置为 False。默认搜索时,ORM 会把这类记录挡在结果外面。
源码里,默认过滤是怎么加上的
在 odoo/orm/models.py 的 _search() 里,Odoo 会先检查:
- 当前模型有没有
active字段 active_test参数是不是开启- context 里是不是也允许
active_test - 你的 domain 里有没有已经显式写了
active
如果都满足,ORM 就会自动补上一条条件:
('active', '=', True)
这意味着你平时写的:
self.env['res.partner'].search([('name', 'ilike', 'Acme')])
实际上往往会变成“只搜 active 的 Acme”。
为什么这条默认条件很容易让人误判
因为它不是显式写在你的 domain 里,而是 ORM 在内部补上的。
所以你会感觉:
- 菜单里明明有这个记录
- 数据库里明明查得到
- 但是
search()返回空
这通常就是 active_test 在起作用。
特别是在这些场景里最常见:
- 客户、产品、供应商、联系人被归档
- Many2one 下拉框里找不到旧数据
- 自定义报表少了“历史对象”
什么时候可以关闭它
如果你确实要把归档记录也算进去,可以显式关闭:
records = self.with_context(active_test=False).search(domain)
或者在更底层的调用里,直接把 active_test=False 传进去。
关闭后,ORM 不会自动帮你补 active=True,归档记录就会重新进入搜索范围。
一个容易忽略的细节:显式 domain 会改变默认行为
如果你的 domain 里已经写了 active 条件,ORM 就不会再重复加一层默认过滤。
这能避免出现“双重筛选”。
所以像下面这样的查询:
[('active', 'in', [True, False])]
就是在告诉 ORM:我自己已经决定要不要包含归档记录了。
search 看不到,不代表 browse 不存在
这是很多人会混淆的点。
search()受 domain、权限和active_test影响browse(ids)只是按 id 取记录集,不等于马上做同样的搜索过滤
所以排障时,不要把“搜索不到”直接等同于“记录没了”。先看它是不是被归档了,再看 context 里有没有 active_test=False。
为什么这条规则对系统性能也有帮助
从设计上看,归档记录默认不参与搜索,能减少很多无意义的结果集。
尤其是大表里,历史记录往往比有效记录多。默认过滤能让:
- 下拉框更干净
- 搜索更快
- 业务界面更聚焦当前可用数据
这也是为什么 Odoo 把 active 设计成默认行为,而不是让每个调用者自己手写过滤。
实战里怎么排查“记录消失”
可以按这个顺序:
- 看记录是否只是归档了
- 看当前代码是否用了
with_context(active_test=False) - 看 domain 里有没有显式过滤
active - 看 Many2one / 搜索面板是不是默认只展示 active 数据
很多问题其实不是权限,也不是 SQL,而只是默认过滤。
结尾
你可以把 active_test 记成一句话:
归档不是删除,
active_test只是默认不让它出现在搜索结果里。
理解这一点,很多“数据丢了”的误会就会立刻消失。
DISCUSSION
评论区