业务源码

Odoo 问卷邀请为什么不是“群发一个链接”而已:收件人识别、重复邀请与 token 链路讲透

Odoo 的 Survey Invite 向导看起来只是填几个邮箱然后发送,但源码里实际处理了 access mode、partner 识别、外部参与资格、重复邀请复用、answer token 生成、模板渲染和语言上下文。本文把 survey.invite 到 survey.user_input 的主链路一次讲清。

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

先说结论

Odoo 里的问卷邀请,不是“生成一个公开链接,然后群发出去”那么简单。

/home/ubuntu/odoo-temp/addons/survey/wizard/survey_invite.pymodels/survey_survey.py 看,真正发生的链路是:

  1. 向导先根据 survey access mode 判断可不可以走邮件邀请
  2. 再把 partner 与手填 email 做一轮归并
  3. 已存在的答卷对象要区分是重发还是新建
  4. 对每个有效接收者创建或复用 survey.user_input
  5. 每个答卷对象再拿到自己的 answer_token / invite_token 语义
  6. 最后模板按语言上下文渲染出真正发送的邮件

所以 Survey Invite 本质上是一个“收件人解析 + 答卷实例生成 + 按对象渲染邮件”流程,不是单纯 mail merge。


第一层:send_email 为什么不是随便都能勾

向导里 send_email 不是自由布尔值,而是由 _compute_send_email() 根据 survey_access_mode 算出来:

  • survey_access_mode == 'token' 时,默认走邮件邀请

这已经说明一个设计原则:

真正适合被“定向邀请”的,是 token 型问卷,而不是所有问卷。

如果问卷本来是 public 访问,系统并不强调“给每个人单独发一个受控入口”;但 token 模式就不同了,每个受邀者最好对应一个明确的答卷对象。


第二层:为什么外部邮箱有时一输就报错

_onchange_emails()_onchange_partner_ids() 都在做资格校验。

典型限制包括:

  • 如果问卷要求登录,且不允许 signup,就不能随便填外部邮箱
  • 如果选择了 partner,但这些 partner 没有关联 user account,同样可能被拒

这里背后的业务边界非常明确:

  • authentication / internal 这类问卷,不是简单地“谁有链接谁都能答”
  • 邀请向导必须先替你把访问资格守住

所以很多现场报错并不是邮箱格式问题,而是:

  • 这个问卷压根不允许外部参与
  • 或者当前参与者没有账号,不满足登录约束

第三层:Odoo 为什么要先把 email 反查成 partner

action_invite() 里一个很值得注意的动作是:

  • 对手填邮箱先做 email_normalize
  • 再去 res.partner 里搜索相同邮箱
  • 如果找到了 partner,就把它并入 valid_partners
  • 找不到的,才留在 valid_emails

这背后的意义不只是“数据更整洁”,而是尽量把邀请对象绑定回业务主体

一旦绑定成 partner,后续你就能更稳定地拿到:

  • 语言
  • 姓名
  • 历史邀请记录
  • 已有答卷关联

这比把所有人都当匿名邮箱强得多。


第四层:为什么重复邀请不会无限造新 token

_prepare_answers() 会先查 survey.user_input

  • 同一个 survey 下
  • partner_id 命中,或 email 命中

然后 _get_done_partners_emails() 再根据 existing_mode 决定怎么处理。

如果 existing_mode == 'resend'

  • 不会为同一个对象无限新建答卷
  • 而是挑该对象最新的一条 existing answer 来发

这个设计很实用,因为它避免了两个问题:

1. 一个客户被你重复邀请 5 次,就出现 5 个平行答卷对象

后续统计和追踪会很乱。

2. 同一个人每次重发都换链接

这样客户不知道该用哪个,内部也很难追踪“到底是哪个邀请完成了”。

所以默认重发模式本质上是在说:

重发是提醒,不是重新开一条完全新的答卷人生。


第五层:真正的 token 是在哪一步生成的

答案在 survey.survey._create_answer()

这个方法会先做 _check_answer_creation(),确保:

  • 问卷还 active
  • access mode 与当前对象身份相符
  • attempts left 还没用完

然后才构造 survey.user_input

这里有两个容易混淆的概念:

1. answer_token

它属于答卷对象本身,最终问卷开始链接通常会带:

  • ?answer_token=...

2. invite_token

当问卷限制尝试次数,且访问模式不是 public 时,系统还可能生成单独的 invite_token 来约束邀请语义。

也就是说,发出去的不是“共享一个问卷地址”,而是每个接收者对应一个答卷身份入口

这正是 Odoo 能做:

  • 邀请级追踪
  • 对象级统计
  • attempts 控制
  • 已答 / 未答状态区分

的基础。


第六层:邮件模板为什么会因收件人不同而不同

向导会把 render_model 指到 survey.user_input,然后 _send_mail() 不是按 survey 渲染,而是按 answer 渲染。

这件事特别关键,因为只有这样模板里像 object.get_start_url() 这类表达式,才能拿到当前答卷对象自己的专属链接

此外,向导还会:

  • 根据 partner 语言上下文切换模板语言
  • 用 answer 级别渲染 subject / body
  • 把 attachment、layout 一起包进去

所以它不是“一个模板发给很多人”,而是“同一模板针对很多答卷对象逐个实例化”。


第七层:为什么有时明明写了邮箱,最后却提示没有有效接收者

action_invite() 的最后会检查:

  • valid_partners
  • valid_emails

如果两边都空,就直接报:Please enter at least one valid recipient.

出现这种情况,常见原因有:

  • 邮箱格式在 normalize 后无效
  • 问卷访问规则不允许这些对象
  • 你填的是重复值,但 existing 处理方式又没给出可发送对象
  • 手工输入内容被分号 / 换行拆开后,实际上没有留下合法邮箱

所以排错时不要只盯着“我明明填了东西”,而要看“填进去的东西最后有没有成功落到 partner 或 email 两类有效集合里”。


第八层:实施时的实用建议

1. 需要对象级追踪,就优先使用 token 模式邀请

这样每个受邀者都有独立答卷对象,后续统计最稳。

2. 有客户主数据时,尽量让邮箱命中 partner

不要把所有受访者都当匿名邮件地址。

3. 对重复提醒,默认用 resend 思路

除非你就是想重开一次独立作答机会,否则别轻易制造平行答卷。

4. 访问资格要和问卷模式一起设计

internal、authentication、public、token,这些模式决定的不是 UI 文案,而是整条邀请链能不能成立。


最后一句

Odoo Survey Invite 真正高明的地方,不是“会发邮件”,而是它把问卷邀请拆成了四件事:

  • 识别谁在被邀请
  • 判断他有没有资格被邀请
  • 为他创建或复用哪一个答卷对象
  • 再把这份对象级入口渲染成邮件发出去

所以你应该把它理解成:

邀请系统不是在发送链接,而是在发送“属于这个接收者的答卷入口”。

DISCUSSION

评论区

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