先说结论
marketing_card 不是一个“自动出图小工具”,而是一套把动态素材、分享入口、UTM 跟踪、点击跳转和结果回写连起来的传播闭环。
从 /home/ubuntu/odoo-temp/addons/marketing_card/models/card_campaign.py、card_card.py 与 card_template.py 看,Odoo 这里真正做的事情是:
- 给一个 campaign 绑定目标模型和目标链接
- 为 campaign 自动创建
link.tracker - 给每一条目标记录生成唯一 card
- 让每张 card 自带 preview / image / redirect 路径
- 用
shared与visited回写执行状态 - 用聚合计数把转化反馈回 campaign
这说明它的重点不是“图做出来没有”,而是:
一张图从被生成、被分享、被访问,到把人送进目标落地页,整个过程能不能追得回来。
第一层:为什么 campaign 创建时就先建 link.tracker
card.campaign.create() 非常关键。
它会在 campaign 建立的同时,先创建一个 link.tracker,并且把:
urltitlesource_idlabel
都一起准备好。
其中最重要的是两件事:
1. 没有 target_url 也会给默认 base_url
这表示 campaign 从诞生那一刻起,就被视作一个有明确跳转目标的传播动作,而不是纯设计稿。
2. 会尝试挂上 utm_source_marketing_card
也就是说,Odoo 不是等点击来了再想“要不要做归因”,而是在 campaign 一创建时就默认:
- 这是一种有来源标记的营销流量
这套设计非常像成熟营销系统的思路:
归因不是事后补表,而是事前埋点。
第二层:为什么 campaign 允许的模型被刻意限制
_get_model_selection() 并不是对所有模型开放,而是硬编码白名单:
res.partnerevent.trackevent.boothevent.registration
很多人看到这里会觉得“为什么不更通用”。
但这恰恰说明 Odoo 在刻意控制场景边界。
因为 marketing card 不是万能图片工厂,而是用于几类高度明确的传播场景:
- 邀请联系人转发
- 宣传活动议程和讲师内容
- 让展商传播自己参展
- 让报名人或参与对象帮忙扩散
场景越明确,动态字段、落地页、归因路径就越容易做稳定。
如果一开始就开放所有模型,模板和路径的复杂度会快速膨胀,最终变成谁也不敢维护的系统。
第三层:为什么 preview card 会被单独缓存和归档
_fetch_or_create_preview_card() 的行为很有意思:
- 找已有预览卡就更新图片并 archive
- 没有就创建一张 preview card
- 用
image_preview作为它的图像内容
这说明 preview 并不是一张临时截图,而是系统里一个明确对象。
它的价值在于:
1. 预览路径稳定
你可以有一个稳定的 preview URL 给内部审样或测试邮件用。
2. 预览与正式发放分离
preview card 可以 archive,避免它和正式分发对象混在一起。
3. 设计变更后可以重新渲染
不是只存模板文本,而是把预览结果作为资产对象对待。
这让营销卡的“发前确认”动作更可靠。
第四层:为什么卡片要对每条记录唯一,而不是一张图大家共用
card.card 上有唯一约束:
unique(campaign_id, res_id)
这意味着在同一个 campaign 里,每个目标对象只有一张唯一 card。
这背后的业务收益非常大。
因为一旦 card 和对象一一对应,你就可以稳定地拥有:
- 每个对象自己的分享地址
- 每个对象自己的 redirect 路径
- 每个对象自己的状态回写
举个活动场景:
- 一个 booth 一张 card
- 一个演讲 track 一张 card
- 一个 registration 一张 card
这样你就不只是“生成了一批海报”,而是在给每个传播对象分配可追踪的独立传播入口。
这和一张 JPEG 群发全员,完全不是一个层级。
第五层:为什么 redirect 路径比直接放 target_url 更重要
card.card 里有:
_get_card_url()_get_redirect_url()_get_path()
也就是说,一张卡片不只是有图片地址,还有自己的 redirect 地址。
这点非常关键。
如果所有人都直接跳原始 target_url,你很难回答:
- 用户到底是从哪张 card 点进去的
- 是哪个 booth、哪个报名对象、哪个传播对象带来的访问
- 哪个 campaign 下的哪类资产更有效
而有了中间 redirect 层后,系统就能在“用户到达目标页之前”留下一次可归因的路径。
这其实就是营销链路里最重要的一件事:
别只关心内容有没有被看到,更要关心它把人送到了哪里。
第六层:为什么 shared 和 visited 都值得统计
share_status 只有两个值:
sharedvisited
而 campaign 的统计逻辑会认为:
shared隐含也发生过访问visited代表至少有人点开或到达过
很多人会觉得这两个状态太粗。 但它其实抓住了传播闭环里最重要的两个门槛:
第一门槛:有没有人愿意拿去转发
这是 shared。
第二门槛:转发出去后有没有带来访问
这是 visited / click。
这比一堆花哨但解释困难的指标更实用。 因为大多数运营复盘最后真正要回答的,往往就是:
- 卡片有没有被人采用
- 采用之后有没有带访问
第七层:为什么设计变更会把 card 标记为 requires_sync
当 render 字段变化时,系统会把相关卡片打成 requires_sync = True,之后 _update_cards() 再批量重渲染。
这说明 Odoo 很清楚营销素材的一个现实问题:
- 模板会改
- 文案会改
- 目标记录的数据会变
- 旧图会过时
如果没有同步标记机制,系统很快就会出现:
- 页面里展示的是新文案
- 外发图却还是旧版本
而 requires_sync 的存在,让 card 不只是一次性导出的静态文件,而是可重新对齐 campaign 当前状态的派生资产。
第八层:为什么还要自动清理旧 card 图像
_gc_card() 会按配置清理 오래未更新的 card 图像。
这个设计说明 Odoo 默认认为:
- 社交平台会缓存图片
- 旧 card 不应该无限期占资源
它不是简单“删缓存省空间”,而是在假设传播资产具有生命周期。
这也提醒实施方一个现实问题:
营销卡不是永久主数据,它更像一批可再生成的短中期传播资产。
最后一句
marketing_card 最值得学的地方,不是它能批量做图,而是它把传播对象、UTM 来源、redirect 路径、状态回写和聚合统计都绑在了一起。
所以如果你把它只当海报工具,就会错过它真正的价值; 如果把它当成一条从卡片到落地页的可归因转化链,这套设计就会突然非常合理。
DISCUSSION
评论区