Wagtail API v2 使用指南:从数据拉取到字段定制的完整实战手册
Wagtail API v2 使用指南从数据拉取到字段定制的完整实战手册【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail本指南基于 Wagtail 官方文档 docs/advanced_topics/api/v2/usage.md 编写系统讲解如何通过 Wagtail API v2 模块对外提供公开、只读、JSON 格式的内容接口供移动端 App、站点前端或任何外部客户端消费。读完本文你将掌握列表/详情端点的请求方式、响应结构、分页与排序、树位置过滤、全文搜索、国际化过滤、字段级定制?fields以及 HTML 路径定位页面等全部实战技能并了解背后的源码实现机制。API v2 概览三个内置端点与只读契约Wagtail API 模块对外暴露的是公开、只读、JSON 格式化的接口适用于外部客户端如移动 App或站点自身的首屏渲染。要使用这些接口站点需要先完成 API 模块的启用与端点注册详见 Wagtail API v2 配置指南注册后即可通过GET请求访问以下三个内置端点页面Pages/api/v2/pages/图片Images/api/v2/images/文档Documents/api/v2/documents/注意实际可用的端点及其 URL 会因站点的 API 配置方式不同而有所差异。例如配置文档中的示例将页面端点挂载在/api/v2/下而你可以通过WagtailAPIRouter与register_endpoint()自由定制端点名称与挂载路径见 docs/advanced_topics/api/v2/configuration.md 与 router.py。从源码角度每个端点本质上是BaseAPIViewSet的子类通过get_urlpatterns()生成三类 URL 模式见 views.py列表视图path(, ..., namelisting)对应listing_view详情视图path(int:pk/, ..., namedetail)对应detail_view查找视图path(find/, ..., namefind)对应find_view三个内置端点类分别为页面wagtail.api.v2.views.PagesAPIViewSet、图片wagtail.images.api.v2.views.ImagesAPIViewSet、文档wagtail.documents.api.v2.views.DocumentsAPIViewSet。图片与文档端点的查询集还会自动剔除用户不可见的受限收藏集通过get_restricted_collection_ids实现见 wagtail/images/api/v2/views.py 与 wagtail/documents/api/v2/views.py。获取内容列表端点的请求与响应结构对任一列表端点发起GET请求即可拉取内容。每个响应都包含两个顶层部分items当前页返回的对象列表meta.total_count结果总数量该计数不受分页影响。典型响应如下GET /api/v2/endpoint_name/ HTTP 200 OK Content-Type: application/json { meta: { total_count: total number of results }, items: [ { id: 1, meta: { type: app_name.ModelName, detail_url: https://api.example.com/api/v2/endpoint_name/1/ }, field: value }, { id: 2, meta: { type: app_name.ModelName, detail_url: https://api.example.com/api/v2/endpoint_name/2/ }, field: different value } ] }从实现上看items与meta.total_count的组装发生在 pagination.py 的WagtailPagination.get_paginated_response()中先对查询集执行queryset.count()得到total_count再对切片后的数据序列化。这也解释了为什么total_count总是全量计数而与当前页无关。列表请求默认只返回每个端点定义的默认字段见下文默认端点字段一节。源码中这一行为由BaseAPIViewSet.listing_default_fields决定views.py例如页面端点的列表默认字段为id、type、detail_url、title、html_url、slug、first_published_at。自定义页面字段用 ?type 参数解锁专属字段Wagtail 站点通常包含多种页面类型每种类型都有自己的字段集合。出于安全与性能考虑pages端点默认只暴露公共字段如title、slug。要访问自定义字段需要通过?type参数指定页面类型。该参数有两个作用将结果过滤为仅包含该类型的页面使该类型上通过api_fields显式导出的自定义字段在 API 中可用。例如假设在配置文档的示例中blog.BlogPage模型通过api_fields导出了published_date、body、authors等字段见 configuration.md那么请求GET /api/v2/pages/?typeblog.BlogPagefieldspublished_date,body,authors(name) HTTP 200 OK Content-Type: application/json { meta: { total_count: 10 }, items: [ { id: 1, meta: { type: blog.BlogPage, detail_url: https://api.example.com/api/v2/pages/1/, html_url: https://www.example.com/blog/my-blog-post/, slug: my-blog-post, first_published_at: 2016-08-30T16:52:00Z }, title: Test blog post, published_date: 2016-08-30, authors: [ { id: 1, meta: { type: blog.BlogPageAuthor, }, name: Karl Hobley } ] }, ... ] }几点关键约束只有开发者显式导出的字段才能在 API 中使用。导出方式是在页面模型上定义api_fields属性详情见 配置文档。?type支持多个页面类型用逗号分隔例如?typeblog.BlogPage,blog.ArticlePage这是 v2 相对 v1 的新增能力。从源码看page_models_from_string()负责解析逗号分隔的类型字符串并校验每个模型都是Page的子类utils.py。图片/文档端点不适用此机制因为它们各自只暴露单一模型但如果项目自定义了图片/文档模型同样可以通过api_fields属性将自定义字段导出到 API。从源码层面看?type参数的处理发生在PagesAPIViewSet.get_queryset()views.py当只指定一个页面类型时查询集会从Page基类切换到具体的页面模型这样才能过滤到该模型的自定义api_fields当指定多个类型时则使用PageQuerySet.type(*models)进行多态查询。分页limit 与 offset列表响应默认每页返回20条结果。可通过两个查询参数控制?limit每页返回的数量默认 20?offset跳过的结果数量。例如请求第 2 页跳过前 20 条GET /api/v2/pages/?offset20limit20 HTTP 200 OK Content-Type: application/json { meta: { total_count: 50 }, items: [ pages 20 - 40 will be listed here. ] }?limit可能存在最大值上限。该上限由项目设置中的WAGTAILAPI_LIMIT_MAX控制设置为数字作为新的最大值设置为None禁用最大值检查不设上限。分页的底层实现在 pagination.pyWagtailPagination.paginate_queryset()中默认limit_max取settings.WAGTAILAPI_LIMIT_MAX缺省 20默认limit为min(20, limit_max)当limit limit_max时会抛出BadRequestErrorHTTP 400limit cannot be higher than ...。同时offset与limit都必须是正整数负数会触发校验错误这是分页参数的第一道防线。排序?order 的四种用法按单字段升序/降序通过?order参数指定排序字段默认升序GET /api/v2/pages/?ordertitle结果将按标题升序a-z排列。在字段名前加-号即可改为降序GET /api/v2/pages/?order-title注意排序是区分大小写的因此在升序排列时小写字母总是排在大写字母之后。从源码看OrderingFilterfilters.py会先把order参数按逗号拆分再逐个校验字段名是否属于可用的数据库字段校验不通过会返回 400 错误cannot order by ... (unknown field)。多字段连续排序多个字段用逗号传入?order即可实现连续排序GET /api/v2/pages/?ordertitle,-slug该请求会先按标题升序a-z排列对于标题相同的记录再按 slug 降序z-a排列。源码中通过queryset.order_by(*validated_fields)将拆分后的字段列表直接传给 Django 的order_by。随机排序向?order传入random关键字结果将以随机顺序返回。若没有缓存每次请求返回的顺序都会不同GET /api/v2/pages/?orderrandom限制随机排序时不能同时使用?offset。因为随机顺序无法在多次请求之间保持一致翻页请求可能返回与前一页重复的结果。源码中对此有显式校验OrderingFilter检测到random与其他字段组合、或与offset同时出现时会抛出 400 错误filters.py。实现上随机排序由 Django ORM 的queryset.order_by(?)完成。过滤字段精确匹配与树位置过滤任意字段的精确匹配任何字段都可以作为过滤条件以字段名作为查询参数名以要匹配的值作为参数值。例如查找 slug 为 about 的页面GET /api/v2/pages/?slugabout HTTP 200 OK Content-Type: application/json { meta: { total_count: 1 }, items: [ { id: 10, meta: { type: standard.StandardPage, detail_url: https://api.example.com/api/v2/pages/10/, html_url: https://www.example.com/about/, slug: about, first_published_at: 2016-08-30T16:52:00Z }, title: About }, ] }该功能由FieldsFilterfilters.py实现它有几个值得注意的细节过滤只对数据库字段开放通过get_available_fields(..., db_fields_onlyTrue)获取可用字段集type/detail_url等非数据库字段不可过滤布尔字段支持true/false/1/0四种取值parse_boolean解析见 utils.py整型与外键字段会被转换为对应 Python 类型后再过滤非法值返回 400标签字段TaggableManager支持按逗号分隔的多个标签过滤过滤值中不允许出现空字符\x00locale虽然是数据库字段但被单独抽出由专门的LocaleFilter处理避免与国际化过滤混淆。树位置过滤仅 pages 端点页面可以依据其在页面树中的位置进行过滤这在构建导航菜单、面包屑时非常实用。?child_of传入一个页面的 ID结果仅包含该页面的直接子页面。例如构建主菜单时传入首页 ID 并配合show_in_menus过滤GET /api/v2/pages/?child_of2show_in_menustrue HTTP 200 OK Content-Type: application/json { meta: { total_count: 5 }, items: [ { id: 3, meta: { type: blog.BlogIndexPage, detail_url: https://api.example.com/api/v2/pages/3/, html_url: https://www.example.com/blog/, slug: blog, first_published_at: 2016-09-21T13:54:00Z }, title: About }, { id: 10, meta: { type: standard.StandardPage, detail_url: https://api.example.com/api/v2/pages/10/, html_url: https://www.example.com/about/, slug: about, first_published_at: 2016-08-30T16:52:00Z }, title: About }, ... ] }?ancestor_of传入一个页面的 ID结果仅包含该页面的所有祖先父页面、祖父页面……直到站点根页面。与type过滤组合可用于查找某个blog.BlogPage所属的blog.BlogIndexPage单独使用则可以从当前页面回溯到站点根页面构建面包屑导航。?descendant_of传入一个页面的 ID结果仅包含该页面的所有后代子页面、孙页面……。从源码看三个过滤器分别由ChildOfFilter、AncestorOfFilter、DescendantOfFilter实现filters.py。其中child_of与descendant_of都支持传root关键字会使用Site.find_for_request(request).root_page作为参照页且descendant_of与child_of不能同时使用源码会抛出 filtering by descendant_of with child_of is not supported。按站点过滤页面默认情况下API 根据请求的 hostname主机名确定站点。当需要查询其他站点的页面时使用?site过滤器其值要求是站点的已配置主机名。如果多个站点共用同一主机名但端口不同可以用hostname:port格式按端口过滤GET /api/v2/pages/?sitedemo-site.local GET /api/v2/pages/?sitedemo-site.local:8080搜索全文检索与 search_operator向?search参数传入查询词即可对结果执行全文搜索。查询词会先按词边界拆分为多个词项terms再对每个词项做规范化处理转小写、去重音符号。例如?searchJamesJoyce号在 URL 中表示空格。search_operator多词项的组合逻辑search_operator参数决定多个词项如何组合有两个可选值and查询中的所有词项排除停用词后必须全部出现在每条结果中or查询中的至少一个词项出现在每条结果中即可。or通常比and更好用因为用户可以不必精确输入而排序算法会保证不相关的结果不会排在前面。默认操作符的选择取决于站点使用的搜索引擎是否支持相关性排序若支持排序如 Elasticsearch默认操作符为or若不支持如数据库后端默认操作符为and。正因如此当?search与?order同时使用这会禁用相关性排序时官方建议显式使用and操作符GET /api/v2/pages/?searchJamesJoyceorder-first_published_atsearch_operatorand从源码看SearchFilterfilters.py是页面端点过滤器链中的最后一个见 views.py 中的注释needs to be last, as SearchResults querysets cannot be filtered further。它通过get_search_backend()获取当前搜索引擎并把search_operator与是否按相关性排序order_by_relevance当 URL 中不存在order时为 True传给搜索后端。此外若WAGTAILAPI_SEARCH_ENABLED False使用?search会返回 400 search is disabled标签过滤?tag...与搜索不能同时使用源码会抛出 filtering by tag with a search query is not supported对未索引的字段进行过滤或排序会返回 400 错误cannot filter by ... while searching (field is not indexed)。国际化站点的专用过滤器当设置WAGTAIL_I18N_ENABLED True详见 i18n 文档时pages 端点会额外提供两个过滤器。按语言区域过滤?locale?locale过滤器只返回指定 locale 的页面GET /api/v2/pages/?localeen-us HTTP 200 OK Content-Type: application/json { meta: { total_count: 5 }, items: [ { id: 10, meta: { type: standard.StandardPage, detail_url: https://api.example.com/api/v2/pages/10/, html_url: https://www.example.com/usa-page/, slug: usa-page, first_published_at: 2016-08-30T16:52:00Z, locale: en-us }, title: American page }, ... ] }LocaleFilterfilters.py会按language_code查找Locale模型再执行queryset.filter(localelocale)。值得一提的是locale字段是否出现在默认响应中与国际化开关联动PagesAPIViewSet会在WAGTAIL_I18N_ENABLED开启时把locale追加到列表默认字段关闭时则从详情默认字段中移除views.py。获取某页面的翻译版本?translation_of?translation_of过滤器接收一个页面 ID只返回该页面的各语言翻译版本GET /api/v2/pages/?translation_of10 HTTP 200 OK Content-Type: application/json { meta: { total_count: 2 }, items: [ { id: 11, meta: { type: standard.StandardPage, detail_url: https://api.example.com/api/v2/pages/11/, html_url: https://www.example.com/gb-page/, slug: gb-page, first_published_at: 2016-08-30T16:52:00Z, locale: en-gb }, title: British page }, { id: 12, meta: { type: standard.StandardPage, detail_url: https://api.example.com/api/v2/pages/12/, html_url: https://www.example.com/fr-page/, slug: fr-page, first_published_at: 2016-08-30T16:52:00Z, locale: fr }, title: French page }, ] }实现上TranslationOfFilterfilters.py通过queryset.translation_of(page)完成过滤。注意当?translation_of与?child_of组合使用时源码会保留child_of的父页面标记_filtered_by_child_of确保后续逻辑如页面资源管理器仍能获取正确的父页面。字段选择?fields 参数完全指南默认情况下响应只返回可用字段的一个子集。?fields参数既能添加额外字段也能移除不需要的默认字段是 API 调优的核心工具。添加额外字段将?fields设为要添加字段的逗号分隔列表。例如?fieldsbody,feed_image会在响应中追加body与feed_image两个字段。该能力同样适用于跨关系嵌套字段?fieldsbody,feed_image(width,height)会把图片的width、height嵌套进feed_image的表示中。嵌套语法可以递归使用如authors(name,photo(url))。添加全部字段将?fields设为星号*会加入所有可用字段非常适合用来发现模型到底导出了哪些字段GET /api/v2/pages/?fields*移除字段在字段名前加-前缀即可移除你知道不需要的默认字段。例如?fields-title,body表示移除title、添加body。该语法可与星号组合?fields*,-body表示添加全部字段但排除body。移除全部默认字段如果你希望精确定义需要的字段可以把?fields的第一项设为下划线_这会移除所有默认字段。例如?fields_,title只返回title字段。从实现角度看?fields的解析由parse_fields_parameter()utils.py完成它使用严格的语法不允许空白字符把参数字符串解析为三元组列表字段名、是否取反negated、嵌套字段列表。随后_get_serializer_class()views.py根据解析结果决定字段集合并注意以下语法规则*与_必须位于第一位且不能取反*后面只允许带子字段或取反字段如*,foo(bar)、*,-foo合法*,foo非法_与取反字段不能组合_ ,-foo非法取反字段不能带嵌套子字段非关联字段不能带子字段会返回 xxx does not support nested fields未知字段名会返回 400 unknown fields: ...。对于关联字段_get_serializer_class()还会通过路由器的get_model_endpoint()找到关联模型的端点类递归为其生成嵌套序列化器对于ParentalKey关联的子模型内联模型默认会嵌套显示全部字段。这就是为什么?fieldsauthors(name)能精确控制子对象字段的原因。详情视图获取单个对象在端点 URL 后追加对象 ID 即可获取单个对象页面/api/v2/pages/1/图片/api/v2/images/1/文档/api/v2/documents/1/详情视图默认返回所有已导出字段。同样可以使用?fields定制显示哪些字段。例如/api/v2/pages/1/?fields_,title,body只返回 id 为 1 的页面的title和body两个字段。源码中详情视图由detail_view处理views.pyget_serializer_class()会根据 action 是否为listing_view决定show_details标志详情视图会包含仅详情字段如页面的meta.parent见detail_only_fields [parent]列表视图则不会。此外PagesAPIViewSet.get_object()会返回页面的specific版本确保序列化的是具体页面类型而非基类Page。按 HTML 路径查找页面find 视图使用/api/v2/pages/find/?html_pathpath可以根据页面的 HTML 路径定位单个页面GET /api/v2/pages/find/?html_path/about/该视图返回两种结果302重定向响应跳转到该页面的详情视图404未找到响应路径对应的页面不存在。例如/api/v2/pages/find/?html_path/总是重定向到站点首页的详情视图。重定向时会保留除查找参数外的其余查询参数因此可以链式追加?fields...等定制参数。实现上PagesAPIViewSet.find_object()views.py会通过Site.find_for_request(request)定位站点将html_path拆分为路径组件后调用site.root_page.specific.route(request, path_components)完成页面路由find_viewviews.py随后生成指向详情视图的绝对 URL 并返回 302。另外所有端点还支持通过?id在 find 视图中按 ID 查找对象。默认端点字段速查下表汇总各端点默认返回的字段供开发时对照。公共字段所有端点id数字对象的唯一 ID。注意除页面类型外其他内容类型各自拥有独立的 ID 空间因此必须将id与type字段组合使用才能得到对象的全局唯一标识。type字符串对象的类型格式为app_label.ModelName。detail_url字符串该对象详情视图的 URL。公共字段由BaseAPIViewSet定义body_fields [id]、meta_fields [type, detail_url]见 views.pytype与detail_url在 serializers.py 中分别由TypeField/PageTypeField与DetailUrlField序列化。页面端点title字符串meta.slug字符串meta.show_in_menus布尔值meta.seo_title字符串meta.search_description字符串meta.first_published_at日期/时间以上字段的值取自页面模型上对应的字段。meta.html_url字符串如果站点存在由 Wagtail 生成的 HTML 前端该字段为页面的 URL。它由PageHtmlUrlField序列化内部调用page.full_url见 serializers.py。meta.parent嵌套返回父页面的部分信息仅详情视图可用属detail_only_fields。由PageParentField实现页面没有真正的parent字段序列化时通过instance.get_parent()查找并校验父页面在当前用户可见的查询集中serializers.py。meta.alias_of字典若页面被标记为别名alias返回原始页面的 ID 与完整 URL。由PageAliasOfField实现serializers.py。图片端点title字符串图片标题字段的值。在 Wagtail 中该值用作图片的altHTML 属性。width数字/height数字原始图片文件的尺寸。meta.tags字符串列表与图片关联的标签列表。图片端点的字段在 wagtail/images/api/v2/views.py 中配置body_fields追加title、width、heightmeta_fields追加tags、download_url。tags由TagsField序列化为按名称排序的字符串列表serializers.py。文档端点title字符串文档标题字段的值。meta.tags字符串列表与文档关联的标签列表。meta.download_url字符串文档文件的下载 URL。文档端点的字段在 wagtail/documents/api/v2/views.py 中配置。生成download_url的绝对地址时依赖WAGTAILAPI_BASE_URL设置若未设置会回退使用当前请求的主机名但配合wagtailfrontendcache前端缓存失效模块使用时WAGTAILAPI_BASE_URL必须显式设置因为缓存失效场景不存在当前请求见 configuration.md。自 v1 以来的变化破坏性变更Breaking changes列表响应的结果数组由 v1 的pages/images/documents统一改名为items。主要新特性Major features?fields参数能力大幅增强支持移除字段、添加全部字段、以及自定义嵌套字段。次要新特性Minor features页面端点新增html_url、slug、first_published_at、expires_at、show_in_menus字段文档端点新增download_url字段页面端点的type参数支持同时指定多个页面类型布尔字段过滤现在支持true与false同时兼容1/0见 utils.pyorder可以与search组合使用新增search_operator参数。小结Wagtail API v2 是一套设计克制、语法统一的只读 JSON 接口limit/offset控制分页order支持升序、降序、多字段与随机排序search与search_operator提供全文检索child_of/ancestor_of/descendant_of/site覆盖树位置与多站点场景locale/translation_of服务国际化站点而?fields的/-/*/_四类操作符让客户端可以精确裁剪响应体积。所有这些行为都能在 wagtail/api/v2 目录下的 views.py、filters.py、pagination.py、serializers.py 与 utils.py 中找到对应实现配合 配置指南 即可在生产站点中快速落地一套安全、高效的内容 API。【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考