Wagtail v3 API 实战:Sites 站点的增删改查接口与权限模型解析
【免费下载链接】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/v3/sites/下提供了站点(Site)的完整 CRUD 能力:列表、详情、创建、更新、删除。本文以官方文档 docs/advanced_topics/api/v3/sites.md 为骨架,结合仓库内路由、Schema、模型与表单源码,逐层解析 Sites 端点的请求方式、字段语义、权限过滤逻辑以及其与后台管理界面共享的校验规则,帮助你直接用 HTTP 请求完成站点的全生命周期管理。
前置条件:启用 v3 API 并完成认证
Sites 端点是 v3 API 的一部分,使用前需要先完成两件事:启用 API 与获取访问令牌。
启用并挂载 v3 API
在 Django 项目设置中将wagtail.api.v3加入INSTALLED_APPS(参考 v3 API 快速开始):
# settings.py INSTALLED_APPS = [ ... 'wagtail.api.v3', ... ]然后在urls.py中挂载 API 路由。由于 v3 目前仍处于预览阶段,官方建议挂载在/api/v3-preview/下以表明其可能随后续版本变化:
# urls.py from wagtail.api.v3.urls import api urlpatterns = [ path("api/v3-preview/", api.urls), # 也可以直接挂载在 /api/v3/ 下: # path("api/v3/", api.urls), ]挂载后,浏览器可访问<API root>/docs/查看交互式文档,<API root>/openapi.json获取机器可读的 OpenAPI 3.1 Schema(两者默认公开,可通过WAGTAILAPI_DOCS_ENABLED设置关闭)。
获取 Bearer Token
Sites 端点仅接受认证请求,不提供匿名访问,因此每个请求都必须携带 Bearer Token。令牌的创建与权限模型详见 v3 API 认证文档,这里给出两种常用方式:
- 后台界面:设置 → API tokens(
wagtail.api.v3在INSTALLED_APPS中时可见),令牌明文只在创建时展示一次; - 命令行:使用管理命令创建,便于脚本化:
TOKEN=$(./manage.py api_tokens create --user=deploy --name="ci")将令牌放入请求头:
curl -H "Authorization: Bearer $TOKEN" https://example.com/api/v3/whoami/Sites 端点总览:五个 CRUD 操作
根据 sites.md 的说明,站点在/api/v3/sites/下以 CRUD 方式暴露:
| 方法 | 路径 | 说明 | 成功状态码 |
|---|---|---|---|
GET | /api/v3/sites/ | 列出站点 | 200 |
GET | /api/v3/sites/{site_id}/ | 返回单个站点 | 200 |
POST | /api/v3/sites/ | 创建站点 | 201 |
PUT | /api/v3/sites/{site_id}/ | 更新站点 | 200 |
DELETE | /api/v3/sites/{site_id}/ | 删除站点 | 204 |
这一组操作在源码中对应 wagtail/api/v3/routers/sites.py 里Router(tags=["sites"], auth=BearerTokenAuth())上注册的五个端点,每个端点都带有明确的 OpenAPI 元数据(summary、operation_id),便于自动生成客户端。
一个站点由id、hostname、port、site_name、root_page_id和is_default_site描述,列表与详情端点都会按权限策略过滤,调用者只能看到自己有权限的站点。
数据模型与字段语义
Sites 端点的响应与请求数据结构定义在 wagtail/api/v3/schemas/sites.py,底层对应wagtail.models.Site模型(见 wagtail/models/sites.py)。
响应 Schema:SiteSchema
class SiteSchema(Schema): id: int hostname: str port: int site_name: str root_page_id: int is_default_site: bool请求 Schema:SiteInputSchema
class SiteInputSchema(Schema): hostname: str port: int = 80 site_name: str = "" root_page: int = Field(..., alias="root_page_id") is_default_site: bool = False几个值得注意的字段细节:
port默认80:与Site模型port = models.IntegerField(default=80)一致。模型层面的帮助文本说明,只有需要在 URL 中体现特定端口时才需修改(例如本地开发端口 8000),它不影响请求处理,因此端口转发仍然有效;site_name默认空字符串:模型上为max_length=255、可留空的可读名称;root_page通过别名root_page_id接收:输入 Schema 刻意接受root_page_id以保持与输出字段一致,但在内部映射为root_page,从而能被SiteForm(一个 DjangoModelForm)直接接受;is_default_site默认False:模型上默认为False,含义是"若为真,该站点将处理所有没有自己站点记录的其他主机名的请求"。
模型层面的核心约束
Site模型定义了两个关键约束(wagtail/models/sites.py):
unique_together = ("hostname", "port"):同一主机名加端口的组合全局唯一,这也是创建/更新时唯一性校验的底层依据;- 默认站点唯一性:
clean_fields()中检查"是否已存在is_default_site=True的站点",若已存在其他默认站点,再设置新默认站点会抛出校验错误,提示必须先取消原默认站点。
hostname 规范化
Site.clean()会执行self.hostname = self.hostname.lower(),即主机名一律转为小写。由于 API 的创建与更新都走SiteForm,这个规范化规则同样作用于 API 写入——通过 API 传入WWW.Example.COM会与后台管理一样被规范化为www.example.com。
权限模型:仅认证 + 权限策略过滤
Sites 端点与公开的页面、图片等只读端点不同,完全没有匿名访问。路由级别统一挂载了BearerTokenAuth(wagtail/api/v3/routers/sites.py 第 15 行),未认证请求直接失败。
在认证之外,每个端点还通过@require_any_permission(Site, ...)装饰器做二次权限校验,其权限集合来自 Wagtail 的policy_registry(权限策略注册表):
list_sites与get_site:要求调用者对Site拥有add、change、delete、view任一权限,并进一步通过instances_user_has_any_permission_for(request.user, ...)对实例级别过滤——即列表与详情返回的站点,必须是当前用户对其持有任一上述权限的记录;create_site:要求add权限;update_site:要求change权限;delete_site:要求delete权限。
这意味着令牌的访问范围与其绑定的用户账号权限完全一致(认证文档中明确"令牌允许的访问级别等同于其绑定的用户账号")。若需要对 API 做最小化授权,官方建议为 API 访问创建专用服务账号,赋予最小权限集,需要时直接吊销而不影响真实用户。结合源码结构可以推断:把站点排除在某用户权限之外,该用户的令牌在列表与详情响应中就看不到该站点,从而同时实现了可见性与可写性的双重控制。
创建站点:从 curl 到 action 的完整链路
官方文档给出了一个可直接复制的创建示例(路径按仓库根目录相对路径理解,实际部署时替换为你的 API 根):
curl -X POST "https://example.com/api/v3/sites/" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "www.example.com", "port": 443, "site_name": "Example", "root_page_id": 4, "is_default_site": true}'创建成功后返回201,响应体为完整的SiteSchema。
底层调用链
创建端点的实现(wagtail/api/v3/routers/sites.py 第 52-66 行)展示了 API 与后台管理共享核心逻辑的设计:
@router.post( "/", response={201: SiteSchema}, url_name="create_site", summary="Create site", operation_id="sites_create", ) @require_any_permission(Site, ("add",)) def create_site(request: HttpRequest, data: SiteInputSchema): form = SiteForm(data.dict()) action_class = action_registry.get_action_class(Site, "create") action_class(form.instance, user=request.user, form=form).execute( skip_permission_checks=True ) return Status(201, form.instance)关键点:
SiteForm(data.dict()):请求 JSON 被转换为SiteForm(wagtail/sites/forms.py),其Meta.fields为("hostname", "port", "site_name", "root_page", "is_default_site")。由于 API 传入的数据与后台表单同源,后台管理界面的所有规则都会生效:hostname 规范化(小写)、(hostname, port)唯一性约束、默认站点唯一性约束、root page 校验(root_page必须是指向有效Page的外键)以及后续的缓存失效逻辑;action_registry.get_action_class(Site, "create"):创建操作经由 Wagtail 的 action 注册表(wagtail/actions/registry.py)分派到Create动作类,与后台界面的保存走同一条业务路径,因此站点相关的 cache invalidation(清缓存/前端缓存失效信号)等副作用不会因为走 API 而缺失;skip_permission_checks=True:权限已在路由装饰器层校验过,action 内部不再重复检查;- 状态码语义:创建返回
201,删除返回204,与文档描述一致。
列出与查看站点:分页与权限过滤
# 列出站点 curl -H "Authorization: Bearer $TOKEN" "https://example.com/api/v3/sites/?limit=20&offset=0" # 查看单个站点 curl -H "Authorization: Bearer $TOKEN" "https://example.com/api/v3/sites/4/"列表端点使用 limit/offset 分页(WagtailLimitOffsetPagination,与 v3 API 全局分页一致),响应形如:
{ "count": 42, "items": [] }其中count是不受分页影响的全部结果数;?limit与?offset用于翻页,limit上限由WAGTAILAPI_LIMIT_MAX设置控制(详见 API 设置参考 及 v2 配置文档)。
值得注意的是列表查询的权限过滤实现:list_sites返回policy_registry.get_by_type(Site).instances_user_has_any_permission_for(request.user, ("add", "change", "delete", "view")),即数据库查询层就完成了按用户权限的站点筛选;详情端点get_site则把同一查询集交给get_object_or_404,对无权限的site_id返回404而非403,避免泄露站点是否存在。
更新与删除站点
# 更新站点(PUT 整体替换) curl -X PUT "https://example.com/api/v3/sites/4/" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "www.example.com", "port": 443, "site_name": "Example v2", "root_page_id": 4, "is_default_site": true}' # 删除站点 curl -X DELETE "https://example.com/api/v3/sites/4/" \ -H "Authorization: Bearer $TOKEN"更新端点使用SiteForm(data.dict(), instance=site)绑定既有实例,再经 action 注册表分派到edit动作执行;删除端点分派到delete动作并返回204 No Content。与创建一样,更新和删除都要求相应权限(change/delete),且同样经由 Wagtail action 体系触发后续的缓存失效与日志记录等副作用。
错误处理约定
v3 API 的统一错误处理同样适用于 Sites 端点(详见 v3 API 概览):处理过的 API 错误采用 RFC 7807 的application/problem+json格式,包括校验失败(HTTP 422)、权限失败(未认证401、已认证但无权403)、404等。例如提交非法数据(如重复的 hostname/port 组合)会得到形如:
{ "type": "about:blank", "title": "Unprocessable Entity", "status": 422, "detail": "Validation failed", "errors": [] }errors数组中会包含SiteForm校验失败的字段级细节。
测试验证与 OpenAPI 参考
仓库在 wagtail/api/v3/tests/test_sites.py 中为 Sites 端点提供了完整的测试覆盖(列表、详情、创建、更新、删除及权限行为),并在wagtail/api/v3/tests/snapshots/openapi.json的快照中固化每个端点的 OpenAPI 描述——这也是文档中"完整生成的 OpenAPI 参考来自 Wagtail 自身的 OpenAPI 快照"一说的来源。
需要完整的端点到字段级别的 OpenAPI 定义时,可直接访问自己实例的<API root>/openapi.json,或阅读 v3 API OpenAPI 参考文档 中的生成参考。想了解 v3 整体设计(分页、错误处理、与其他资源的对应关系)可继续阅读 v3 API 索引 与 Schema 文档。
小结
Wagtail v3 API 的 Sites 端点是一个"仅认证 + CRUD + 复用后台表单与 action 体系"的典型实现:五个端点覆盖站点生命周期,字段与Site模型一一对应,写入路径与后台管理共享SiteForm的全部校验规则(hostname 规范化、唯一性、默认站点约束、root page 校验、缓存失效),而权限策略注册表则保证了列表、详情、写操作三级的细粒度控制。对于需要以编程方式管理多站点 Wagtail 项目的团队,这组端点提供了一个与后台界面行为一致、可脚本化的站点管理入口。
【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考