其他深度

Odoo 问卷为什么不能只靠一个公开链接:access token、deadline 与 retry 控制链路讲透

很多人把 Odoo Survey 理解成“发个链接让人填完就行”,但从 survey.controllers.main 和 survey_user_input.py 来看,真正决定问卷是否可答、能答几次、能否继续上次进度的,是 answer token、invite token、deadline、cookie 和 attempts 这一整套会话控制机制。搞懂这层,问卷才能从表单工具升级成可靠的考试与邀请系统。

其他
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo 问卷最容易被低估的,不是题型,而是它对“谁可以答、什么时候还能答、失败后还能不能再来一次”这件事控制得非常细。

/home/ubuntu/odoo-temp/addons/survey/controllers/main.pymodels/survey_user_input.py 看,Survey 真正的骨架是:

  • survey.survey:定义问卷
  • survey.user_input:定义一次具体作答
  • access_token / invite_token:定义访问身份
  • deadline / attempts_limit:定义时间与次数边界

所以 Survey 本质上不是“公开表单”,而是一个带会话和权限约束的答题系统

第一层:为什么问卷访问判断要分 survey token 和 answer token

控制器里最关键的入口之一是 _fetch_from_access_token()_get_access_data()

Odoo 会同时区分两种 token:

  • survey_token:定位哪份问卷
  • answer_token:定位某个人的那次答卷

这两个 token 分开,意义非常大。

因为现实里有两种完全不同的访问场景:

场景一:公开问卷

你只需要知道是哪份问卷,系统可以现场生成一条新的 survey.user_input

场景二:被邀请答题 / 继续作答 / 打开旧答卷

这时你不能只知道问卷本身,还得知道“你对应的是哪一次答卷”。

如果把这两个概念混在一起,系统就很难同时支持:

  • 公开访问
  • 定向邀请
  • 作答中断后恢复
  • 重试时延续同一邀请上下文

第二层:为什么 Odoo 要把 validity_code 做成一整套业务判断,而不是一个布尔值

_check_validity() 返回的不是简单 true/false,而是像下面这样的业务码:

  • survey_wrong
  • survey_auth
  • survey_closed
  • survey_void
  • token_wrong
  • token_required
  • answer_deadline
  • answer_wrong_user

这说明 Odoo 不只是想判断“能不能进”,而是想知道为什么不能进

这在问卷场景里特别重要,因为“不能继续答”背后的含义完全不同:

  • 问卷不存在
  • 需要登录
  • 问卷已关闭
  • 链接错了
  • 这个答案链接过期了
  • 当前登录人与答卷绑定人不匹配

如果这些情况都只显示成一句“访问失败”,用户体验和排障体验都会很差。

所以官方在控制器层把错误语义拆细,是很成熟的设计。

survey_start() 里有个很容易被忽略的细节:

  • 如果 URL 里没给 answer_token
  • 系统会尝试读取 cookie 中的 survey_<survey_token>

这意味着 Odoo 把“继续答上次没写完的问卷”视为默认需要支持的用户行为。

这很现实。

因为真实用户经常会:

  • 刷新页面
  • 关掉浏览器再回来
  • 从邮件点进问卷后中途离开

如果没有 cookie 恢复机制,用户就很容易多生成几条 user_input,后台也会多出很多半截答卷。

更妙的是,Odoo 还会在 cookie 对应的 token 已失效、或与当前登录用户不匹配时重新回退判断,而不是盲目相信 cookie。也就是说,系统在“方便恢复”和“避免串号”之间做了平衡。

第四层:为什么 deadline 是挂在 user_input 上,而不是 survey 上

survey.user_input 有一个非常关键的字段:deadline

控制器里判断访问合法性时,检查的是:

  • 当前这条 answer 有没有 deadline
  • deadline 是否已经早于现在

这代表 Odoo 的设计不是“整份问卷统一截止”,而是允许每次答卷拥有自己的截止时间

这个设计非常有价值,因为很多企业问卷根本不是全员同一规则:

  • 某批学员三天内完成
  • 某个候选人 24 小时内完成
  • 某次补考沿用原 invite token 但保留原截止时间

如果 deadline 只在问卷主表上,你几乎做不了这些差异化控制。

第五层:retry 为什么要保留 invite_token、deadline 和 nickname

survey_retry() 会在允许重试时创建一个新的 survey.user_input,但又不会彻底从头来过。

因为 _prepare_retry_additional_values() 明确保留了:

  • deadline
  • nickname

再加上创建时还会继续带上:

  • partner
  • email
  • invite_token
  • test_entry

这说明 Odoo 对“重试”的理解不是重新生成一个毫无关系的答卷,而是:

在同一邀请上下文、同一用户身份、同一时间边界下,再开一次新的尝试。

这特别适合认证考试和培训测验。

因为你通常想允许用户重答,但并不想:

  • 重答后绕开原来的截止时间
  • 失去邀请批次的追踪关系
  • 把排行榜昵称、参与者身份搞丢

第六层:attempts 统计为什么要同时看 partner、email 和 invite token

_compute_attempts_info() 不是简单按 user_input 数量来计数。

它会关注:

  • 问卷是否开启 attempts limit
  • 当前答卷是否已 done
  • 是否是 test_entry
  • partner_idemail
  • invite_token

这里最精妙的一点是:

  • 同一个联系人 / 邮箱,才可能算同一个人
  • 但如果有 invite token,还要结合 invite token 看是不是同一池次

这意味着 Odoo 默认接受这样一种业务现实:

  • 同一个邮箱可能来自不同邀请批次
  • 同一个人也可能在不同上下文里拥有不同尝试池

所以 attempts 不是前端按钮逻辑,而是后台会话识别逻辑。

第七层:打印答案为什么也要走 token,而不是后台直接打开记录

action_print_answers() 生成的是一个带 answer_token 的网站 URL。

这说明在 Odoo 里,“查看某次答卷”依然被当成 token 驱动的受控访问,而不是纯后台字段展示。

这个思路很统一:

  • 答卷对象可以被邀请、被恢复、被重试
  • 也可以被打印、被访问
  • 这些行为都围绕同一条 user_input 展开

这样一来,前后端的访问语义是一致的。

最容易误解的三个点

误区一:Survey 只是发一个公开链接

不是。 公开链接只是其中一种模式,完整系统还支持邀请式、恢复式、限次式访问。

误区二:截止时间只需要设在问卷上

不够。 很多真实场景需要每次答卷自己的 deadline。

误区三:retry 等于重新开始

也不是。 Odoo 的 retry 更像“保留身份和边界条件,再开新一轮尝试”。

实施和扩展时怎么用最稳

如果你在做 Survey 项目,我很建议把这几条当成默认原则:

  1. 需要精确控制对象时,优先走 answer token,而不是只发 survey 公链
  2. 需要补考、重试时,先想清楚要不要沿用 invite token 与原 deadline
  3. 不要只在前端限制 attempts,真正可信的边界在 survey.user_input
  4. 用户反馈“明明点过链接却进不去”时,优先排查 validity_code 对应的具体原因
  5. 对登录用户、公开用户、邀请用户三类流程分别做回归测试

最后总结

Odoo Survey 真正厉害的地方,不在题型数量,而在它把问卷访问设计成了一套可控会话:

  • survey token 负责找到问卷
  • answer token 负责找到这次答卷
  • cookie 负责恢复现场
  • deadline 负责时间边界
  • invite token 与 attempts 负责次数边界

理解这条链之后,你就会发现 Odoo Survey 离“考试系统”和“邀请式测评系统”其实只差配置,不差底层骨架。

DISCUSSION

评论区

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