ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Django REST framework 3.1 全解析:新一代分页 API、版本控制、国际化与 PostgreSQL 新字段支持

Django REST framework 3.1 全解析:新一代分页 API、版本控制、国际化与 PostgreSQL 新字段支持 Django REST framework 3.1 全解析新一代分页 API、版本控制、国际化与 PostgreSQL 新字段支持【免费下载链接】django-rest-frameworkWeb APIs for Django. 项目地址: https://gitcode.com/gh_mirrors/dj/django-rest-framework本文以仓库内 docs/community/3.1-announcement.md 为核心骨架结合当前仓库的源码实现rest_framework/pagination.py、rest_framework/versioning.py、rest_framework/fields.py、rest_framework/serializers.py 等展开深入讲解 3.1 版本引入的三大核心能力——分页 API 重构、内置版本控制、国际化支持并覆盖新字段类型、ModelSerializer 字段映射定制与组件迁移清单。读完后你将能够直接迁移到基于分页类配置的新分页体系、为 API 接入 URL/Accept Header 版本控制、启用多语言错误响应并利用DictField/ListField/UUIDField表达更丰富的表示层数据结构。一、3.1 版本概览Kickstarter 系列发布的中间一站Django REST framework 3.1 是 Kickstarter 系列版本发布中的中间一步3.0 为首个版本它并不是一次伤筋动骨的破坏性升级而是在 3.0 的基础上补充了大量此前缺失或尚未公开 API 化的功能。官方公告列举的主要亮点包括一套聪明的游标cursor分页方案改进的分页 API同时支持在响应体body与响应头header中输出分页信息分页控件直接在**可浏览 APIbrowsable API**中渲染更好的 API 版本控制支持内置 URL 与 Accept Header 两种风格内置国际化支持错误响应可翻译完整支持 Django 1.8 新增的HStoreField、ArrayField等字段类型。其中不少能力在今天的源码中依然完整保留并持续演进例如分页模块rest_framework/pagination.py中的三类内置分页类、版本控制模块rest_framework/versioning.py中的五种内置方案以及字段模块rest_framework/fields.py中的ListField、DictField、HStoreField、UUIDField。理解 3.1 引入的这些设计是掌握现代 DRF 分页、版本控制与序列化字段体系的捷径。二、分页 API 重构从单一风格到三类内置方案3.1 之前REST framework 只内置了一种分页风格。3.1 起仓库内置了三种开箱即用的分页方案分页类查询参数风格适用场景PageNumberPagination?page4可选page_size常规的页码式分页适合数据量适中、结果集相对稳定的场景LimitOffsetPagination?limit100offset400客户端显式控制条数 偏移适用于可自由跳跃的结果集CursorPagination?cursor编码后的游标大量或频繁变化的结果集客户端逐页迭代在 rest_framework/pagination.py 中三者均继承自BasePagination。BasePagination定义了两个必须实现的抽象方法paginate_queryset()与get_paginated_response()以及可选的to_html()用于可浏览 API 的控件渲染、get_results()、get_schema_operation_parameters()等钩子任何自定义分页类都需要遵循这一接口约定。2.1 配置方式的变化从 settings 键到分页类属性分页 API 改进的同时一批旧的设置键与视图属性被移入待弃用pending deprecation状态。控制分页风格的方式从此主要变为覆盖一个分页类、修改其配置属性然后通过DEFAULT_PAGINATION_CLASS指向它。具体变化如下PAGINATE_BY设置键继续可用但进入待弃用状态应改用命名更直观的PAGE_SIZEPAGINATE_BY_PARAM、MAX_PAGINATE_BY设置键进入待弃用状态应改为在分页类上设置配置属性如page_size_query_param、max_page_size视图上的paginate_by、page_query_param、paginate_by_param、max_paginate_by属性同样进入待弃用状态统一改为配置分页类pagination_serializer_class视图属性与DEFAULT_PAGINATION_SERIALIZER_CLASS设置键已彻底失效——分页 API 不再依赖序列化器决定输出格式若要定制输出结构需在分页类中覆盖get_paginated_response()方法。在 rest_framework/settings.py 中可以印证这一演进痕迹当前REMOVED_SETTINGS列表中已包含PAGINATE_BY、PAGINATE_BY_PARAM、MAX_PAGINATE_BY三个键settings.py#L159-L161即它们在后续版本中已被彻底移除而DEFAULTS中保留了PAGE_SIZE: None与DEFAULT_PAGINATION_CLASS: Nonesettings.py#L53、settings.py#L67None意味着默认不启用分页需要开发者显式配置。一个典型的启用方式如下REST_FRAMEWORK { DEFAULT_PAGINATION_CLASS: rest_framework.pagination.PageNumberPagination, PAGE_SIZE: 100, }2.2 PageNumberPagination页码分页PageNumberPaginationpagination.py#L155使用 Django 标准Paginator处理页码核心配置属性包括page_size默认页大小默认取自api_settings.PAGE_SIZEpage_query_param页码查询参数名默认pagepage_size_query_param是否允许客户端指定页大小默认None关闭设为page_size即可开启max_page_size客户端可请求的最大页大小上限仅在开启page_size_query_param时生效last_page_strings将某些字符串解析为末页默认(last,)即?pagelast跳转到最后一页invalid_page_message非法页码时的错误消息源码中以NotFound异常抛出pagination.py#L201-L207。其paginate_queryset()的流程pagination.py#L188-L213是先通过get_page_size()解析页大小若为空则直接返回None表示不启用分页随后用django_paginator_class构造分页器、解析页码、捕获InvalidPage异常并转换为 404NotFound响应最后当总页数大于 1 时置display_page_controls True使可浏览 API 渲染分页控件。默认的响应体结构由get_paginated_response()定义包含四个键{ count: 123, next: http://api.example.org/accounts/?page4, previous: http://api.example.org/accounts/?page2, results: [] }next/previous链接由get_next_link()/get_previous_link()基于当前请求的绝对 URI 动态生成当页码为 1 时previous会直接移除page查询参数而非生成page1。2.3 LimitOffsetPaginationlimit/offset 分页LimitOffsetPaginationpagination.py#L334通过limit返回条数与offset起始下标两个参数切片查询集核心属性包括default_limit默认条数默认取自PAGE_SIZElimit_query_param/offset_query_param参数名默认limit/offsetmax_limit客户端可请求的limit上限默认None不限制。其实现不依赖 DjangoPaginator而是直接在查询集上执行切片queryset[self.offset:self.offset self.limit]pagination.py#L362因此既支持 QuerySet 也支持普通列表get_count()会优雅地回退到len(queryset)。请求示例http://api.example.org/accounts/?limit100 http://api.example.org/accounts/?offset400limit1002.4 CursorPagination面向高频变化结果集的游标分页CursorPaginationpagination.py#L518是本次发布中最聪明的方案特别适合客户端迭代大量或频繁变化的结果集。相比页码分页游标分页不依赖总页数/总条数这些容易漂移的统计值天然避免翻页过程中新增/删除数据导致重复或遗漏的问题。它的两个设计要点位置 偏移position offset双信息游标游标中既记录排序字段的位置值p也记录偏移量o。这使得分页可以作用于非唯一索引——例如排序字段是毫秒级精度的创建时间多个对象可能共享同一个位置值时用偏移量在相同位置内继续推进。源码用Cursor namedtuple(Cursor, [offset, reverse, position])表示这一结构pagination.py#L127。支持前向与反向reverse游标next与previous链接都能生成反向翻页时内部会对排序做反转_reverse_ordering()见 pagination.py#L116-L124。游标本身以 Base64 编码的查询字符串形式出现在 URL 中。encode_cursor()pagination.py#L806-L820将ooffset、rreverse 标志、pposition三个 token 编码进?cursor...参数decode_cursor()负责反向解码并对恶意构造的 offset 设定了硬性上限offset_cutoff 1000避免产生昂贵的数据库查询pagination.py#L544。关键配置属性cursor_query_param游标参数名默认cursorordering排序字段默认-created必须设置为一个稳定、唯一或近似唯一的字段如-created或pkpage_size/page_size_query_param/max_page_size与页码分页语义一致offset_cutoff偏移量硬上限默认1000。get_ordering()pagination.py#L739-L779中的三条断言非常值得注意它们是游标分页正确性的纪律未声明ordering会直接断言报错不支持包含__的双下划线关联查找排序必须是模型上不变、唯一或近似唯一的字段排序值必须是字符串、列表或元组。游标分页的响应体默认不含count只包含next、previous、results三个键pagination.py#L830-L835因为游标方案天然不需要也不主张暴露总数。2.5 可浏览 API 中的分页控件3.1 起分页结果会直接在可浏览 API 中渲染控件页码与 limit/offset 风格渲染为页号控件游标风格则渲染为更简洁的上一页/下一页控件。相关模板位于 rest_framework/templates/rest_framework/pagination/numbers.html页码式与 previous_and_next.html游标式渲染入口是各分页类的to_html()方法。2.6 支持基于响应头的分页Header Pagination旧版分页 API 只能在响应体中输出分页信息。3.1 起分页类可以完全自定义响应输出因此可以写出使用Link或Content-Range响应头的分页方案。实现方式是在自定义分页类中覆盖get_paginated_response()将get_next_link()/get_previous_link()的结果写入响应头class HeaderLimitOffsetPagination(LimitOffsetPagination): def get_paginated_response(self, data): next_url self.get_next_link() previous_url self.get_previous_link() if next_url is not None and previous_url is not None: link {next_url}; relnext, {previous_url}; relprev elif next_url is not None: link {next_url}; relnext elif previous_url is not None: link {previous_url}; relprev else: link return Response(data, headers{Link: link})更完整的说明见仓库文档 docs/api-guide/pagination.md#custom-pagination-styles。三、API 版本控制URL 与 Accept Header 双方案内置3.1 让构建带版本的 API变得更容易内置方案覆盖了 URL 与 Accept Header 两大类。在 rest_framework/versioning.py 中所有方案均继承自BaseVersioning后者定义了统一的determine_version()抽象方法、从DEFAULT_VERSION/ALLOWED_VERSIONS/VERSION_PARAM三个设置项读取的默认版本、允许版本集合与版本参数名并实现了is_allowed_version()校验逻辑。内置方案一览分页类版本来源非法版本时的异常URLPathVersioningURL 路径中的命名参数如?PversionNotFoundNamespaceVersioningDjango URL namespaceNotFoundAcceptHeaderVersioningAccept: application/json; version1.0媒体类型参数NotAcceptableHostNameVersioning请求主机名如v1.example.comNotFoundQueryParameterVersioning查询参数如?version0.1NotFound启用方式是在REST_FRAMEWORK中配置REST_FRAMEWORK { DEFAULT_VERSIONING_CLASS: rest_framework.versioning.NamespaceVersioning, DEFAULT_VERSION: v1, ALLOWED_VERSIONS: [v1, v2], VERSION_PARAM: version, }3.1 NamespaceVersioning 与版本感知的超链接序列化器官方公告特别强调使用 URL 方案时超链接序列化器会把关系解析到与当前请求相同的 API 版本上。以NamespaceVersioning为例其reverse()实现versioning.py#L132-L140会在请求携带版本时通过get_versioned_viewname()将视图名改写为请求版本 : 视图名从而保证反向解析出的 URL 落在正确的 namespace 下。例如配置了v1、v2两个 namespace 的 URLconf# urls.py urlpatterns [ path(v1/, include(users.urls, namespacev1)), path(v2/, include(users.urls, namespacev2)), ]配合如下的超链接序列化器class AccountsSerializer(serializers.HyperlinkedModelSerializer): class Meta: model Accounts fields [account_name, users]当客户端请求v2版本时输出中的users关系也会指向v2下的地址与入站请求保持版本一致GET http://example.org/v2/accounts/10 # 版本 v2 { account_name: europa, users: [ http://example.org/v2/users/12, # 版本 v2 http://example.org/v2/users/54, http://example.org/v2/users/87 ] }从源码结构看URLPathVersioning的reverse()versioning.py#L82-L91则是把版本写回 URL 关键字参数kwargs[self.version_param]而AcceptHeaderVersioning与HostNameVersioning因版本信息不在 URL 中无需实现reverse()。四、内置国际化支持多语言错误响应开箱即用3.1 起REST framework 内置了一套完整翻译支持国际化的错误响应。你可以整体更换默认语言也可以允许客户端通过Accept-Language请求头指定语言。更改默认语言使用 Django 标准的LANGUAGE_CODE设置LANGUAGE_CODE es-es开启按请求切换语言需要在MIDDLEWARE_CLASSES中加入LocaleMiddlewareMIDDLEWARE_CLASSES [ ... django.middleware.locale.LocaleMiddleware, ]启用按请求国际化后客户端请求会尽可能尊重Accept-Language头。例如请求一个不支持的媒体类型RequestGET /api/users HTTP/1.1 Accept: application/xml Accept-Language: es-es Host: example.orgResponseHTTP/1.0 406 NOT ACCEPTABLE { detail: No se ha podido satisfacer la solicitud de cabecera de Accept. }注意错误响应的结构保持不变仍然包含detail键只是文案被翻译了。如果你需要进一步定制响应结构可以编写自定义异常处理器见 docs/api-guide/exceptions.md#custom-exception-handling。官方内置的翻译同时覆盖两类场景标准异常情形与序列化器校验错误。当前仓库的 rest_framework/locale/ 目录下保存了数十种语言的django.po/django.mo翻译文件含中文zh_CN、zh_Hans、zh_Hant、zh_TW等可以直观地核对实际支持的语言范围。若只想支持语言全集的一个子集使用 Django 标准的LANGUAGES设置LANGUAGES [ (de, _(German)), (en, _(English)), ]更完整的说明见仓库文档 docs/topics/internationalization.md。五、新字段类型完整支持 Django 1.8 的 PostgreSQL 与 UUID 字段Django 1.8 新增的ArrayField、HStoreField、UUIDField在 3.1 中得到完整支持这也催生了两个通用的序列化器字段类型serializers.DictField()与serializers.ListField()使 API 能表达和校验更广泛的表示层数据结构。5.1 ListField列表输入校验ListFieldfields.py#L1647校验列表输入通过child关键字参数指定列表中每个元素的校验字段。核心参数包括child、allow_empty默认True、min_length、max_length。声明式用法与实例化用法都支持# 实例化用法每个元素都是 0~100 的整数 scores serializers.ListField(childserializers.IntegerField(min_value0, max_value100)) # 声明式子类 class ScoresField(serializers.ListField): child serializers.IntegerField(min_value0, max_value100)从源码看ListField的to_internal_value()fields.py#L1699-L1709会拒绝字符串与映射类型not_a_list错误并在allow_emptyFalse时拒绝空列表run_child_validation()会逐项执行child.run_validation()把错误按下标收集为{index: detail}的结构。5.2 DictField 与 HStoreField字典输入校验DictFieldfields.py#L1734校验字典输入同样接收child参数指定值字段支持allow_empty。HStoreFieldfields.py#L1811是DictField的子类child默认是CharField(allow_blankTrue, allow_nullTrue)——因为 hstore 扩展把所有值都存为字符串源码中还会断言child必须是CharField实例fields.py#L1814-L1819。在ModelSerializer中PostgreSQL 的ArrayField会自动映射为ListField、HStoreField自动映射为HStoreField。这一映射发生在 serializers.py 的 build_standard_field()当检测到模型字段是postgres_fields.ArrayField时会递归调用build_standard_field(child, model_field.base_field)构造子字段并填入child参数。5.3 UUIDField 主键与超链接序列化器UUIDFieldfields.py#L817让 Django 1.8 新项目可以用 UUID 作为模型主键。这一风格与超链接序列化器天然兼容自动生成如下形式的 URLhttp://example.org/api/purchases/9b1a433f-e90d-4948-848b-300fdc26365d六、ModelSerializer 可扩展的字段映射 API3.0 的序列化器重构没有为ModelSerializer 如何根据模型自动生成字段集提供公开 API。3.1 重新引入了这一 API允许你创建行为不同的 ModelSerializer 基类例如为关系字段换用不同的默认风格。在 rest_framework/serializers.py 中ModelSerializer提供了一组可覆盖的类属性与构建方法它们共同构成字段映射的可扩展点可覆盖的类属性决定用什么字段类serializer_field_mapping模型字段到序列化器字段的默认映射表serializer_related_field正向/反向关系字段的默认类serializer_related_to_fieldto_field指定的关联目标字段的默认类serializer_url_field对象自身 URL 字段的默认类serializer_choice_field带choices的模型字段所用字段类。可覆盖的构建方法决定每个字段怎么生成build_standard_field()serializers.py#L1289普通模型字段build_relational_field()serializers.py#L1350正反向关系build_nested_field()serializers.py#L1368嵌套关系由depth触发build_property_field()serializers.py#L1383模型方法/属性统一映射为只读的ReadOnlyFieldbuild_url_field()serializers.py#L1392对象 URL 字段build_unknown_field()无法识别的字段名默认抛出断言错误。所有构建方法都由统一的build_field()分发入口serializers.py#L1266-L1287调用按模型字段 → 关系字段 → 模型方法/属性 → URL 字段的优先级匹配。定制一个关系字段默认使用 slug 风格的 ModelSerializer 基类只需覆盖serializer_related_fieldclass SlugRelatedModelSerializer(serializers.ModelSerializer): serializer_related_field serializers.SlugRelatedField class Meta: model MyModel详细说明见仓库文档 docs/api-guide/serializers.md#customizing-field-mappings。七、移出核心的组件包OAuth、XML、YAML、JSONP3.1 把若干原先内置于核心的包迁移为可单独安装的第三方包目的是分散维护工作量、让核心保持聚焦同时也让社区对推荐哪个外部包有更大的灵活度例如社区维护良好的 Django OAuth toolkit 自此成为集成 OAuth 的推荐选项。以下包被移出核心需要单独安装OAuth →djangorestframework-oauthXML →djangorestframework-xmlYAML →djangorestframework-yamlJSONP →djangorestframework-jsonp如果你正在使用这些功能迁移成本很低新增一个依赖 修改 import 路径。例如启用 XML 渲染pip install djangorestframework-xml并在REST_FRAMEWORK设置中调整渲染器列表REST_FRAMEWORK { DEFAULT_RENDERER_CLASSES: [ rest_framework.renderers.JSONRenderer, rest_framework.renderers.BrowsableAPIRenderer, rest_framework_xml.renderers.XMLRenderer, ] }注意这一示例中使用的是迁移后新包的 import 路径rest_framework_xml.*而不是旧的rest_framework.renderers.XMLRenderer——这正是公告中修改一些 import 路径所指的内容。八、弃用项清单与迁移路径3.1 将一批此前处于待弃用状态的 API 正式移入已弃用deprecated状态这些 API 在 3.1 中仍可使用但会触发警告并将在 3.2 中被彻底移除。请求对象属性request.DATA、request.FILES、request.QUERY_PARAMS从待弃用转为已弃用。请改用request.data与request.query_params详见 docs/community/3.0-announcement.md 的说明。ModelSerializer的 Meta 选项write_only_fields、view_name、lookup_field转为已弃用。请改用extra_kwargs。例如旧式写法class MySerializer(serializers.ModelSerializer): class Meta: model MyModel fields [id, email, notes, is_admin] write_only_fields [is_admin]新式写法class MySerializer(serializers.ModelSerializer): class Meta: model MyModel fields [id, email, notes, is_admin] extra_kwargs { is_admin: {write_only: True}, }九、3.2 及之后的规划3.1 发布时官方将下一个开发焦点放在了 API 输出的 HTML 渲染上计划包括序列化器的 HTML 表单渲染此前 3.0 中已以模板形式存在、但尚未公开 API 化可浏览 API 内置的过滤控件一种替代性的 admin 风格界面。上述工作被规划为单独的 3.2 发布或拆分为两次发布HTML 表单与过滤控件随 3.2 推出admin 风格界面可能随 3.3 推出。小结3.1 版本的意义在于把 3.0 重构打下的地基兑现为一批可公开使用、可扩展的正式 API以分页类为核心的三类内置分页方案与 header 分页能力、URL 与 Accept Header 双轨并行的版本控制、开箱即用的国际化错误响应、Django 1.8 新字段的完整支持以及可覆盖的 ModelSerializer 字段映射 API。这些能力在今天的源码中依然清晰可见是理解 DRF 分页、版本控制、国际化与序列化字段体系的稳定入口。若需更全面的指引可继续阅读仓库中的 docs/api-guide/pagination.md、docs/api-guide/versioning.md、docs/topics/internationalization.md 与 docs/api-guide/serializers.md。【免费下载链接】django-rest-frameworkWeb APIs for Django. 项目地址: https://gitcode.com/gh_mirrors/dj/django-rest-framework创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进