企业电子表格

Odoo 企业版为什么不让外部人直接编辑在线 Spreadsheet:冻结副本、token 只读与修订隔离边界讲透

很多人以为 Documents 里的在线 spreadsheet 只要分享链接就能像普通云表格一样协同外部编辑。但从 `documents_spreadsheet` 源码和测试看,Odoo 企业版刻意把“内部协作态”和“外部分发态”拆开:在线表格禁止 portal / 链接编辑,真正对外共享走的是 freeze-and-copy 只读副本,连 revision、Excel 下载和 live data 展示都单独设了边界。

企业 协同办公
进阶 开发者 3 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

如果你把 Odoo 企业版 Documents 里的在线 spreadsheet 理解成“谁拿到链接谁都能一起改的云表格”,那会误解这个模块的产品边界。

/home/ubuntu/odoo-temp/enterprise/documents_spreadsheet 的模型、控制器和测试一起看,官方真正实现的是两种完全不同的对象语义:

  1. 在线 spreadsheet:面向内部协作,允许 revision、snapshot、贡献者追踪;
  2. frozen spreadsheet:面向外部分发或归档,只允许受控只读访问和 Excel 下载;
  3. 外部访问 token 只是访问入口,不等于获得协作写权限;
  4. 修订历史本身不是共享对象,即便文档可读,也不会把 revision ORM 能力一并放出去;
  5. live data 也不是随便公开渲染,前台分享前还会额外拦一道。

所以更准确的理解是:

Odoo 企业版把 spreadsheet 当成“内部协作工件”,而不是“默认可外包编辑的公共云文档”。对外分享时,官方更偏向先冻结成发布副本,而不是继续开放在线编辑。

这也是为什么它很适合归到 企业, 协同办公:重点不在 BI 展示,而在协同边界治理。


先看 models/documents_document.py 里的两个 SQL 级约束:

_spreadsheet_access_via_link = models.Constraint(
    "CHECK((handler != 'spreadsheet') OR access_via_link != 'edit')",
    "To share a spreadsheet in edit mode, add the user in the accesses",
)
_frozen_spreadsheet_access_via_link_access_internal = models.Constraint(
    "CHECK((handler != 'frozen_spreadsheet') OR (access_via_link != 'edit' AND access_internal != 'edit'))",
    "A frozen spreadsheet can not be editable",
)

这两条约束非常关键:

  • 普通在线 spreadsheet 不能通过链接直接给 edit
  • frozen spreadsheet 不只是“默认只读”,而是 从 link 和 internal 两个维度都禁止 edit

测试 test_spreadsheet_can_not_share_write_access_to_portal() 进一步把这件事钉死:

  • access_via_link = 'edit' 会触发数据库层 CheckViolation
  • frozen 副本如果想改成 access_internal='edit',同样直接失败。

这说明官方不是靠前端按钮灰掉来“劝你不要这么做”,而是把规则下沉到数据层:

在线协作可以发生,但必须绑定明确内部身份;任何“拿个链接就能改”的分享方式,都不被允许。


二、外部人为什么不能被赋予 edit:因为 Odoo 把编辑权视作“内部身份能力”

再看 models/documents_access.py

if (
    access.document_id.handler == 'spreadsheet'
    and (not user_ids or all(user_ids.mapped("share")))
    and access.role == 'edit'
):
    raise ValidationError(_('Spreadsheets can not be shared in edit mode to non-internal users.'))

意思很直接:

  • 如果对方没有用户,或者只有 portal/share 用户;
  • 你又想给 spreadsheet 的 edit
  • 系统直接拒绝。

测试里覆盖了三种情况:

  • portal user;
  • 没有内部用户的 partner;
  • 归档的内部用户仍然允许保留内部身份语义。

这背后的产品判断很明确:

  • 编辑 spreadsheet 不只是“能不能改 JSON”;
  • 它还意味着进入协同会话、触发 revision、参与 snapshot、影响 live 数据与共享状态;
  • 所以它被归类为一种 内部协作权限,不是外部访客权限。

也就是说,Odoo 并不想把 Documents Spreadsheet 做成 Google Sheets 式的匿名外部协作。


三、真正对外分发的正确姿势,不是继续开放原表,而是 action_freeze_and_copy()

action_freeze_and_copy() 是整个模块最值得注意的方法。

它的流程不是“生成一个分享链接”那么简单,而是:

  1. 先检查你是否有创建文档和读取当前表格的权限;
  2. 如果原文件夹下还没有 Frozen spreadsheets 文件夹,就 sudo 建一个专用文件夹;
  3. 复制当前 spreadsheet,名称改成 Frozen at <date>: <name>
  4. 把新副本的 handler 改成 frozen_spreadsheet
  5. 自动把原有 access 中的 edit 全降成 view
  6. access_via_link 固定给 view
  7. 同时保存一份 excel_export,供外部下载。

这里最有意思的地方有两个。

1)它不是“分享当前对象”,而是“复制成另一种对象”

冻结分享并不延续原 spreadsheet 的对象身份,而是复制一条新文档记录,放进专门的 frozen folder。

这意味着官方明确区分:

  • 原对象:继续内部协作;
  • 新对象:负责对外阅读、下载、归档。

2)权限会自动降级,而不是原样照搬

复制 access 时,源码用了:

'role': 'view' if access.role == 'edit' else access.role,

也就是:

  • 原来你在协作表上有 edit;
  • 到冻结副本后也只剩 view。

这说明 frozen 的目标不是保留协作现场,而是发布一份稳定只读版。

