解析流程

每个页面请求都会走同一条管线:从多个 提供者(provider) 收集元数据,按优先级顺序合并各层,再把结果定型为可渲染的对象。理解这个模型后,其他所有功能都会更容易理解。


优先级阶梯

提供者按优先级从低到高依次合并。对每个字段而言,优先级高的提供者胜出——但 JSON-LD 和额外的 head 片段例外,它们会从所有层 累加。

优先级 来源 典型用途
10 SEO_SUITE["DEFAULTS"] 全站兜底的标题后缀、robots、og:type
20 SEO_SUITE["SITE_DEFAULTS"][site_id] 多站点部署中按站点设置的默认值
30 匹配请求路径的 SeoPath 记录 针对特定 URL、可在后台编辑的覆盖项
40 对象的 get_seo_metadata() 或 SeoObject 记录 模型字段,或为第三方模型设置的后台覆盖项
50 视图的 get_seo_metadata() / seo_* 属性 视图级的覆盖与增强

三态字段:UNSET、None 与具体值

每个元数据字段都处于以下三种状态之一:

  • UNSET:表示“这个提供者不表态”。解析器会跳过这一层,沿用更低优先级提供者给出的值。
  • None 或 "":表示“明确为空”。它会覆盖更低优先级的值,且不渲染任何内容。当你想抑制某个低层本来会输出的字段时(例如某个页面不应有 meta description),就用这种方式。
  • 字符串值:原样输出。

实际使用中你很少需要直接考虑这些。Mixin 和基于数据库列的提供者在模型字段为空时会把字段设为 UNSET,因此低优先级的值自然会得到保留。


字段合并规则

字段类型 合并行为
标量(title、meta_description、robots、canonical_url 等) 高优先级胜出;UNSET 则让位
og 字典、twitter 字典 逐键深度合并;每个键由高优先级胜出
hreflang 列表 高层提供列表时整体替换
jsonld 列表、extra_head 列表 累加:所有层都参与贡献

JSON-LD 的累加规则是有意为之:视图添加的 BreadcrumbList 结构可以与模型添加的 Article 结构并存,页面会把两个 <script type="application/ld+json"> 块都输出。


og / twitter 字段的自动填充

合并完成后,定型器会用解析出的页面级元数据补齐 og 和 twitter 字典中的空缺:

  • og:title ← title(若未设置 og:title)
  • og:description ← meta_description(若未设置)
  • og:url ← canonical_url(若未设置)
  • og:image ← og_image(若未设置)
  • twitter:* 字段镜像对应的 og:* 字段
  • twitter:card 在有图片时默认为 "summary_large_image",否则为 "summary"

在任意一层显式设置 og 或 twitter 字典,即可覆盖上述任何一项。


一个具体例子

假设 settings 中定义了标题后缀和全局 robots,一条 SeoPath 记录为 /about/ 设置了自定义标题,视图又设置了描述:

Layer 10 (DEFAULTS):  title_suffix=" | Acme",  robots="index,follow"
Layer 30 (SeoPath):   title="About Acme"
Layer 50 (View):      meta_description="We build great things."

合并结果:

title         = "About Acme"      (from layer 30)
title_suffix  = " | Acme"        (from layer 10)
meta_description = "We build great things."  (from layer 50)
robots        = "index,follow"    (from layer 10)

渲染出的 title 标签:<title>About Acme | Acme</title>


解析器做了什么

解析器每个请求只调用一次(结果缓存在 request 对象上)。它会:

  1. 按优先级顺序收集所有层。
  2. 用 SeoMetadata.merge_all 合并。
  3. 根据请求的 host 或 CANONICAL_DOMAIN 把 canonical URL 转为绝对地址。
  4. 返回一个 FinalizedSeoMetadata 对象。其中不再有 UNSET 哨兵值,所有字段都可直接渲染。

如果 SEO_SUITE["CACHE_TTL"] 大于 0,定型后的结果会存入 Django 缓存,键为 site:language:app.model:pk:generation。模型保存或删除时 generation 计数器会自动递增,因此永远不会返回过期的缓存条目。


在代码中读取解析结果

如果你需要在模板之外查看解析后的元数据(例如在 REST 接口或管理命令中),直接使用 resolve_seo:

from seo_suite.context import resolve_seo

def my_view(request):
    seo = resolve_seo(request)
    print(seo.title)
    print(seo.canonical_url)
    print(seo.jsonld)

对于最常见的情况——已经使用 SeoViewMixin 的类视图——在 get_context_data 运行后,元数据已经存在于 context["seo"] 和 request.seo 中。