3.1 核心关系图
res.partner <--- _inherits / partner_id --- res.users
|
| res_groups_users_rel
v
res.groups
/ | \
implied_ids | +--> menu/view access
+----------> ACL / record rules
res.users ---- company_id ------> res.company 当前主公司
---- company_ids -----> res.company 允许切换的公司集合
res.users 与 hr.employee 不是同一个概念;安装 hr 后通过用户/员工关系连接。
3.2 res.users 的设计
res.users 使用委托继承 _inherits = {'res.partner': 'partner_id'},源码起点为 odoo/addons/base/models/res_users.py:318-399。这意味着姓名、邮箱、地址等联系人属性来自关联的 res.partner,但用户并不等于员工。
重要字段:
| 字段 | 类型 | 作用 | 权限设计注意点 |
|---|---|---|---|
partner_id |
Many2one | 委托继承的联系人 | 删除受 restrict 约束;不要把联系人权限等同用户权限 |
login |
Char | 登录标识 | 数据库唯一 |
password / new_password |
Char | 凭证维护入口 | 不属于业务授权配置 |
active |
Boolean | 是否启用 | 归档不是完整的授权审计 |
groups_id |
Many2many | 用户实际拥有的组 | 包含直接和物化后的隐含组,原表不保存来源 |
share |
computed | 外部共享用户标识 | 本质上由是否拥有 base.group_user 计算 |
company_id |
Many2one | 默认/主公司 | 必须属于 company_ids |
company_ids |
Many2many | 用户可用公司 | 不等于本次请求已启用公司 |
action_id |
Many2one | 登录后的 Home Action | 只是入口,不授予目标模型权限 |
新内部用户默认复制模板用户 base.default_user 的组,见 res_users.py:357-363 和 odoo/addons/base/security/base_groups.xml:25-32,50-62。给模板用户新增组时,源码还会把新增项同步给现有内部用户;删除组没有对称的批量移除行为,见 res_users.py:711-756。因此生产系统中修改模板用户不仅影响以后新建用户,也可能立即扩大现有用户权限,必须纳入变更预览和审批。
3.3 内部、门户和公共用户
三种基本用户类型由以下组表达:
| 类型 | 典型组 | 用途 |
|---|---|---|
| 内部用户 | base.group_user |
后台员工用户 |
| 门户用户 | base.group_portal |
已登录的外部伙伴 |
| 公共用户 | base.group_public |
未登录网站访问共享身份 |
这三个组位于 base.module_category_user_type,用户类型要求互斥;约束会连同隐含组一起检查,源码见 res_users.py:605-637。公共用户是共享身份,不能把某个访问者的个体授权简单存到 Public user 上。
share 不是第四种用户类型。它按用户是否拥有内部用户组计算,用于区分内部与外部共享用户,见 res_users.py:529-534。
3.4 res.groups 的重要字段
模型定义在 odoo/addons/base/models/res_users.py:175-200:
| 字段 | 作用 |
|---|---|
name |
组名;同一分类下唯一 |
users |
组中的用户,groups_id 的反向关系 |
category_id |
应用/权限分类,也影响用户表单展示方式 |
model_access |
该组关联的 ACL |
rule_groups |
该组关联的非全局记录规则 |
menu_access |
该组关联的菜单 |
view_access |
该组关联的视图记录 |
share |
用于共享数据的组标记 |
implied_ids |
当前组直接隐含的组 |
trans_implied_ids |
传递闭包计算结果 |
组应表示稳定、可复用的技术能力,如“销售用户”“销售经理”“会计开票”。不要为每个员工、每张单据、每次临时授权都建一个组,否则组数量、规则 SQL 和运维复杂度会迅速失控。
3.5 隐含组不是纯运行时继承
implied_ids 表示“拥有 A 组就自动拥有 B 组”。Odoo 会计算传递闭包,并把隐含组实际写入 res_groups_users_rel:
销售经理
implied_ids -> 销售用户
implied_ids -> 内部用户
最终用户的 groups_id 中会实际存在三个组。
核心源码:
res_users.py:1441-1455:implied_ids和传递闭包。res_users.py:1468-1496:递归 SQL 物化隐含组。res_users.py:1557-1589:用户 create/write 时补全隐含组。
这带来两个后果:
- ACL 与规则查询很快,因为用户组已经物化。
- 原生关系表不记录“直接授予、由哪个岗位授予、由哪条继承路径得到”,所以撤销和权限溯源困难。
修改 implied_ids 时还要注意删除语义。普通 write 主要负责补加;设置项使用 _remove_group() 才会判断其他继承路径后清理用户关系,见 res_users.py:1510-1527。增强模块若需要可靠来源,不能只依赖原生 M2M 表。
3.6 用户表单中的权限字段为什么找不到数据库列
用户表单上常见的权限复选框和单选项,其字段名类似:
in_group_42
sel_groups_10_11_12
它们是动态生成的虚拟字段,不是数据库列:
res_users.py:1591-1610说明命名规则。res_users.py:1648-1779动态重写base.user_groups_view。res_users.py:1953-1979把虚拟字段转换回groups_id的 M2M 命令。
category_id 和组之间的 implied 关系会影响显示成 checkbox 还是互斥 selection。排查“用户表单为什么只允许选一个权限级别”时,要看分类与组包含关系,而不是寻找 sel_groups_* 数据库字段。
3.7 root、Access Rights 和 Settings 的区别
| 身份/组 | 含义 | 是否自动绕过 ACL/规则 |
|---|---|---|
UID 1 / env.su=True |
超级用户模式 | 是 |
base.group_erp_manager |
Access Rights 管理权限 | 否 |
base.group_system |
Settings 管理权限 | 否 |
Settings -> Access Rights -> Internal User 的继承链定义于 base/security/base_groups.xml:10-28。env.is_admin() 和 env.is_system() 会识别这些组,但它们不等于 env.su,见 odoo/api.py:656-668。
3.8 用户读取和修改自己的特例
普通内部用户未必拥有通用的 res.users 管理权限,但仍需要修改自己的语言、时区、签名和当前公司。Odoo 为“当前用户自己的记录”定义了白名单:
SELF_READABLE_FIELDS:包括login、groups_id、company_id、语言、时区、头像等。SELF_WRITEABLE_FIELDS:只包括签名、Home Action、当前公司、邮箱、姓名、头像、语言和时区等较安全字段。
定义见 odoo/addons/base/models/res_users.py:337-355,特殊 read/write 处理见 res_users.py:645-665,720-788。满足“只操作本人且字段全部在白名单”时,模型内部会以最小白名单 sudo 完成操作。
groups_id 可供用户读取以判断界面能力,但不在本人可写白名单中。增强 res.users 时若向这些属性追加字段,必须分别评估信息泄露和自助修改风险;不能因为字段只是“偏好设置”就同时加入读写白名单。
老赵解读
中国企业常把“部门、岗位、职级、角色、公司”都塞进用户组,短期配置快,长期一定会形成组爆炸。原生组更适合表达稳定的技术能力;组织归属、岗位任职、临时代理和数据范围应当保留各自语义,再投影成必要的原生组。这样才能回答“为什么有权、权从哪里来、何时失效”。
目前没有任何评论。