跳转至

Response Schema 响应模式

Django Ninja 允许你为验证和文档目的定义响应的模式。

想象一下,你需要创建一个创建用户的 API 操作。 input 参数将是 username+password, 但这个操作的 输出 应该是 id+username (不包含 密码).

让我们创建输入模式:

from ninja import Schema

class UserIn(Schema):
    username: str
    password: str


@api.post("/users/")
def create_user(request, data: UserIn):
    user = User(username=data.username) # User 类来自 django auth.User
    user.set_password(data.password)
    user.save()
    # ... return ?

现在让我们定义输出模式,并将其作为 response 参数传递给 @api.post 装饰器:

from ninja import Schema

class UserIn(Schema):
    username: str
    password: str


class UserOut(Schema):
    id: int
    username: str


@api.post("/users/", response=UserOut)
def create_user(request, data: UserIn):
    user = User(username=data.username)
    user.set_password(data.password)
    user.save()
    return user

Django Ninja 将使用 response 模式来:

  • 将输出数据转换为声明的模式
  • 验证数据
  • 添加一个 OpenAPI 模式定义
  • 它将被自动文档系统使用
  • 并且,最重要的是,它将 限制输出数据 为仅在模式中定义的字段。

嵌套对象

也经常有需要返回带有一些嵌套/子对象的响应。

想象我们有一个带有 User 外键的 Task Django 模型:

from django.db import models

class Task(models.Model):
    title = models.CharField(max_length=200)
    is_completed = models.BooleanField(default=False)
    owner = models.ForeignKey("auth.User", null=True, blank=True)

现在让我们输出所有任务,并为每个任务输出一些关于用户的字段。

from typing import List
from ninja import Schema

class UserSchema(Schema):
    id: int
    first_name: str
    last_name: str

class TaskSchema(Schema):
    id: int
    title: str
    is_completed: bool
    owner: UserSchema = None  # ! None - 标记使它非必填


@api.get("/tasks", response=List[TaskSchema])
def tasks(request):
    queryset = Task.objects.select_related("owner")
    return list(queryset)

如果你执行这个操作,你应该得到这样的响应:

[
    {
        "id": 1,
        "title": "Task 1",
        "is_completed": false,
        "owner": {
            "id": 1,
            "first_name": "John",
            "last_name": "Doe",
        }
    },
    {
        "id": 2,
        "title": "Task 2",
        "is_completed": false,
        "owner": null
    },
]

别名

与其使用嵌套响应,您可能希望仅展平响应输出。 Ninja 的 Schema 对象扩展了 Pydantic 的 Field(..., alias="") 格式,以处理带点的响应。

使用上面的模型,让我们创建一个模式,该模式仅内联包含任务所有者的名字,并且还使用 completed 而不是 is_completed:

from ninja import Field, Schema


class TaskSchema(Schema):
    id: int
    title: str
    # 第一个 Field 参数是默认值,对于必填字段使用...。
    completed: bool = Field(..., alias="is_completed")
    owner_first_name: str = Field(None, alias="owner.first_name")

别名也支持 Django 模板语法变量访问:

class TaskSchema(Schema):
    last_message: str = Field(None, alias="message_set.0.text")
class TaskSchema(Schema):
    type: str = Field(None)
    type_display: str = Field(None, alias="get_type_display") # callable 将要被执行

解析器

您还可以通过基于字段名称的解析方法创建计算字段。

该方法必须接受一个参数,该参数将是模式要解析的对象。

当将解析器创建为标准方法时,self 使您能够访问模式中的其他经过验证和格式化的属性。

class TaskSchema(Schema):
    id: int
    title: str
    is_completed: bool
    owner: Optional[str] = None
    lower_title: str

    @staticmethod
    def resolve_owner(obj):
        if not obj.owner:
            return
        return f"{obj.owner.first_name} {obj.owner.last_name}"

    def resolve_lower_title(self, obj):
        return self.title.lower()

访问额外上下文

Pydantic v2 允许您处理传递给序列化器的额外上下文。在以下示例中,您可以有一个解析器,该解析器从传递的 context 参数中获取请求对象:

class Data(Schema):
    a: int
    path: str = ""

    @staticmethod
    def resolve_path(obj, context):
        request = context["request"]
        return request.path

如果您将此模式用于传入请求 - 请求对象将自动传递到上下文。

您也可以传递自己的上下文:

data = Data.model_validate({'some': 1}, context={'request': MyRequest()})

返回查询集

在上一个示例中,我们专门将查询集转换为列表(并在评估期间执行 SQL 查询)。

您可以避免这种情况并返回查询集作为结果,它将自动评估为列表:

@api.get("/tasks", response=List[TaskSchema])
def tasks(request):
    return Task.objects.all()

警告

如果您的操作是异步的,此示例将不起作用,因为 ORM 查询需要安全地调用。

@api.get("/tasks", response=List[TaskSchema])
async def tasks(request):
    return Task.objects.all()

有关更多信息,请参阅 异步支持

文件字段和图像字段

Django Ninja 默认将文件和图像 (使用 FileFieldImageField声明) 转换成 字符串 URL.

一个例子:

