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)中输出分页信息;
- 分页控件直接在**可浏览 API(browsable 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 | ?page=4(可选page_size) | 常规的页码式分页,适合数据量适中、结果集相对稳定的场景 |
LimitOffsetPagination | ?limit=100&offset=400 | 客户端显式控制"条数 + 偏移",适用于可自由跳跃的结果集 |
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_SIZE;PAGINATE_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: None(settings.py#L53、settings.py#L67),None意味着默认不启用分页,需要开发者显式配置。
一个典型的启用方式如下:
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 100, }2.2 PageNumberPagination:页码分页
PageNumberPagination(pagination.py#L155)使用 Django 标准Paginator处理页码,核心配置属性包括:
page_size:默认页大小,默认取自api_settings.PAGE_SIZE;page_query_param:页码查询参数名,默认'page';page_size_query_param:是否允许客户端指定页大小,默认None(关闭),设为'page_size'即可开启;max_page_size:客户端可请求的最大页大小上限(仅在开启page_size_query_param时生效);last_page_strings:将某些字符串解析为末页,默认('last',),即?page=last跳转到最后一页;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/?page=4", "previous": "http://api.example.org/accounts/?page=2", "results": [] }next/previous链接由get_next_link()/get_previous_link()基于当前请求的绝对 URI 动态生成;当页码为 1 时,previous会直接移除page查询参数而非生成page=1。
2.3 LimitOffsetPagination:limit/offset 分页
LimitOffsetPagination(pagination.py#L334)通过limit(返回条数)与offset(起始下标)两个参数切片查询集,核心属性包括:
default_limit:默认条数,默认取自PAGE_SIZE;limit_query_param/offset_query_param:参数名,默认'limit'/'offset';max_limit:客户端可请求的limit上限,默认None(不限制)。
其实现不依赖 DjangoPaginator,而是直接在查询集上执行切片queryset[self.offset:self.offset + self.limit](pagination.py#L362),因此既支持 QuerySet 也支持普通列表(get_count()会优雅地回退到len(queryset))。请求示例:
http://api.example.org/accounts/?limit=100 http://api.example.org/accounts/?offset=400&limit=1002.4 CursorPagination:面向高频变化结果集的游标分页
CursorPagination(pagination.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)将o(offset)、r(reverse 标志)、p(position)三个 token 编码进?cursor=...参数;decode_cursor()负责反向解码,并对恶意构造的 offset 设定了硬性上限offset_cutoff = 1000,避免产生昂贵的数据库查询(pagination.py#L544)。
关键配置属性:
cursor_query_param:游标参数名,默认'cursor';ordering:排序字段,默认'-created',必须设置为一个稳定、唯一或近似唯一的字段(如'-created'或'pk');page_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}>; rel="next", <{previous_url}>; rel="prev"' elif next_url is not None: link = '<{next_url}>; rel="next"' elif previous_url is not None: link = '<{previous_url}>; rel="prev"' 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()校验逻辑。
内置方案一览:
| 分页类 | 版本来源 | 非法版本时的异常 |
|---|---|---|
URLPathVersioning | URL 路径中的命名参数(如?P<version>) | NotFound |
NamespaceVersioning | Django URL namespace | NotFound |
AcceptHeaderVersioning | Accept: application/json; version=1.0媒体类型参数 | NotAcceptable |
HostNameVersioning | 请求主机名(如v1.example.com) | NotFound |
QueryParameterVersioning | 查询参数(如?version=0.1) | NotFound |
启用方式是在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', namespace='v1')), path('v2/', include('users.urls', namespace='v2')), ]配合如下的超链接序列化器:
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中加入LocaleMiddleware:
MIDDLEWARE_CLASSES = [ ... 'django.middleware.locale.LocaleMiddleware', ]启用按请求国际化后,客户端请求会尽可能尊重Accept-Language头。例如,请求一个不支持的媒体类型:
Request
GET /api/users HTTP/1.1 Accept: application/xml Accept-Language: es-es Host: example.orgResponse
HTTP/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:列表输入校验
ListField(fields.py#L1647)校验列表输入,通过child关键字参数指定列表中每个元素的校验字段。核心参数包括child、allow_empty(默认True)、min_length、max_length。声明式用法与实例化用法都支持:
# 实例化用法:每个元素都是 0~100 的整数 scores = serializers.ListField(child=serializers.IntegerField(min_value=0, max_value=100)) # 声明式子类 class ScoresField(serializers.ListField): child = serializers.IntegerField(min_value=0, max_value=100)从源码看,ListField的to_internal_value()(fields.py#L1699-L1709)会拒绝字符串与映射类型(not_a_list错误),并在allow_empty=False时拒绝空列表;run_child_validation()会逐项执行child.run_validation(),把错误按下标收集为{index: detail}的结构。
5.2 DictField 与 HStoreField:字典输入校验
DictField(fields.py#L1734)校验字典输入,同样接收child参数指定值字段,支持allow_empty。HStoreField(fields.py#L1811)是DictField的子类,child默认是CharField(allow_blank=True, allow_null=True)——因为 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 主键与超链接序列化器
UUIDField(fields.py#L817)让 Django 1.8 新项目可以用 UUID 作为模型主键。这一风格与超链接序列化器天然兼容,自动生成如下形式的 URL:
http://example.org/api/purchases/9b1a433f-e90d-4948-848b-300fdc26365d六、ModelSerializer 可扩展的字段映射 API
3.0 的序列化器重构没有为"ModelSerializer 如何根据模型自动生成字段集"提供公开 API。3.1 重新引入了这一 API,允许你创建行为不同的 ModelSerializer 基类,例如为关系字段换用不同的默认风格。
在 rest_framework/serializers.py 中,ModelSerializer提供了一组可覆盖的类属性与构建方法,它们共同构成字段映射的可扩展点:
- 可覆盖的类属性(决定"用什么字段类"):
serializer_field_mapping:模型字段到序列化器字段的默认映射表;serializer_related_field:正向/反向关系字段的默认类;serializer_related_to_field:to_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):模型方法/属性,统一映射为只读的ReadOnlyField;build_url_field()(serializers.py#L1392):对象 URL 字段;build_unknown_field():无法识别的字段名,默认抛出断言错误。
所有构建方法都由统一的build_field()分发入口(serializers.py#L1266-L1287)调用,按"模型字段 → 关系字段 → 模型方法/属性 → URL 字段"的优先级匹配。定制一个"关系字段默认使用 slug 风格"的 ModelSerializer 基类,只需覆盖serializer_related_field:
class SlugRelatedModelSerializer(serializers.ModelSerializer): serializer_related_field = serializers.SlugRelatedField class Meta: model = MyModel详细说明见仓库文档 docs/api-guide/serializers.md#customizing-field-mappings。
七、移出核心的组件包:OAuth、XML、YAML、JSONP
3.1 把若干原先内置于核心的包迁移为可单独安装的第三方包,目的是分散维护工作量、让核心保持聚焦,同时也让社区对"推荐哪个外部包"有更大的灵活度(例如,社区维护良好的 Django OAuth toolkit 自此成为集成 OAuth 的推荐选项)。
以下包被移出核心,需要单独安装:
- OAuth →
djangorestframework-oauth - XML →
djangorestframework-xml - YAML →
djangorestframework-yaml - JSONP →
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),仅供参考