先说结论
Odoo 网站表单最容易被误解的一点是:
- 前台上看,很多表单都像“填写后提交一下”;
- 但后台里,它们并不是进入同一个收件箱。
从 website/controllers/form.py 与 website/models/website_form.py 看,决定提交结果的核心不是页面长相,而是:
- 这个 snippet form 绑定的是哪个模型;
- 这个模型有没有
website_form_access; - 哪些字段被白名单放开;
- 自定义字段和元数据该落到哪个默认文本字段;
- 目标模型有没有自己的
website_form_input_filter()再加工。
所以正确理解应该是:
网站表单是一条“按目标模型路由”的受限录入管道,而不是一个统一留言箱。
也正因为如此,看起来几乎一样的两个网站表单,提交后完全可能走向不同业务:
- 一个进 CRM lead;
- 一个进 helpdesk ticket;
- 另一个只是把补充说明写进某个文本字段或 chatter。
一、为什么 snippet form 的“选模型”比页面样式更重要
控制器真正接收请求的路径是:
/website/form/<string:model_name>
也就是说,后端第一眼看到的不是“这是联系我们页”还是“这是售后页”,而是:
- 你到底想往哪个模型投递。
_handle_website_form() 会先查 ir.model,只允许:
website_form_access = True
的模型接收这次提交。
这说明前台 snippet form 的本质不是一个通用 HTML 表单,而是:
- 一个预先绑定业务模型的提交入口。
因此,所谓“改一个字段标签就能把联系表单改成工单入口”,这类想法通常都不成立。真正决定路由的是模型,而不是文案。
二、为什么 lead 和 helpdesk 的差异,首先是模型语义差异
如果一个表单落到 crm.lead,系统默认会把它理解成:
- 线索;
- 商机前置;
- 营销 / 销售跟进入口。
如果落到 helpdesk.ticket,业务语义则会更偏向:
- 服务请求;
- 故障支持;
- 工单处理流。
前台用户也许只觉得“我提交了一张表单”,但后端系统看到的是两条完全不同的运营链路:
- 分配规则不同;
- SLA 不同;
- 通知对象不同;
- 后续看板和报表也不同。
所以网站表单路由最重要的第一步,不是字段设计,而是先定:
这条入口到底想创建什么业务对象。
三、为什么同样的字段名,不一定会走进同样的业务字段
_get_form_writable_fields() 和 website_form_blacklisted 共同决定了:
- 哪些字段真能被网站写入;
- 哪些字段即使在页面上出现,也不一定进入 ORM create。
而 extract_data() 又会按字段类型做转换。
这带来一个很现实的实施结论:
- 前端上出现了
email、subject、description,不代表后台模型一定按你预想接收; - 不同模型即使字段名相似,也可能因为白名单、必填、默认值和业务过滤,走出完全不同结果。
所以“复制一个 snippet form,再把 action 换掉”经常不够。
因为目标模型一变:
- 可写字段集合可能变了;
- 默认字段语义可能变了;
- 业务过滤器也可能变了。
四、为什么 default field 决定了“看不懂的字段最后去哪儿”
ir.model 上有个很关键的配置:
website_form_default_field_id
它的作用是:
- 把白名单之外但又需要保留的自定义输入、元数据等,汇总进某个文本字段。
这非常重要,因为在真实业务表单里,前台常会出现两类输入:
- 真正驱动流程的结构化字段;
- 只是补充说明、活动上下文、页面来源等杂项信息。
如果没有 default field,这些杂项要么丢失,要么只能进 chatter。
而一旦目标模型切换成 lead 或 ticket,default field 的位置和意义也可能随之改变。
这就是为什么两个都“提交成功”的表单,后台记录可读性却差很多——不是前台差异,而是落点设计不同。
五、为什么 website_form_input_filter() 是真正的业务分流口
控制器在提取完字段后,还会检查目标模型有没有实现:
website_form_input_filter(request, values)
这是网站表单最值得重视的一道扩展口。
因为它允许模型自己决定:
- 怎么把前台字段重组;
- 怎么补默认标题;
- 怎么把来源页、营销参数、组合字段变成更符合业务的数据。
对 CRM lead 来说,它常常意味着:
- 把零散联系信息拼成更适合销售跟进的 record。
对 helpdesk ticket 来说,它更可能意味着:
- 把用户问题整理成工单标题、描述或分类所需结构。
所以同样一份前台提交,真正把它变成“线索”还是“工单”的,不只是在模型名那一步,也在 input filter 这一步。
六、为什么附件和最后一条记录会影响感谢页与后续动作
表单成功后,控制器还会:
- 给目标记录插入附件;
- 把
form_builder_model_model和form_builder_id记到 session; - 让网站能通过
_website_form_last_record()取回最近一次创建对象。
这意味着网站表单不仅创建记录,还会影响:
- 上传文件最后挂在哪个业务对象上;
- 感谢页能不能显示刚刚创建的结果;
- 后续模板页面是否能引用上一条提交。
因此 lead 和 helpdesk 路由差异,不只是后台看板不同,连前台提交后的体验都可能不同。
最后给实施方的 5 个提醒
1. 先定目标模型,再设计前台字段
别反过来做。
2. 白名单字段要按目标模型重新核对
复制旧表单最容易在这一步翻车。
3. 把 default field 当成可读性保险
别让补充信息悄悄消失。
4. 业务路由尽量放进 input filter,而不是堆前端 JS
后端语义更稳定,也更可审计。
5. 附件、感谢页和最近记录逻辑一起测试
别只测“能提交成功”。
参考源码
addons/website/controllers/form.pyaddons/website/models/website_form.py
DISCUSSION
评论区