愿望清单

Odoo 商品愿望清单为什么不是“点个爱心”这么简单:session 归属、重复合并与价格记忆链路讲透

很多人把 Odoo 的 wishlist 当成前端小收藏,但 website_sale_wishlist 实际同时处理匿名 session、登录后归属迁移、重复商品去重、失效商品过滤和加入当下价格快照。

网站
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo 电商里的愿望清单,不是前端页面上多放一个“收藏”按钮而已。

website_sale_wishlist/models/product_wishlist.py 和对应 controller 来看,它真正解决的是一条很现实的业务链:

  • 游客没登录时,收藏先挂在 session;
  • 登录后,要把匿名收藏迁到真实客户;
  • 同一个商品不能在同一用户名下无限重复;
  • 已下架或不可加购的商品,前台要自动过滤;
  • 收藏时的价格还要留一份快照,方便后续比较。

所以 wishlist 的重点从来不只是“记住用户喜欢什么”,而是:

在匿名浏览、登录转化和商品状态变化之间,尽量保住一份能继续运营的兴趣清单。


一、为什么 Odoo 要单独建 product.wishlist

很多人第一反应是:

  • 既然已经有购物车,为什么不直接在 cart 里加个“收藏”状态?

Odoo 没这么做,而是单独建了 product.wishlist 模型,并且保存:

  • partner_id
  • product_id
  • website_id
  • pricelist_id
  • price
  • active

这说明 wishlist 在产品设计上不是“待加入购物车的行”,而是另一类对象:

  • 它和购物车都围绕商品;
  • 但购物车偏向交易准备;
  • 愿望清单偏向长期兴趣沉淀。

一旦把两者混为一谈,后面关于归属、去重、过期清理的逻辑就都会乱掉。


二、为什么匿名用户的愿望单先挂在 session,而不是强迫登录

current() 方法非常关键。

如果当前网站用户还是 public user,系统不会去按 partner_id 查,而是直接从:

  • request.session['wishlist_ids']

里拿当前 session 关联的 wishlist 记录。

这背后的产品思路很合理:

  1. 网站浏览阶段要尽量低摩擦;
  2. 用户刚开始逛时,未必愿意立刻注册;
  3. 但商家又希望先把“感兴趣的商品”留住。

所以 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_id
  • currency_id
  • price

一起写进去。

很多人会问:

  • 既然商品价格以后还能重新算,为什么还要存一份旧价格?

因为 wishlist 的一个核心体验,恰恰是“我当时看上它时是什么价格”。

这份快照有几个价值:

  1. 后续做降价提醒时有比较基线;
  2. 不同 pricelist 用户不会完全混成同一口径;
  3. 运营可以判断“用户收藏时看到的价”和“今天看到的价”是否变化。

也就是说,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

评论区

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