freeze-and-copy 的本质,是把“内部持续演进中的表格”切换成“对外可分发的快照制品”。


四、token 只是访问凭证,不是写权限万能钥匙

很多人会下意识以为:既然有 access_token,是不是拿到 token 就能推 revision?

_check_spreadsheet_share() 和测试 test_collaborative_dispatch_spreadsheet_with_token() / test_collaborative_readonly_dispatch_spreadsheet_with_token() 给出的答案是否定的。

写入要同时满足几件事:

  • token 必须正确;
  • 文档必须允许该操作;
  • 如果是写操作,access_via_link 还得是 edit
  • 但 spreadsheet 本身又被 SQL 约束禁止 access_via_link='edit'

于是结论就出来了:

  • 对普通 spreadsheet,token 可以辅助受控访问,但不会把公开链接升级成开放编辑
  • 对 frozen spreadsheet,非 sudo 写入直接被拒绝:
if not self.env.su and operation == 'write' and self.sudo().handler == 'frozen_spreadsheet':
    raise AccessError(_("You can not edit a frozen spreadsheet"))

这就是非常典型的“凭证 != 权限”设计。

官方允许 token 解决公开入口问题,但协作写权限仍然被牢牢拴在对象类型和访问规则上。


五、为什么 frozen spreadsheet 能下载 Excel,而原始在线表反而没有普通内容流

控制器 controllers/documents.py 还有一个很漂亮的边界设计。

_documents_content_stream() 里:

  • frozen spreadsheet 下载时走 excel_export
  • 普通 spreadsheet 反而直接抛错:ValueError("non-frozen spreadsheets have no content")

这意味着在线 spreadsheet 的“真实载体”不是一个稳定二进制文件下载口,而是:

  • snapshot
  • revision
  • JSON 数据
  • 协作状态

而 frozen spreadsheet 则被重新包装成适合共享/下载的制品。

这和前面的 freeze-and-copy 完全一致:

  • 在线对象强调协作;
  • 冻结对象强调交付。

六、前台公开查看前还要再检查一次:含 live data 的表不能随便公开渲染

同一个控制器在 _documents_render_portal_view() 里还有一道保护:

if document._contains_live_data():
    return request.render("documents_spreadsheet.documents_error_live_data")

这说明即便文档可分享,前台渲染也不是无条件放行。

为什么?因为 spreadsheet 里可能带有:

  • pivot / list 等动态数据源;
  • 依赖后端权限和会话上下文的 live 数据;
  • 不适合公开页面直接暴露的查询结果。

所以 Odoo 宁可提示错误,也不把一个需要内部上下文的数据表硬塞给公共页面。

这体现出官方对“可看”和“可安全公开渲染”做了进一步区分。


七、贡献者追踪和 revision ORM 也说明它是内部协作工件,不是公共文件

spreadsheet.contributor 会在读取序列化数据和元数据时更新最近贡献者; test_spreadsheet_collaborative.py 还明确验证了一个更重要的点:

  • 有文档读写权,不代表能直接读写 spreadsheet.revision ORM;
  • revision 记录的 ORM 访问被严格收口;
  • 普通有权限用户协作,是通过既定消息/协同入口进行,而不是开放底层 revision 表。

这说明官方把 revision 看成内部协同引擎的一部分,而不是共享 API。

如果把这层也公开出去,就会出现两个问题:

  1. 外部人可能绕过文档级约束直接碰 revision;
  2. 协作协议会暴露成低层存储接口,治理会失控。

八、实战里最容易误解的 4 件事

误区 1:有分享链接,就应该能在线共同编辑

在 Odoo 企业版里不是这样。

分享链接解决的是访问入口,不是开放协作编辑。

误区 2:给 portal 用户加个 edit 就行

不行。模型约束和测试都说明,portal / 非内部用户不能拿 spreadsheet edit。

误区 3:冻结副本只是 UI 上的“另存为”

也不是。它会重建对象类型、切换文件夹、降级权限、保存 Excel 导出,本质上是发布工件化。

误区 4:文档可读,revision 也应该能看

官方明确没这么做。协作底层记录不是普通共享资产。


九、这套设计对实施和二开的启发

如果你在做企业内部表格协同,documents_spreadsheet 最值得借鉴的是这几条:

  1. 把在线协作态和对外发布态拆成两类对象
  2. 对外分享优先走只读快照,而不是继续开放主对象编辑
  3. 公开 token 只解决入口,不提升写权限等级
  4. 协作底层 revision 要和普通文档权限分层治理
  5. 涉及 live data 时,宁可拒绝公开渲染,也不要误把内部数据视图暴露出去。

很多系统失败,就是因为把“内部共创文档”和“外部可分享文件”当成同一件事。

Odoo 企业版在这里的态度反而很克制:

真正允许多人实时改的,是内部协作表;真正允许公开带走的,是冻结后的只读副本。

这条边界一旦想清楚,很多权限设计就顺了。


源码依据

  • enterprise/documents_spreadsheet/models/documents_document.py
  • enterprise/documents_spreadsheet/models/documents_access.py
  • enterprise/documents_spreadsheet/controllers/documents.py
  • enterprise/documents_spreadsheet/models/spreadsheet_contributor.py
  • enterprise/documents_spreadsheet/tests/test_spreadsheet_share.py
  • enterprise/documents_spreadsheet/tests/test_spreadsheet_documents_sharing.py
  • enterprise/documents_spreadsheet/tests/test_spreadsheet_collaborative.py

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。