☰
Django Ninja 查询参数(Query Parameters)完全指南:类型转换、默认值与 Schema 封装
2026/9/25 13:12:32 网站建设 项目流程
  • 后端
  • API设计

【免费下载链接】django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs

项目地址:https://gitcode.com/gh_mirrors/dj/django-ninja
点击查看免费下载

本篇指南聚焦 Django Ninja 中GET 查询参数(query parameters)的声明、类型转换、校验与文档化机制。你将学会如何让函数签名中的普通参数自动成为查询参数,掌握必填/可选参数的声明方式、bool/date/int等类型的转换规则,以及如何使用Query[...]+ Schema 对复杂过滤条件进行结构化封装。读完即可在自己的 API 中写出类型安全、自动生成 OpenAPI 文档、可被编辑器与测试框架完整感知的查询参数层。

查询参数的本质:函数签名即参数声明

在 Django Ninja 中,路由处理函数除了第一个request参数外,所有不属于路径参数(path parameters)的函数参数,都会被自动解释为查询参数。这与 FastAPI 的设计一脉相承:你不需要显式声明"这是一个 query 参数",类型注解和默认值本身就承载了全部声明信息。

以 docs/src/tutorial/query/code01.py 为例:

weapons = ["Ninjato", "Shuriken", "Katana", "Kama", "Kunai", "Naginata", "Yari"] @api.get("/weapons") def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset: offset + limit]

访问如下 URL:

http://localhost:8000/api/weapons?offset=0&limit=10

框架会从查询串中取出offset与limit,按注解转换为int,经校验后传入函数。

这一自动推断机制的核心实现位于 ninja/signature/details.py 的_get_param_type方法。其判定优先级非常清晰:

  1. 参数类型是Param子类(如Query(...)、Path(...)),直接用该定义;
  2. 参数名出现在路径模板中,则归为路径参数Path(...);
  3. 参数是集合类型或 Pydantic 模型,归为Body(...);
  4. 其余所有情况一律归为Query(...)——这正是"未标注即查询参数"规则的源码依据。

从源码结构可以推断,这一优先级设计使得路径参数、查询参数、请求体三类数据源能够共存于同一函数签名,互不冲突。

为什么值得使用查询参数注解

原文档指出,查询参数与路径参数享受同样的四重收益:

  • 编辑器支持(Editor support):IDE 能基于函数签名给出参数提示、类型补全与重构支持,杜绝魔法字符串;
  • 数据解析(Data "parsing"):框架自动把 URL 查询串中的字符串解析为注解声明的 Python 类型;
  • 数据校验(Data validation):类型不匹配或缺失必填项时自动返回 422 校验错误;
  • 自动文档(Automatic documentation):参数会被自动录入 Swagger UI / ReDoc 的 OpenAPI schema,前端与调用方可直接查看。

一个关键默认行为必须牢记:默认情况下,GET 参数在 HTTP 层全部是字符串;只有当你为函数参数加上类型注解时,Django Ninja 才会将其转换为对应类型并执行校验。若不加注解,参数将按str处理:

@api.get("/weapons") def list_weapons(request, limit, offset): # type(limit) == str # type(offset) == str

这一行为的底层逻辑同样在_get_param_type中:当注解缺失时(annotation == self.signature.empty),框架会回退为str(见 ninja/signature/details.py)。

默认值:让查询参数"可省略"

查询参数不属于路径的固定组成部分,因此天然是可选的,可以设置默认值:

@api.get("/weapons") def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset : offset + limit]

这里默认offset=0、limit=10。于是访问:

http://localhost:8000/api/weapons

等价于访问:

http://localhost:8000/api/weapons?offset=0&limit=10

而访问:

http://localhost:8000/api/weapons?offset=20

时,函数内部拿到的参数值是:

  • offset=20(URL 显式设置的值)
  • limit=10(默认值兜底)

测试目录 tests/main.py 中/query/int/default路由即验证了该行为:get_query_type_optional_10(request, query: int = 10)在请求/query/int/default时返回"foo bar 10",在请求/query/int/default?query=50时返回"foo bar 50"(见 tests/test_query.py)。

