Wagtail API v3 实战指南:基于 Django Ninja 的读写内容 API(Wagtail 8.0 预览版)
【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail
本文基于 Wagtail 8.0 引入的 v3 API 预览文档,讲解这套基于 Django Ninja 与类型提示构建的新 API:如何在 Django 项目中挂载它、如何通过 OpenAPI 3.1 Schema 做类型发现、如何使用 limit/offset 分页,以及如何理解其 RFC 7807 风格的结构化错误响应。读完本文,你可以将 v3 API 作为 Wagtail 内容后端的读写接口接入前端、Headless 站点或自动化脚本,并对照仓库源码理解每个行为的底层实现。
什么是 Wagtail API v3
Wagtail 8.0 引入了新版 v3 API 的预览(preview)。与传统 v2 REST API 不同,v3 的核心技术特征是:
- 构建于 Django Ninja 之上:采用 Ninja 的 Router / Schema / 分页体系,而不是 v2 中手写的 view 与序列化器组合;
- 全面使用类型提示(type hints):请求与响应结构以 Pydantic Schema 声明,从而支持类型检查与自动文档生成;
- 导出 OpenAPI 3.1 Schema:客户端可以从
openapi.json获得完整的机器可读接口定义; - 声明式的按类型 Schema:每种内容类型(如各类 Page 模型)都有独立的 read/create/patch Schema,而不是所有模型共用一个宽泛的序列化器;
- 同时支持读取与写入的 CMS 操作:设计目标是覆盖 RFC 115 中描述的读写能力,大致对齐 Wagtail 后台管理界面所能完成的常见操作。
需要注意,v3 目前处于preview状态,官方建议挂载在/api/v3-preview/路径下,以表明其可能随后续版本调整。
快速开始:安装与挂载
1. 注册应用
首先把wagtail.api.v3加入 Django 项目的INSTALLED_APPS:
# settings.py INSTALLED_APPS = [ ... 'wagtail.api.v3', ... ]对应的应用配置类定义在 apps.py 中,应用标签为wagtailapi_v3;其ready()钩子会额外调用APIRichText.check_setting()校验富文本格式相关设置(详见下文“与 v2 共享的配置”)。
2. 挂载 URL
在项目的 URL 配置中注册 API。预览阶段推荐挂载到/api/v3-preview/:
# urls.py from wagtail.api.v3.urls import api urlpatterns = [ path("api/v3-preview/", api.urls), # You can also mount it at /api/v3/ if you prefer: # path("api/v3/", api.urls), ]从 urls.py 的模块文档字符串可以看到两点关键约束:
- 挂载方式与 v2 保持一致(
api.urls是一个可挂载的 URL 对象),保证 v2 用户的迁移成本低; - 所有 Router 必须在访问
api.urls之前注册到api实例上,否则 Django Ninja 会抛出ConfigError。Wagtail 内部已经在 api.py 中完成了 Router 注册:
# wagtail/api/v3/api.py(节选) api.add_router("/pages/", pages_router) api.add_router("/schema/", schema_router) api.add_router("/sites/", sites_router) api.add_router("/", whoami_router)也就是说,当前随包提供的路由包括页面(/pages/)、Schema 发现(/schema/)、站点(/sites/)以及根路径下的whoami端点。
3. 浏览生成的文档
挂载成功后,两个开箱即用的入口会自动暴露:
- 人类可读的文档仪表盘:
<API root>/docs/(例如/api/v3-preview/docs/); - 机器可读的 OpenAPI Schema:
<API root>/openapi.json。
这两个路由的定义可以直接在NinjaAPI实例参数中确认(api.py):
api = NinjaAPI( title="Wagtail API", version="3.0.0", description="Wagtail v3 read and write API", urls_namespace="wagtailapi_v3", docs_decorator=_gate_docs, openapi_url="/openapi.json", docs_url="/docs/", )OpenAPI Schema 与文档仪表盘默认是公开可访问的(包含匿名端点与认证端点的描述)。如果希望隐藏这两条路由,把设置WAGTAILAPI_DOCS_ENABLED设为False即可。其实现是一个简单的装饰器门控(api.py):
def _gate_docs(view): """Uses ``WAGTAILAPI_DOCS_ENABLED`` to 404 the OpenAPI schema / interactive docs.""" @wraps(view) def wrapper(request, *args, **kwargs): if not getattr(settings, "WAGTAILAPI_DOCS_ENABLED", True): raise Http404 return view(request, *args, **kwargs) return wrapper即:关闭后这两条路由会直接返回 404(而非 403),对探测者而言表现为“不存在”。
端点组成与按应用动态启用的机制
v3 API 暴露哪些端点,取决于项目中安装了哪些 Wagtail 应用:
- Pages 端点始终可用;
- Images、Documents、Snippets、Locales、Redirects 端点只有在对应应用位于
INSTALLED_APPS且相应模型完成注册时才会出现。
从源码结构看,这一动态发现机制由 registry.py 中的ContentTypeRegistry支撑:模块导入时会立即调用registry.register_defaults()注册所有页面模型,并为每个模型生成三个方向的 Pydantic Schema(read/create/patch):
# wagtail/api/v3/registry.py(节选) def register_defaults(self) -> None: """Register the content types shipped with Wagtail (currently ``pages``).""" from wagtail.models import get_page_models for model in get_page_models(): read_schema = read_generator.generate_schema(model, base_class=PageSchema) ... self.register(ContentTypeRegistration( name=model._meta.label, label=str(model._meta.verbose_name), read_schema=read_schema, create_schema=create_schema, patch_schema=patch_schema, ))/schema/路由消费这个注册表,让客户端先“发现”有哪些内容类型,再查询每种类型的 read/create/patch JSON Schema;某个方向没有 Schema 时,会返回占位符{"description": "Not available."}。注册时机上的一个细节值得注意:源码注释说明register_defaults()之所以放在模块导入时而不是AppConfig.ready()中执行,是因为 Router 模块在自身导入阶段就要读取注册内容来构建请求/响应 Schema,若依赖ready()会引入INSTALLED_APPS顺序的隐式依赖(见 registry.py)。
v3 覆盖的功能范围
文档明确列出了 v3 API 支持的 CMS 操作,大致对齐 Wagtail 后台管理界面:
- 页面(Pages),包括草稿(drafts)、修订(revisions)与页面操作(page actions);
- 站点(Sites)、语言(locales)与重定向(redirects);
- 图片(images)与文档(documents);
- 启用了 API 的 Snippets;
- 富文本,支持 HTML 与 Markdown 两种格式;
- StreamField 内容;
- Schema 发现与 OpenAPI 参考。
文档同时坦诚列出了当前尚未覆盖、期待社区反馈的方向:
- 工作流 / 审核操作(提交、批准、驳回等);
- Site settings 支持;
- 更精确的 StreamField block Schema;
- 复用该 API 的官方客户端库或 UI;
- v2 API 的弃用与最终移除计划;
- 官方 API 教程。
v3 的每个子主题在仓库文档中都有独立章节,可按需深入:
- 认证
- Pages 端点
- Images 端点
- Documents 端点
- Snippets 端点
- Redirects 端点
- StreamField 内容
- 富文本(HTML/Markdown)
- Sites 端点
- Locales 端点
- Schema 发现
- 完整参考
- 从 v2 迁移指南
分页:limit/offset 与 count 语义
所有列表端点使用limit/offset 分页,响应中的count是不受分页影响的总结果数:
{ "count": 42, "items": [] }通过?limit与?offset查询参数翻页,WAGTAILAPI_LIMIT_MAX限制limit的上限。
实现细节见 pagination.py:WagtailLimitOffsetPagination继承 Ninja 的LimitOffsetPagination,并做了两处与 v2 对齐的关键调整:
默认
limit为 20(Wagtail 的 API 默认值),而不是 Ninja 原生的 100:class Input(LimitOffsetPagination.Input): limit: int = Field(default=20, ge=1) offset: int = Field(default=0, ge=0)超限行为不同:当
limit超过WAGTAILAPI_LIMIT_MAX时,v3 直接抛出400错误("limit cannot be higher than {max}"),与 v2 行为一致;而 Ninja 基础分页器会静默地把 limit 截断到上限:def paginate_queryset(self, queryset, pagination, request, **params): max_limit = _get_max_limit() if max_limit != inf and pagination.limit > int(max_limit): raise HttpError(400, f"limit cannot be higher than {int(max_limit)}") return super().paginate_queryset(queryset, pagination, request, **params)其中
WAGTAILAPI_LIMIT_MAX的默认值为 20,设为None时上限为无穷(见_get_max_limit)。
错误处理:RFC 7807 的 application/problem+json
v3 API 的所有受控错误统一使用 RFC 7807 定义的application/problem+json媒体类型返回。覆盖范围包括:
| 场景 | 状态码 | 说明 |
|---|---|---|
| Schema / 内容 / 模型层校验失败 | 422 | 含逐字段errors列表 |
| 未认证访问受保护端点 | 401 | Authentication required |
| 已认证但权限不足 | 403 | 附带权限错误信息 |
| 资源不存在 | 404 | Not found |
| 富文本格式错误 | 400 | 由RichTextFormatError触发 |
一个校验失败的响应示例:
{ "type": "about:blank", "title": "Unprocessable Entity", "status": 422, "detail": "Validation failed", "errors": [] }对应的实现集中在 errors.py,几个值得了解的点:
错误体结构由 Pydantic Schema 声明:
ProblemDetail包含type、title、status、detail、errors五个字段,默认type为about:blank,title缺省时取 HTTP 状态短语(如 422 对应"Unprocessable Entity");多种校验异常归一到 422:Pydantic 校验错误、Ninja 校验错误、Django
ValidationError以及表单校验异常(FormValidationError)都会经validation_error_handler统一包装为 422 响应,并各自转换出对应格式的errors列表;401 与 403 的判定逻辑:从源码注释看,v3不信任基于会话的认证——只有当 bearer token 成功解析出用户时才返回 403,否则一律 401(errors.py):
@api.exception_handler(PermissionDenied) def permission_denied_handler(request, exc): # v3 never trusts session auth: 401 unless a bearer token resolved. if not request.user.is_authenticated: return problem_response(status=401, detail="Authentication required") return problem_response(status=403, detail=str(exc) or "Permission denied")未处理异常的边界行为:未匹配到任何处理器的异常不会被转换成 problem+json 信封。在生产环境(
DEBUG=False)中它们会被重新抛出,交由 Django 自身的错误处理机制,因此响应格式可能不是application/problem+json;仅在DEBUG=True时才会以 500 的 problem 响应返回(errors.py)。
这些行为的回归测试分别位于 test_errors.py、test_docs.py 与 test_openapi_snapshot.py(后者用快照文件 openapi.json 锁定 OpenAPI 输出,可用于观察 Schema 的演进)。
与 v2 共享的配置项
v3 API 在适用之处读取与 v2 相同的WAGTAILAPI_*设置,这意味着已有 v2 项目的配置可以平滑沿用:
WAGTAILAPI_BASE_URL:用于生成响应中绝对 URL 的基础地址;WAGTAILAPI_LIMIT_MAX:分页limit的上限(默认 20,None表示不限制);WAGTAILAPI_SEARCH_ENABLED:是否启用搜索参数;WAGTAILAPI_RICH_TEXT_FORMAT:富文本输出格式(HTML / Markdown)。
各设置的完整定义见 API 设置参考,v2 的配置说明见 v2 配置文档。
集成建议与小结
- 由于 v3 处于 preview 阶段,接入时请挂载在
/api/v3-preview/并关注 迁移指南 中关于 v2 → v3 差异的说明; - 优先利用
openapi.json与/docs/做客户端代码生成,而非手工拼请求;需要对外隐藏文档时设置WAGTAILAPI_DOCS_ENABLED = False; - 客户端错误处理应以
application/problem+json为契约:先检查status与title,再解析 422 响应中的errors列表做字段级提示; - 记住分页语义:
count是总数,limit默认 20,超过WAGTAILAPI_LIMIT_MAX会得到 400 而不是被静默截断; - 需要写操作时,先通过 认证文档 配置 token 认证,v3 对会话认证的信任策略与 v2 不同(未解析出 bearer token 一律 401)。
整体来看,v3 API 用 Django Ninja + Pydantic 类型体系换取了自动化的 OpenAPI 3.1 文档、按内容类型声明的 read/create/patch Schema,以及结构化的 RFC 7807 错误契约——这正是它对 v2 的核心增量,也是其作为 Wagtail Headless 读写接口未来演进方向的基础。
【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考