跳至内容

第6章 字段权限

6.1 三种容易混淆的写法

# 1. 服务端字段访问限制
secret = fields.Char(groups="demo.group_manager")

# 2. 只影响 UI 是否可编辑
amount = fields.Float(readonly=True)
<!-- 3. 只控制这个视图节点是否返回给用户 -->
<field name="amount" groups="demo.group_manager" readonly="1"/>

只有 Python 字段定义的 groups= 是字段级服务端访问边界。readonly=True 的源码说明明确写着只影响 UI,代码赋值仍可工作,见 odoo/fields.py:152-175

6.2 Python 字段 groups= 如何生效

Field.is_accessible(env) 根据当前用户组判断,见 odoo/fields.py:950-956。ORM 会:

  • fields_get() 中移除不可访问字段:odoo/models.py:3693-3717
  • 从最终视图架构中移除或合并字段组条件。
  • 显式 read/fetch 不可访问字段时抛 AccessError
  • write 不可访问字段时抛 AccessError
  • 在搜索 domain 使用不可访问字段时检查并拒绝。

字段组采用与 has_groups() 一致的表达方式:多个正组是 OR,负组以 !xml_id 表示排除。核心敏感字段建议使用简单、稳定的正组,避免难以解释的复杂组合。

6.3 Odoo 18 create 路径的源码注意事项

当前快照中:

  • write() 明确调用 check_field_access_rights('write', vals.keys()),见 models.py:4661-4666
  • BaseModel.create()models.py:4895-5041 检查模型 create ACL,但没有调用 check_field_access_rights('create', vals.keys())

这是基于本版本源码得出的重要结论,不应泛化到未检查的其他版本。对于能影响金额、审批状态、密钥、公司归属或其他完整性的受限字段,建议在模型中加固:

@api.model_create_multi
def create(self, vals_list):
    supplied = {name for vals in vals_list for name in vals}
    self.check_field_access_rights("create", supplied)
    return super().create(vals_list)

这段加固只覆盖调用方显式提供的 key;默认值、precompute、inverse、委托父字段以及业务不变量仍需分别验证。配套实验模块采用了显式 key 检查,并另外约束负责人和初始状态。

6.4 原生字段组的表达力边界

字段只有一套 groups,无法直接表达:

  • A 组可读不可写;
  • B 组只能在草稿时写;
  • 金额十万元以下可写,以上只读;
  • 只能修改自己的备注字段;
  • 字段值从 X 改到 Y 需要审批。

这些策略应在 write()、专用 action 或增强策略服务中实现。要保护工作流状态,不能只把状态字段在视图里设成 readonly,因为 RPC 可以直接调用 write({'state': ...})

6.5 安全的字段与状态写入模式

def write(self, vals):
    if "state" in vals:
        raise AccessError(_("Use a workflow action to change the state."))
    return super().write(vals)

def _set_state(self, state):
    # 下划线方法不能通过普通 RPC 调用;BaseModel.write 仍会检查记录访问。
    return super(MyModel, self).write({"state": state})

def action_approve(self):
    self.check_access("write")
    if not self.env.user.has_group("my_module.group_approver"):
        raise AccessError(_("Only approvers can approve."))
    if any(record.state != "submitted" for record in self):
        raise UserError(_("Only submitted records can be approved."))
    if any(record.owner_id == self.env.user for record in self):
        raise AccessError(_("You cannot approve your own request."))
    return self._set_state("approved")

这里同时保护了组、记录访问、当前状态和职责分离。按钮上的 groups 只负责让普通用户看不到入口。

老赵解读

字段权限适合保护稳定、敏感的字段,例如成本、薪资或内部控制字段;涉及状态、金额阈值、本人回避等动态条件时,应回到业务方法逐项校验。一个可靠的审批按钮至少要同时检查能力、记录范围、当前状态、关键字段和职责分离,字段 groups 与按钮 groups 都只能承担其中一部分。

额定值
0 0

目前没有任何评论。

成为第一个发表评论的人。