必填与可选参数:遵循 Python 函数参数语义

声明查询参数必填还是可选,与声明普通 Python 函数参数完全一致——没有默认值的参数就是必填的:

weapons = ["Ninjato", "Shuriken", "Katana", "Kama", "Kunai", "Naginata", "Yari"] @api.get("/weapons/search") def search_weapons(request, q: str, offset: int = 0): results = [w for w in weapons if q in w.lower()] return results[offset : offset + 10]

在上述例子中,Django Ninja 会始终校验 GET 请求必须携带q参数,而offset是可选整数(缺省为 0)。

源码中,必填/可选是通过Query(...)与Query(default)区分的:...(Ellipsis)表示必填,具体值表示默认值(见 ninja/signature/details.py)。测试用例 tests/test_query.py 给出了完整的行为矩阵:

请求路径状态码说明
/query422缺少必填的query参数,返回missing校验错误
/query?query=baz200正常返回
/query?not_declared=baz422未声明的参数也会触发缺失校验(声明了query但没传)
/query/optional200可选参数可缺省
/query/int?query=42.5422int注解拒绝浮点字符串,返回int_parsing错误
/query/int?query=foo422非数字字符串同样被拒绝

错误响应体采用标准格式,例如:

{ "detail": [ { "type": "missing", "loc": ["query", "query"], "msg": "Field required" } ] }

这印证了"类型注解即校验规则"的设计:q: str缺失即报missing,query: int收到无法解析的字符串即报int_parsing。

GET 参数类型转换规则

声明多个不同类型的参数时,转换规则各不相同:

from datetime import date @api.get("/example") def example(request, s: str = None, b: bool = None, d: date = None, i: int = None): return [s, b, d, i]
  • str类型:原样透传,不做任何转换;
  • int/float:解析为对应数值类型,无法解析时返回 422(如/query/int?query=42.5对int注解报错);
  • bool类型:下面列出的任意写法(大小写变体同样有效),函数收到的b均为布尔值True;其余写法一律视为False:
http://localhost:8000/api/example?b=1 http://localhost:8000/api/example?b=True http://localhost:8000/api/example?b=true http://localhost:8000/api/example?b=on http://localhost:8000/api/example?b=yes
  • date类型:既支持标准日期字符串,也支持 unix 时间戳整数:
http://localhost:8000/api/example?d=1577836800 # same as 2020-01-01 http://localhost:8000/api/example?d=2020-01-01

上述转换发生在 Pydantic 校验层。Django Ninja 在请求进入时把查询串交给Parser,其中parse_querydict负责把MultiValueDict(Django 的查询字典)转换为普通字典(见 ninja/parser.py),随后由动态构建的QueryModel通过 Pydanticmodel_validate完成类型转换与校验(见 ninja/params/models.py)。整个过程对开发者透明:你只声明类型,转换、校验、报错全由框架完成。

列表型查询参数:重复键的聚合

查询串中可以携带重复键,例如?query=a&query=b&query=c。Django 的QueryDict天然支持多值,Django Ninja 的Parser.parse_querydict会通过data.getlist(key)聚合这些值(见 ninja/parser.py)。配合List注解即可直接接收列表:

from typing import List from ninja import Query @router.get("/query/list") def get_query_list(request, query: List[str] = Query(...)): return ",".join(query)

访问/query/list?query=a&query=b&query=c将得到"a,b,c"。还可以声明可空列表:

@router.get("/query/list-optional") def get_query_optional_list(request, query: Optional[List[str]] = Query(None)): if query: return ",".join(query) return query

测试矩阵覆盖了列表参数的必填/可选行为(见 tests/test_query.py)。在源码层面,detect_collection_fields会识别注解中的集合类型并标记为"列表字段",parse_querydict据此决定使用getlist还是单值提取(见 ninja/signature/details.py)。

使用 Schema 封装查询参数

当查询参数增多时,逐个声明函数参数会让签名臃肿。Django Ninja 支持把查询参数封装进一个 Pydantic Schema,用Query[...]泛型标记:

