先说结论
Odoo 电商里的愿望清单,不是前端页面上多放一个“收藏”按钮而已。
从 website_sale_wishlist/models/product_wishlist.py 和对应 controller 来看,它真正解决的是一条很现实的业务链:
- 游客没登录时,收藏先挂在 session;
- 登录后,要把匿名收藏迁到真实客户;
- 同一个商品不能在同一用户名下无限重复;
- 已下架或不可加购的商品,前台要自动过滤;
- 收藏时的价格还要留一份快照,方便后续比较。
所以 wishlist 的重点从来不只是“记住用户喜欢什么”,而是:
在匿名浏览、登录转化和商品状态变化之间,尽量保住一份能继续运营的兴趣清单。
一、为什么 Odoo 要单独建 product.wishlist
很多人第一反应是:
- 既然已经有购物车,为什么不直接在 cart 里加个“收藏”状态?
Odoo 没这么做,而是单独建了 product.wishlist 模型,并且保存:
partner_idproduct_idwebsite_idpricelist_idpriceactive
这说明 wishlist 在产品设计上不是“待加入购物车的行”,而是另一类对象:
- 它和购物车都围绕商品;
- 但购物车偏向交易准备;
- 愿望清单偏向长期兴趣沉淀。
一旦把两者混为一谈,后面关于归属、去重、过期清理的逻辑就都会乱掉。
二、为什么匿名用户的愿望单先挂在 session,而不是强迫登录
current() 方法非常关键。
如果当前网站用户还是 public user,系统不会去按 partner_id 查,而是直接从:
request.session['wishlist_ids']
里拿当前 session 关联的 wishlist 记录。
这背后的产品思路很合理:
- 网站浏览阶段要尽量低摩擦;
- 用户刚开始逛时,未必愿意立刻注册;
- 但商家又希望先把“感兴趣的商品”留住。
所以 Odoo 并没有把 wishlist 设计成“必须登录才可用”的功能,而是允许:
- 先收藏;
- 后识别身份;
- 再做归属迁移。
这比很多一上来就弹登录框的做法友好多了。
三、登录后为什么要做一次“愿望单归并”
真正体现源码思路的,是 _check_wishlist_from_session()。
这个方法会在用户从匿名状态变成已登录状态后,把 session 里的 wishlist 和该 partner 现有 wishlist 做一次合并。
合并时它做了两件很重要的事:
1. 先找重复商品
源码会把用户原来已经有的 partner_wishes 里的 product_id 抽出来,再检查 session 里哪些商品已经存在。
2. 重复的直接删除,不重复的改归属
- 重复项
unlink(); - 剩余项
write({'partner_id': 当前用户伙伴}); - 最后把 session 里的
wishlist_ids清掉。
这说明 Odoo 很明确:
登录不是“多拿到一份清单”,而是“把匿名兴趣并入正式客户画像”。
如果不这么处理,用户会看到:
- 登录前收藏一份;
- 登录后老账号里又有一份;
- 同一商品出现两次。
那 wishlist 很快就会失去可信度。
四、为什么系统还要记录“加入当时的价格”
_add_to_wishlist() 除了存商品本身,还会把:
pricelist_idcurrency_idprice
一起写进去。
很多人会问:
- 既然商品价格以后还能重新算,为什么还要存一份旧价格?
因为 wishlist 的一个核心体验,恰恰是“我当时看上它时是什么价格”。
这份快照有几个价值:
- 后续做降价提醒时有比较基线;
- 不同 pricelist 用户不会完全混成同一口径;
- 运营可以判断“用户收藏时看到的价”和“今天看到的价”是否变化。
也就是说,wishlist 保存的不是纯商品 ID,而是:
- 某个网站用户在某个价格语境下,对某个商品表达过兴趣。
五、为什么前台显示前还要再过滤一遍
current() 最后并不是直接把记录吐给前台,而是又过滤了一次:
- 商品模板必须
website_published; - 商品还得
_is_add_to_cart_possible()。
这一步很重要。
因为用户收藏的商品,过几天可能已经:
- 下架;
- 切站点了;
- 不允许加购;
- 或者变成前台不该再曝光的状态。
如果系统机械地把旧记录全展示出来,用户体验会很差:
- 点进去发现买不了;
- 收藏页堆一堆失效商品;
- 甚至引出“为什么后台删了前台还在”的误解。
所以 Odoo 的策略是:
wishlist 可以保存历史兴趣,但前台列表只展示当前仍然有效的兴趣。
六、为什么还要做垃圾回收
_gc_sessions() 是另一个很容易被忽略的细节。
它会清理:
- 创建时间过旧;
partner_id为空;- 也就是只属于匿名 session 的 wishlist。
默认逻辑按周数回收,避免长期堆积无主记录。
这说明 Odoo 很清楚匿名 session 的价值和边界:
- 短期内,它能提高转化;
- 长期不认领,它就不该无限留在数据库里。
否则愿望单表最终会变成一个充满“几周前随手点过爱心的匿名痕迹”的垃圾场。
七、实施时最容易误解的地方
1. 以为 wishlist 只是前端状态
不对。它是有独立模型、有归属迁移、有清理策略的数据对象。
2. 以为登录后就是简单读取 partner 的收藏
不对。还要先把 session 里的匿名收藏并过去。
3. 以为收藏页应该无条件展示所有旧记录
不对。源码会把已下架或不可加购的商品过滤掉。
4. 以为价格可以永远实时重算,不必存快照
不对。收藏行为本身就有发生时点,快照才方便后续比较和运营。
最后总结
Odoo 的 website_sale_wishlist 真正做的,不是“网页上多个爱心图标”,而是:
- 允许匿名用户先表达兴趣;
- 在登录后把兴趣迁到正式客户身份;
- 避免同商品重复堆积;
- 对外只显示仍有效的商品;
- 用价格快照把“当时为什么感兴趣”也一并保存下来。
所以更准确地说,它是一套:
围绕匿名到实名转化设计的商品兴趣沉淀机制。
DISCUSSION
评论区