class Picture(models.Model):
    title = models.CharField(max_length=100)
    image = models.ImageField(upload_to='images')

如果您需要将图像字段输出到响应中,为其声明一个模式如下:

class PictureSchema(Schema):
    title: str
    image: str

一旦您将此输出到响应中,将为每个对象自动生成 URL:

{
    "title": "Zebra",
    "image": "/static/images/zebra.jpg"
}

多个响应模式

有时您需要定义多个响应模式。

例如,在身份验证的情况下,您可以返回:

  • 200 successful -> token
  • 401 -> Unauthorized
  • 402 -> Payment required
  • 等等..

实际上, OpenAPI 规范 允许您传递多个响应模式。

您可以将一个字典传递给 response 参数,其中:

  • 键是响应码
  • 值是该代码的 schema 模式

此外,当您返回结果时 - 您还必须传递一个状态代码以告诉 Django Ninja 应该使用哪个模式进行验证和序列化。

一个示例:

class Token(Schema):
    token: str
    expires: date

class Message(Schema):
    message: str


@api.post('/login', response={200: Token, 401: Message, 402: Message})
def login(request, payload: Auth):
    if auth_not_valid:
        return 401, {'message': 'Unauthorized'}
    if negative_balance:
        return 402, {'message': 'Insufficient balance amount. Please proceed to a payment page.'}
    return 200, {'token': xxx, ...}

多种响应代码

在前面的示例中,你看到我们基本上将 Message 模式重复了两次:

...401: Message, 402: Message}

为避免这种重复,你可以对一个模式使用多种响应代码:

...
from ninja.responses import codes_4xx


@api.post('/login', response={200: Token, codes_4xx: Message})
def login(request, payload: Auth):
    if auth_not_valid:
        return 401, {'message': 'Unauthorized'}
    if negative_balance:
        return 402, {'message': 'Insufficient balance amount. Please proceed to a payment page.'}
    return 200, {'token': xxx, ...}

Django Ninja 带有以下 HTTP 代码:

from ninja.responses import codes_1xx
from ninja.responses import codes_2xx
from ninja.responses import codes_3xx
from ninja.responses import codes_4xx
from ninja.responses import codes_5xx

你也可以使用 frozenset 创建自己的范围:

my_codes = frozenset({416, 418, 425, 429, 451})

@api.post('/login', response={200: Token, my_codes: Message})
def login(request, payload: Auth):
    ...

空响应

有些响应,如 204 无内容,没有主体。 要表示响应主体为空,用 None 而不是 Schema 模式来标记 response 参数 :

@api.post("/no_content", response={204: None})
def no_content(request):
    return 204, None

错误响应

有关更多信息,请查看 处理错误

Self-referencing schemes

  • 有时你需要创建一个模式,该模式引用自身或树状结构对象。

  • 要做到这一点,你需要:

  • 将你的模式类型用引号括起来

  • 使用 update_forward_refs 方法应用自引用类型

class Organization(Schema):
    title: str
    part_of: 'Organization' = None     #!! 注意这里的类型需要放在引号中!! !!


Organization.update_forward_refs()  # !!! 这一行很重要


@api.get('/organizations', response=List[Organization])
def list_organizations(request):
    ...

create_schema() 生成的自引用模式

为了能够使用通过 create_schema() 生成的模式的 update_forward_refs() 方法 , 类的 "name" 需要在我们的命名空间中。 在这种情况下,将 name 参数传递给 create_schema() 非常重要。

UserSchema = create_schema(
    User,
    name='UserSchema',  # !!! 这对 update_forward_refs() 很重要
    fields=['id', 'username']
    custom_fields=[
        ('manager', 'UserSchema', None),
    ]
)
UserSchema.update_forward_refs()

在视图之外进行序列化

可以通过在模式对象上使用 .from_orm() 方法直接在代码中对你的对象进行序列化。

考虑以下模型:

class Person(models.Model):
    name = models.CharField(max_length=50)

可以使用以下模式进行访问:

class PersonSchema(Schema):
    name: str

可以使用模式上的 .from_orm() 方法直接进行序列化。 一旦你有了模式对象的实例, .dict().json() 方法允许你获得字典输出和字符串 JSON 版本。

>>> person = Person.objects.get(id=1)
>>> data = PersonSchema.from_orm(person)
>>> data
PersonSchema(id=1, name='Mr. Smith')
>>> data.dict()
{'id':1, 'name':'Mr. Smith'}
>>> data.json()
'{"id":1, "name":"Mr. Smith"}'

多个条目:或查询集(或列表)

>>> persons = Person.objects.all()
>>> data = [PersonSchema.from_orm(i).dict() for i in persons]
[{'id':1, 'name':'Mr. Smith'},{'id': 2, 'name': 'Mrs. Smith'}...]

Django HTTP 响应

也可以返回常规的 django http 响应:

from django.http import HttpResponse
from django.shortcuts import redirect


@api.get("/http")
def result_django(request):
    return HttpResponse('some data')   # !!!!


@api.get("/something")
def some_redirect(request):
    return redirect("/some-path")  # !!!!

大功告成

继续下一章节! 调整响应

评论


本文总阅读量