import datetime from typing import List from pydantic import Field from ninja import Query, Schema class Filters(Schema): limit: int = 100 offset: int = None query: str = None category__in: List[str] = Field(None, alias="categories") @api.get("/filter") def events(request, filters: Query[Filters]): return {"filters": filters.dict()}

关键点说明:

  • Query[Filters]表示"从查询串解析出Filters实例",函数内部通过filters.limit、filters.offset等属性访问各字段;
  • alias映射外部参数名:URL 中使用categories(如/filter?categories=a&categories=b),Schema 字段名却是category__in——这在调用方参数名与内部实现解耦时非常有用,例如对接前端固定命名或 Django ORM 的__in查找语法;
  • Schema 内同样遵循默认值/必填语义:limit: int = 100可选且默认 100,offset: int = None可选且默认 None;
  • 从源码看,Schema 型查询参数通过_args_flatten_map的扁平化映射把 Schema 字段展开为查询键,并在QueryModel.get_request_data中经parse_querydict收集数据后交给 Pydantic 校验(见 ninja/signature/details.py 与 ninja/params/models.py)。

嵌套的 Pydantic 模型同样支持扁平化展开,字段会映射为扁平键名(FLATTEN_PATH_SEP分隔,见 ninja/signature/details.py),这为组织超多参数的复杂查询提供了可扩展方案。

参数级约束与别名

除了 Schema 封装,还可以用Query()函数在单个参数上施加更细的约束(见 ninja/params/functions.py 与 ninja/params/models.py):

@api.get("/search") def search( request, q: str = Query(..., min_length=3, max_length=50, description="搜索关键词"), page: int = Query(1, ge=1, description="页码,从 1 开始"), size: int = Query(10, ge=1, le=100, description="每页条数"), sort: str = Query("name", pattern="^(name|date|rating)$"), categories: List[str] = Query(None, alias="cat"), ): ...

可用约束包括:gt/ge/lt/le(数值范围)、min_length/max_length(字符串长度)、pattern(正则)、alias(参数别名)、title/description/example/examples(OpenAPI 文档展示)、deprecated(标记废弃)以及include_in_schema(是否显示在文档中)。这些约束既作用于运行时校验,也会同步写入自动生成的 OpenAPI schema,让文档与校验规则永远一致。

自动化文档与复杂过滤

由于查询参数声明完全基于类型注解,Swagger UI / ReDoc 会自动为每个查询参数生成参数说明、类型、默认值、必填标记与校验约束,无需手写任何文档代码。这正是原文档强调的"Automatic documentation"收益在 OpenAPI 层的落地。

对于更复杂的过滤场景(如组合多个条件、支持__in等 Django ORM 风格查找),推荐进一步阅读 过滤指南(Filtering);查询参数配合过滤功能,可构建出既类型安全又文档完备的检索 API。

小结

场景写法行为
基础查询参数def f(request, limit: int = 10)自动识别为查询参数,缺省用默认值
必填参数def f(request, q: str)缺失返回 422
无注解参数def f(request, x)按str处理
布尔参数b: bool1/True/true/on/yes(含大小写变体)→True
日期参数d: date支持2020-01-01或 unix 时间戳
列表参数q: List[str]重复键自动聚合
Schema 封装filters: Query[Filters]结构化访问 + alias 映射外部名
单参数约束q: str = Query(..., min_length=3)运行时校验 + OpenAPI 文档同步

Django Ninja 的查询参数体系完全由类型注解驱动:解析入口是 ninja/parser.py 的parse_querydict,参数分类逻辑在 ninja/signature/details.py 的_get_param_type,数据模型在 ninja/params/models.py 的QueryModel,而完整的必填/可选/类型/列表行为矩阵可在 tests/test_query.py 中查阅验证。掌握这套机制,你就能以最小的样板代码写出校验严格、文档自动生成、前后端契约清晰的查询参数层。

  • 后端
  • API设计

【免费下载链接】django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs

项目地址:https://gitcode.com/gh_mirrors/dj/django-ninja
点击查看免费下载

相关推荐

上一篇:终极解决方案:3分钟彻底解决Windows VC运行库缺失问题
下一篇:mistral.rs 运行 Gemma 3n:Python SDK 多模态推理实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询