扩展 django-seo-suite

django-seo-suite 提供了稳定的扩展点,第三方包可以借此增加能力(自定义 Provider、额外的 <head> 渲染器、新的 Schema Profile),而无需 fork 代码或做 monkey-patch。

本页面向包作者,简要梳理这些扩展点。如果你的项目只是需要一些自定义行为,通常通过覆写 get_seo_metadata、覆盖模板,或监听 seo_head_rendering 信号就能做到,不必单独写一个包。


扩展发现

任何已安装的 Django 应用,只要在顶层带有 seo_extensions.py 模块,该模块就会在启动时被自动导入(通过 AppConfig.ready)。推荐用这种方式注册 Provider、渲染器或 Schema Profile:

# mypackage/seo_extensions.py

from seo_suite.extension import (
    Provider,
    SeoMetadata,
    provider_registry,
    PRECEDENCE_PATH,
)


class MyCustomProvider(Provider):
    priority = 35  # between PATH (30) and OBJECT (40)

    def provide(self, context):
        # context has: request, view, object, path, site_id, language
        if context.path and context.path.startswith("/promo/"):
            return SeoMetadata.partial(robots="noindex")
        return None


provider_registry.register(MyCustomProvider())

另一种方式是在包的 pyproject.toml 中,于 seo_suite.extensions 组下发布一个 entry point:

[project.entry-points."seo_suite.extensions"]
mypackage = "mypackage.seo_extensions"

两种机制都是幂等的,在 Django 的 autoreload 下运行也没有问题。


注册表

Provider 注册表。 收集每次解析时要查询的 Provider:

from seo_suite.extension import provider_registry, Provider, SeoMetadata

provider_registry.register(my_provider)
provider_registry.unregister("mypackage.providers.MyProvider")

渲染器注册表。 收集在渲染阶段贡献额外 <head> 片段的可调用对象。每个渲染器接收 (request, finalized_metadata),返回字符串或 None:

from seo_suite.extension import renderer_registry

def my_renderer(request, metadata):
    if metadata and metadata.og.get("type") == "article":
        return '<meta name="article:author" content="...">'
    return None

renderer_registry.register("mypackage.my_renderer", my_renderer)

Schema 注册表。 以字符串为键收集 JSON-LD Profile:

from seo_suite.schema.registry import schema_registry, SchemaProfile

class MyProfile(SchemaProfile):
    key = "mypkg:CustomType"

    def build(self, obj, context):
        return {"@context": "https://schema.org", "@type": "CustomType", "name": "..."}

schema_registry.register(MyProfile())

键名请加命名空间(例如 "mypkg:CustomType"),避免与免费版自带的 Profile 冲突。


可替换的类

设置项 控制内容
SEO_SUITE["RESOLVER_CLASS"] 负责收集并合并各 Provider 层的类。继承 seo_suite.resolver.BaseResolver,覆写 collect_layers、build_canonicalizer 或 finalize。
SEO_SUITE["JSONLD_SERIALIZER"] 把 JSON-LD 字典序列化为字符串的可调用对象。

信号

信号 触发时机 可以做什么
seo_metadata_resolved 解析器完成元数据的最终处理之后 就地修改 metadata(这是唯一明确支持修改的信号)
seo_head_rendering 每次渲染 {% seo_head %} 时 返回额外的 <head> HTML 字符串
seo_cache_invalidate 收到清除缓存条目的请求时 发送该信号以失效某个特定对象的缓存

从 seo_suite.extension 导入:

from seo_suite.extension import seo_metadata_resolved, seo_head_rendering, seo_cache_invalidate

供 contrib 应用使用的抽象模型

如果你的包要添加自己的、基于数据库的 Provider,可以直接复用这些抽象基类:

from seo_suite.contrib.base import AbstractSeoColumns
from seo_suite.contrib.seopath.models import AbstractSeoPath
from seo_suite.contrib.seoobject.models import AbstractSeoObject

SeoColumnsMixin(只包含字段、不带 get_seo_metadata 的 mixin)也可以从 seo_suite.providers.model_mixin 导入。如果你想让某个具体模型拥有标准字段集,又不想继承 SeoModelMixin,就用它。


优先级常量

要把 Provider 注册到优先级阶梯的特定位置,请使用这些公开常量:

from seo_suite.extension import (
    PRECEDENCE_GLOBAL,   # 10
    PRECEDENCE_SITE,     # 20
    PRECEDENCE_PATH,     # 30
    PRECEDENCE_OBJECT,   # 40
    PRECEDENCE_VIEW,     # 50
)

priority 可以是任意整数。取公开常量之间的值(例如 35),即可插入现有层之间,而基础包无需重新编号。