62. drf之定制返回格式_多表关联反序列化

1. 序列化定制返回格式:source

1.1 基础操作:查询一条

总路由

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/app01/', include('app01.urls')),  # api/v1/的含义是符合restful规范
]

app01的models.py

class Student(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    school = models.CharField(max_length=32)

 序列化组件app01的serializer.py

from rest_framework import serializersclass StudentSerializer(serializers.Serializer):
    # 写要序列化的字段,针对Student表不一定需要将4个字段都序列化
    id = serializers.IntegerField()
    name = serializers.CharField()  
    age = serializers.IntegerField()
    school = serializers.CharField()

app01的views.py

from .models import Student
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import StudentSerializer

class StudentDetailView(APIView):
    def get(self, request, pk):
        # 1.根据id,取出数据
        student = Student.objects.filter(pk=pk).first()
        # 2.序列化  只有instance为序列化,只有data为反序列化的新增
        serializer1 = StudentSerializer(instance=student)
        # 3.返回给前端
        return Response(serializer1.data)

app01的urls.py

from django.urls import path
from .views import StudentDetailView

urlpatterns = [
    path('student/<int:pk>/', StudentDetailView.as_view()),
]

表中添加数据

image

查询一条数据

image

1.2 source的基础使用

[1] 方式一:在序列化组件中,前端展示字段时替换字段名称

需求:在1.1中,查询数据返回时将展示的school字段替换成scho

   用到序列化定制返回格式source

 序列化组件app01的serializer.py

class StudentSerializer(serializers.Serializer):
    # 写要序列化的字段,针对Student表不一定需要将4个字段都序列化
    id = serializers.IntegerField()
    name = serializers.CharField()
    age = serializers.IntegerField()
    scho = serializers.CharField(source='school')

source实现字段的替换

image

[2] 方式二:在模型表中,前端展示字段时给字段添加前缀

(1) 函数版本

app01的models.py

class Student(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    school = models.CharField(max_length=32)

    def add_pre(self):
        return "high_" + self.school

序列化组件app01的serializer.py

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

class StudentSerializer(serializers.Serializer):
    # 写要序列化的字段,针对Student表不一定需要将4个字段都序列化
    id = serializers.IntegerField()
    name = serializers.CharField()
    age = serializers.IntegerField()
    scho = serializers.CharField(source='add_pre')  # 将函数当成字段在使用

image

(2) 数据属性版本

使用property将以上函数伪装成数据属性

app01的models.py

class Student(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    school = models.CharField(max_length=32)

    @property
    def add_pre(self):
        return "high_" + self.school

效果没有变化

image

1.3 source的跨表查询

book表的字段:name、price、出版社publish的名字(即publish的name)

假设在序列化组件中定义book表的序列化类

在book类里需要定义出版社publish字段,如果在字段中不指定source,获取到的是book.publish这个对象,而不是出版社的名字,因此要用到source

publish = serializers.CharField(source='publish.name')

2. 多表关联序列化定制返回格式

2.1 一对多定制返回格式

[1] 准备

app01的models.py

from django.db import models

# Create your models here.

class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)
    city = models.CharField(max_length=32)

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    addr = models.CharField(max_length=32)

迁移至数据库,添加数据

image

image

image

image

[2] 一对多代码实现

出版社和图书是一对多  写个查询图书的接口---返回出版社信息

序列化组件app01的serializer.py

from rest_framework import serializers

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    publish = serializers.CharField(source='publish.addr')  # source可以取publish表的所有字段

app01的views.py

from .models import Book
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import BookSerializer

class BookDetailView(APIView):
    def get(self, request, pk):
        # 1.根据id,取出数据
        book = Book.objects.filter(pk=pk).first()
        # 2.序列化  只有instance为序列化,只有data为反序列化的新增
        serializer1 = BookSerializer(instance=book)
        # 3.返回给前端
        return Response(serializer1.data)

app01的urls.py

from .views import BookDetailView
from django.urls import path

urlpatterns = [
    path('book/<int:pk>/', BookDetailView.as_view()),
]

序列化组件查询publish.addr

image

序列化组件查询publish.city

image

 

[3]需求:通过书籍表的序列化组件获取出版社的所有字段name、addr、city/作者表,放到一个字典或列表中

(1) 方式一:模型表中写函数

在模型表中写一个函数(可以使用:property),函数有返回值(字典、字符串、列表)

序列化组件中,使用DictField、ListField、CharField接收返回值(字典、列表、字符串),但是CharField为纯字符串,不能被调用

字典版本:在模型表中写函数,在书籍表的序列化组件中使用DictField接收

对models.py的book表加以修改

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

    @property  # 将函数属性变为数据属性
    def publish_detail(self):
        return {'name': self.publish.name, 'addr': self.publish.addr, 'city': self.publish.city}

序列化组件app01的serializer.py

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    publish_detail = serializers.DictField()

返回的publish数据为字典

image

列表版本:在书籍表的序列化组件中使用ListField接收

对models.py的book表加以修改

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

    @property  # 将函数属性变为数据属性
    def authors_detail(self):
        info = []
        for i in self.authors.all():
            info.append({"name": i.name, "age": i.age, "addr": i.addr})
        return info

序列化组件app01的serializer.py

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    authors_detail = serializers.ListField()

返回的数据格式为列表

image

 

(2) 方式二:序列化组件中写函数

写一个字段,对应的字段类是:SerializerMethodField
必须对应一个 get_字段名 的函数,函数必须接受一个obj,函数的返回值什么,这个字段对应的值就是什么

obj就是当次序列化得到的book对象,可以看成模型表中的self

models.py的book表保持不变

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

序列化组件app01的serializer.py

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    publish_detail = serializers.SerializerMethodField()

    # 函数的返回值是什么,publish_detail的值就是什么
    def get_publish_detail(self, obj):
        return {'name': obj.publish.name, 'addr': obj.publish.addr, 'city': obj.publish.city}

get后面的名称必须和之前的字段名保持一致,否则报错

image

image

(3) 方式三:子序列化

定义出版社的序列化组件,将出版社序列化组件当作变量的值存放在书籍的序列化组件中

模型表无需任何额外操作

app01的serializer.py中

class PublishSerializer(serializers.Serializer):
    name = serializers.CharField()
    addr = serializers.CharField()
    city = serializers.CharField()

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    publish = PublishSerializer()  # 字段名必须为外键名publish

image

2.2 多对多定制返回格式

[1] 多对多分析

图书和作者是多对多  写个查询图书的接口---返回作者信息

总路由,app01的urls.py、views.py与一对多保持不变

[2] 方式一:模型表中写函数

由于接口定义在图书查询中,因此函数写在图书的模型表中

app01的models.py

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

    # 写返回作者列表的函数
    @property
    def author_l(self):
        l = []
        for i in self.authors.all():  # 获取所有的作者
            l.append({'name': i.name, 'age': i.age, 'addr': i.addr})
        return l

序列化组件app01的serializer.py

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    author_l = serializers.ListField()

id为1的图书对应2个作者,返回的数据格式为列表

image

[3] 方式二:序列化组件中写函数

写一个字段,对应的字段类是:SerializerMethodField
必须对应一个 get_字段名 的函数,函数必须接受一个obj,函数的返回值什么,这个字段对应的值就是什么

obj就是当次序列化得到的book对象,可以看成模型表中的self

由于接口定义在图书查询中,因此函数写在图书的序列化组件中

models.py的book表无需修改

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

序列化组件app01的serializer.py

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    author_l = serializers.SerializerMethodField()

    # 函数的返回值是什么,author_l字段的值就是什么
    def get_author_l(self, obj):
        l = []
        for i in obj.authors.all():  # 获取所有的作者
            l.append({'name': i.name, 'age': i.age, 'addr': i.addr})
        return l

image

[4] 方式三:子序列化

定义作者的序列化组件,将作者序列化组件当作变量的值存放在图书的序列化组件中

模型表无需任何额外操作

app01的serializer.py中,多对多一定要加上many=True

class AuthorSerializer(serializers.ModelSerializer):  # 子序列化组件
    name = serializers.CharField()
    age = serializers.IntegerField()
    addr = serializers.CharField()

class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.IntegerField()
    authors = AuthorSerializer(many=True)  # 字段名必须为外键名authors

image

3. ListField介绍

3.1 概念

反序列化(接收前端数据,data=xxx + is_valid()输入校验),必须写 child
仅序列化输出(只传instance,只读不校验输入),可以不写 child,但不推荐
ListField代表整个字段是一个列表,child规定列表里面每一项是什么类型。

3.2 语法

serializers.ListField(child= 列表内部字段对象)

3.3 代码

[1] 场景1:只用来做序列化输出(只读,只向前端返回,不接收前端传入)

# 可以运行,但不规范
authors_detail = serializers.ListField()

只输出没问题;一旦用于 post 校验直接报错。

规范写法:

authors_detail = serializers.ListField(child=serializers.DictField(), read_only=True)

read_only=True:这个字段只用于序列化输出,反序列化直接忽略该字段,不会用来接收前端数据。

[2]场景2:只用来做前端输入的反序列化校验,不返回给前端(只写)

city = serializers.ListField(child=serializers.IntegerField(), write_only=True)

post 的时候接收列表,serializer.data序列化返回给前端时不会带出这个字段。

3.4 post与get的ListField对比

[1] POST 接口(反序列化,必须 child)

# ✅正确
authors = serializers.ListField(child=serializers.IntegerField())

# ❌错误,post调用is_valid直接报错
authors = serializers.ListField()

[2] GET接口(仅序列化输出)

# 能运行,但是不规范
authors_detail = serializers.ListField()

# ✅规范,read_only标记只读
authors_detail = serializers.ListField(child=serializers.DictField(), read_only=True)

3.5 常用参数

参数作用
child 列表内部元素字段,反序列化必填
allow_empty=True 是否允许空列表[],默认 True;设 False 不允许空数组
read_only=True 仅输出,不接收前端传入
write_only=True 仅接收输入,不输出

3.6 总结

ListField()括号内不加参数,只有纯读场景可以运行;只要做校验 is_valid,一定报错

 

4. 多表关联反序列化

4.1 多表关联反序列化之新增 

[1] 需求:新增图书

book表的字段为

image

前端传给后端的validated_data为{'name':'信息论', price:666, 'publish_id':1, 'authors':[1, 2]}

前端传过来的publish_id要与数据库中的字段保持一致

 

app01的models.py

from django.db import models

# Create your models here.

class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)
    city = models.CharField(max_length=32)

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    addr = models.CharField(max_length=32)

class Student(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    school = models.CharField(max_length=32)

app01的urls.py

from .views import BookView
from django.urls import path

urlpatterns = [
    path('book/', BookView.as_view()),
]

[2] 错误示范

(1) 报错代码

视图函数中序列化返回给前端,序列化组件中serializers.ListField( )不加参数

序列化组件app01的serializer.py

from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.Serializer):
    # 前端传入的图书数据格式为{'name':'信息论', price:666, 'publish_id ':1, 'authors':[1, 2]}
    name = serializers.CharField()
    price = serializers.IntegerField()
    publish_id = serializers.IntegerField()  # 数据库的字段名为publish_id
    authors = serializers.ListField()

    # 重写create函数
    # validated_data是字典,即前端传入经过校验的数据(以上字典中的内容),保存到指定的表中
    # **validated_data对数据进行解包
    def create(self, validated_data):
        # 为了和数据库中book表对应,需要剔除publish和authors
        # 1.前端数据第一部分保存到book表
        authors = validated_data.pop('authors')  # authors已从validated_data剔除
        book = Book.objects.create(**validated_data)  # 一对多反序列化,书与出版社
        # 2.前端数据第二部分保存到关联表book_authors,注意create与add的用法
        book.authors.add(*authors)  # 多对多反序列化,书与作者
        return book

app01的views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import BookSerializer

class BookView(APIView):
    def post(self, request):
        # 1.取出前端传入的请求体中的数据(json、urlencoded、form-data)
        # 2.前端传入的图书数据格式为{'name':'信息论', price:666, 'publish_id':1, 'authors':[1, 2]}
        #   外键字为id,非实际值
        # 3.括号参数只有instance为序列化,只有data为反序列化的新增
        # 4.序列化组件使用前端传入的数据request.data实例化得到对象
        serializer1 = BookSerializer(data=request.data)
        # 5.反序列化校验、保存到数据库、返回给前端
        # 6.不知道要保存到哪个表中,要在序列化组件中重写create方法
        #   serializer1.save如果是新增,会触发serializer.create方法的运行
        if serializer1.is_valid():  # 反序列化(输入校验)
            serializer1.save()
            return Response(serializer1.data)  # 序列化(输出)  读取data,返回给前端
        else:
            return Response(serializer1.errors)

image

image

(2) 报错原因

校验阶段:serializer.is_valid()拿 request.data 做反序列化(输入校验),ListField 不知道列表里面是什么类型,校验失败;ListField必须声明内部元素类型,ListField()括号内不加参数序列化校验直接报错,is_valid()返回 False。
就算绕过校验,serializer1.data把 book 实例做序列化输出:同样ListField没有参数,DRF 不知道列表authors里的数据该怎么序列化格式化输出,执行return Response(serializer1.data)时直接抛出异常。
简而言之:ListField()不能空参,必须加上参数,否则校验、序列化输出 (serializer.data) 两步都会出错。
加上child=serializers.IntegerField(),告诉 DRF:列表内部是整数,输入校验、输出序列化两者全部正常工作。

(3) 结论

只做序列化输出(读),不做反序列化(数据接收校验),ListField( )不加参数可以运行;
serializers.ListField( )如果既要接收前端提交数据 (反序列化),又要序列化输出(读),ListField( )不加参数会报错。

[3] 修正

(1) 返回字符串

将serializer1.data 改为普通字符串返回,代码也能运行

class BookView(APIView):
    def post(self, request):
        serializer1 = BookSerializer(data=request.data)
        if serializer1.is_valid():  # 反序列化(输入校验)
            serializer1.save()
            return Response("ok") 
        else:
            return Response(serializer1.errors)

调用 is_valid() 不会抛出 Python 异常导致代码崩溃,而是直接返回 False,错误放在 serializer1.errors(低于DRF3.10的版本输入校验时ListField 强制要求有参数,DRF3.10以后的版本不强制要求有参数)

1.serializer1.is_valid()运行,遇到 authors = serializers.ListField(), 没有参数
2.ListField 反序列化校验失败,给 errors 增加错误信息,函数返回 False
3.进入 else 分支,返回错误信息,永远不会运行到 return Response ("ok")
关键点:
is_valid() 一定会运行,一定会检测到 ListField 缺少参数,但是不会崩溃,只是返回 False;
return Response("ok") 是 if 内部代码,校验失败根本不会运行。

(2) 给serializers.ListField加上参数

class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.IntegerField()
    publish_id = serializers.IntegerField()
    # 只改这一行,加上参数
    authors = serializers.ListField(child=serializers.IntegerField())

    def create(self, validated_data):
        authors = validated_data.pop('authors')
        book = Book.objects.create(**validated_data)
        book.authors.add(*authors)
        return book

加上child=serializers.IntegerField(),告诉 DRF:列表内部是整数,输入校验、输出序列化理论上可以运行,输出序列化还存在数据格式的问题

serializer1.data序列化输出时,直接拿模型authors多对多字段(是ManyRelatedManager对象),序列化器顶层没有定义authors输出规则,DRF 原生序列化无法直接迭代ManyRelatedManager管理器,触发TypeError: 'ManyRelatedManager' object is not iterable。
运行链路:
1.serializer.save()正常执行入库、多对多绑定,数据库无问题。
2.运行return Response(serializer1.data)读取序列化输出。
3.序列化器BookSerializer里:authors字段是只用于入参反序列化的ListField(用来接收前端传入 ID 数组做新增);
序列化输出(正向序列化,模型转 JSON)时,DRF 尝试用这个ListField去读取模型实例的book.authors;
book.authors是ManyRelatedManager,不是普通列表,ListField内部尝试遍历直接报错。
总结:同一个authors字段,入参用来接收 ID 列表,出参要读取多对多管理器,类型不匹配直接迭代报错。

(3) 直接移除序列化输出的 authors 字段,返回不要作者列表

class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.IntegerField()
    publish_id = serializers.IntegerField()
    # 只改这一行,加上参数
    authors = serializers.ListField(child=serializers.IntegerField(), write_only=True)

    def create(self, validated_data):
        authors = validated_data.pop('authors')
        book = Book.objects.create(**validated_data)
        book.authors.add(*authors)
        return book

只返回书籍基础字段,不会处理多对多字段,无报错。
write_only=True:该字段仅反序列化(接收前端数据)生效,正向序列化.data直接跳过该字段,不会去读取模型的authors关联管理器,彻底规避报错。

image

image

4.2 序列化组件传值补充

如果前端传的字段比序列化组件和数据库中的字段要多,多了个addr,{"name":"三国演义","price":19,"publish_id":1,"authors":[1,2],"addr":"上海"}
到了序列化组件create函数中,validated_data中得到的数据也不包含该字段addr:{"name":"三国演义","price":19,"publish_id":1,"authors":[1,2]}

4.3 多表关联反序列化之修改

[1] 前言

image

image

image

image

以book模型表为切入点修改数据

  修改book的基础数据

  修改book的出版社

  修改book的作者

[2] 代码

app01的urls.py

from .views import BookDetailView
from django.urls import path

urlpatterns = [
    path('book/<int:pk>/', BookDetailView.as_view()),
]

app01的views.py

from .models import Book
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import BookSerializer

class BookDetailView(APIView):
    def put(self, request, pk):
        # 前端传过来用于修改的数据的格式{'name':'信息论', price:666, 'publish_id':1, 'authors':[1, 2]}
        # 1.根据id,查询需要修改的对象
        book = Book.objects.filter(pk=pk).first()
        # 2.获取前端传入的数据,使用data数据对instance对象进行修改
        serializer1 = BookSerializer(data=request.data, instance=book)
        # 3.校验数据
        # 4.保存到数据库中
        if serializer1.is_valid():
            # 需要在序列化组件中重写update方法
            serializer1.save()
            return Response(serializer1.data)
        else:
            return Response(serializer1.errors)

序列化组件app01的serializer.py

class BookSerializer(serializers.Serializer):
    # 序列化组件在进行反序列化校验时,以下字段需要和前端传入的字段一一对应
    # 前端传入的图书数据格式为{'name':'信息论', price:666, 'publish_id ':1, 'authors':[1, 2]}
    name = serializers.CharField()
    price = serializers.IntegerField()
    publish_id = serializers.IntegerField()  # 数据库的字段名为publish_id
    authors = serializers.ListField(child=serializers.IntegerField(), write_only=True) # 只写

    # 重写update函数
    def update(self, instance, validated_data):
        """
        :param instance: 待修改的对象
        :param validated_data:前端传入,校验过后的数据
        :return:
        """
        # 1.为了和数据库中book表对应,需要剔除authors
        authors = validated_data.pop('authors')  # authors已从validated_data剔除
        # 2.修改book表
        for k in validated_data:
            # 3.利用反射方法  等同于instance.k=validated_data.get(k)
            setattr(instance, k, validated_data.get(k))
        # 4.修改中间表book_authors
        instance.authors.set(authors)  # set将原来数据先有移除,再新增,等同于clear+add(*authors)
        instance.save()
        return instance  # 返回修改的对象

对book表id为3的记录进行修改

image

image

image

 

posted @ 2026-08-09 03:30  pythondjango  阅读(2)  评论(0)    收藏  举报