DRF之序列化类介绍及使用

一、序列化类

1、介绍

序列化组件在 DRF 中扮演着重要的角色,帮助开发者轻松地定义数据的序列化和反序列化过程,同时提供了数据验证、字段定义、嵌套序列化等功能。通过使用序列化组件,您可以更好地控制 API 的数据输入和输出,确保数据的有效性和一致性。

serializers.Serializer 是基本的序列化组件,用于定义如何将数据转换为可传输的格式以及如何反序列化数据。开发者可以通过定义字段和序列化逻辑来定制 Serializer 类。

2、作用

(1)字段定义

  • 序列化类允许开发者定义各种字段,包括模型字段、自定义字段和计算字段等,以控制数据的序列化和反序列化过程。

(2)数据验证

  • 序列化类提供了数据验证功能,可以确保客户端提交的数据符合预期的格式和规则,保证数据的有效性和一致性。

(3)嵌套序列化

  • 序列化类支持嵌套序列化,允许处理复杂的数据结构和关联关系,使得在 API 中展示相关数据变得更加灵活。

(4)自定义序列化逻辑

  • 开发者可以在序列化类中实现自定义的序列化逻辑,包括数据转换、计算字段值、条件逻辑等,以满足特定的业务需求。

3、主要方法

(1)to_representation(self, instance)

  • 该方法用于将模型实例转换为序列化后的数据表示形式。在这个方法中,开发者可以定义如何将模型数据转换为序列化后的格式。

(2)to_internal_value(self, data)

  • 该方法用于将客户端提交的数据转换为内部数值表示形式,通常用于反序列化过程中。在这个方法中,数据通常会被验证和转换为适合存储或处理的形式。

(3)create(self, validated_data)

  • 如果序列化类用于创建新的模型实例,开发者可以实现该方法来定义如何创建新的实例。在该方法中,通常会使用传入的验证过的数据来创建新的对象。

(4)update(self, instance, validated_data)

  • 如果序列化类用于更新现有的模型实例,开发者可以实现该方法来定义如何更新现有的实例。在该方法中,通常会使用传入的验证过的数据来更新现有对象。

(5)validate_<field_name>(self, value)

  • 对于每个字段,开发者可以定义一个以 validate_<field_name> 命名的方法,用于对特定字段进行额外的验证。这些方法通常用于执行字段级别的验证逻辑。

4、小结

  • 序列化
    • 序列化器会把模型对象(queryset,单个对象)转换成字典
    • 经过response以后变成json字符串
  • 反序列化
    • 把客户端发送过来的数据,经过request.data以后变成字典
    • 序列化器可以把字典转成模型
  • 反序列化
    • 可以在反序列化保存到数据库之前,做数据库校验

二、序列化应用场景

1、使用步骤

  • 写一个序列化的类,并继承Serializer
  • 在类中写要序列化的字段, 序列化字段类(有很多, 常用的就几个, 等同于models中的字段类)
  • 在视图类中使用, 导入序列化类把要序列化的对象传入, 得到序列化对象
  • 可以通过[序列化类的对象].data获取序列化后的字典

2、创建模型表

  • models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
publish = models.CharField(max_length=32)

3、创建序列化文件

  • serializer.py
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.IntegerField()
publish = serializers.CharField()

4、 视图函数中使用序列化

  • view.py
from .serializer import BookSerializer
from rest_framework.response import Response
class BookView(APIView):
def get(self, request):
obj_list = Book.objects.all()
# 要序列化的Queryset对象,但是如果是多条数据,必须加 many=True
serializer=BookSerializer(instance=obj_list,many=True) # instance=None, data=empty
return Response({'code': 100, 'msg': '查询成功', 'results':serializer.data})
class BookDetailView(APIView):
def get(self, request, pk):
obj = Book.objects.filter(pk=pk).first()
# 单条,不用传many=True,非要传many=Fasle
serializer=BookSerializer(instance=obj)
return Response({'code':100,'msg':'成功','result':serializer.data})

4、路由

  • urls.py
from django.urls import path
from app01.views import BookView, BookDetailView
urlpatterns = [
path('books/', BookView.as_view()),
path('books/<int:pk>', BookDetailView.as_view()),
]

5、测试

  • 序列化类中写了三个序列化字段,展示了三个序列化的字段

image

  • 注释掉publish之后,发现只剩两个字段了

image

三、常用序列化字段和参数

1、常用字段

字段 字段构造方式 详解
BooleanField BooleanField() 布尔字段用于存储和表示真/假值。构造方法不需要参数。
NullBooleanField NullBooleanField() 可空布尔字段是可以接受三个值的布尔字段:True、False和None(空值)。构造方法不需要参数。
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) 字符字段用于存储短文本数据。max_length指定字符的最大长度,min_length指定最小长度。allow_blank指定是否允许为空值。trim_whitespace指定是否在保存数据前去除首尾的空格。
EmailField EmailField(max_length=None, min_length=None, allow_blank=False) Email字段用于存储和验证电子邮件地址。max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False) 正则表达式字段用于存储和验证符合特定模式的数据。regex指定正则表达式,max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
SlugField SlugField(max_length=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9_-]+ Slug字段用于存储URL友好的文本标识符。max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
URLField URLField(max_length=200, min_length=None, allow_blank=False) URL字段用于存储和验证URL地址。max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
UUIDField UUIDField(format='hex_verbose') format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "1234567012312313134124512351145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" UUID字段用于存储和验证通用唯一标识符。format参数指定UUID的格式。
IPAddressField IPAddressField(protocol='both', unpack_ipv4=False, **options) IP地址字段用于存储和验证IP地址。protocol参数指定所允许的IP地址协议类型,unpack_ipv4参数指定是否拆分IPv4地址。
IntegerField IntegerField(max_value=None, min_value=None) 整数字段用于存储整数值。max_value指定最大值,min_value指定最小值。
FloatField FloatField(max_value=None, min_value=None) 浮点数字段用于存储浮点数值。max_value指定最大值,min_value指定最小值。
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 十进制字段用于存储精确的十进制数值。max_digits指定最多位数,decimal_places指定小数点位置。coerce_to_string指定是否将值强制转化为字符串形式。max_value指定最大值,min_value指定最小值。
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) 日期时间字段用于存储日期和时间。format参数指定日期时间的输出格式,input_formats参数指定输入格式。
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None) 日期字段用于存储日期。format参数指定日期的输出格式,input_formats参数指定输入格式。
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None) 时间字段用于存储时间。format参数指定时间的输出格式,input_formats参数指定输入格式。
DurationField DurationField() 持续时间字段用于存储一段时间的持续时间。构造方法不需要参数。
ChoiceField ChoiceField(choices) choices与Django的用法相同 选择字段用于存储和验证预定义选项中的一个值。choices参数指定可选的选项值。
MultipleChoiceField MultipleChoiceField(choices) 多选字段用于存储和验证多个预定义选项中的值。choices参数指定可选的选项值。
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) 文件字段用于上传和保存文件。max_length指定文件名的最大长度,allow_empty_file指定是否允许为空文件。use_url指定是否使用文件的URL路径。
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) 图片字段用于上传和保存图片文件。max_length指定文件名的最大长度,allow_empty_file指定是否允许为空文件。use_url指定是否使用图片的URL路径。
ListField ListField(child=, min_length=None, max_length=None) 列表字段用于存储和验证列表类型的数据。child参数指定列表中元素的类型,min_length指定最小长度,max_length指定最大长度。
DictField DictField(child=) 字典字段用于存储和验证字典类型的数据。child参数指定字典中value的类型。
  • 总结:常用字段
IntegerField
CharField
DateTimeField
DecimalField
ListField和DictField

2、字段参数(校验数据)

(1)选项参数:(CharField,IntegerField)

参数名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最小值
min_value 最大值

(2)通用参数:

参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
  • 非常重要的参数
read_only
write_only

3、案例展示

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
def validate_name(name):
if name.startswith('sb'):
raise ValidationError('不能以sb开头')
else:
return name
class TaskSerializer(serializers.Serializer):
task_name = serializers.CharField(required=False,allow_null=True,default='默认任务')
task_id = serializers.CharField(max_length=8,error_messages={'max_length':'太长了,受不了了','required':'不能没有你'})
task_time = serializers.DateTimeField()

四、反序列化应用场景

  • 使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。验证的规则在定义序列化类时已经定义

  • 在获取反序列化的数据前,必须调用 is_valid() 方法进行验证,验证成功返回 True,否则返回 False

  • 验证失败,可以通过序列化器对象的 errors 属性获取错误信息,返回字典,包含了字段和字段的错误

  • 验证成功,可以通过序列化器对象的 validated_data 属性获取数据。

1、校验数据(增加)

(1)自定义序列化文件

  • serializer.py
  • 校验前端传入的数据
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class BookSerializer(serializers.Serializer):
# 1. 数据校验第一层,字段自己校验
# 名字最长8位,最短3位
name = serializers.CharField(max_length=8, min_length=3) # 字段参数,控制 数据校验
price = serializers.IntegerField(max_value=100, min_value=10)
publish = serializers.CharField()
# 2. 局部钩子函数--->给某个字段加限制条件
def validate_name(self, name):
# 书名不能以66开头
# if name.startswith('66'):
# 书名不能有66
if '66' in name:
# 不合法,抛出异常
raise ValidationError('书名不能以66开头')
else:
# 合法,返回原来的数据
return name
"""
局部钩子,一个字段只能写一个
全局钩子,也只能写一个
"""
# 3 全局钩子--->多个字段校验
# 要求:书名和出版社不能一样
def validate(self, attrs):
# attrs 是前端传入,经过字段自己校验和局部钩子校验都通过的数据 [字典]
if attrs.get('name') == attrs.get('publish'):
raise ValidationError('书名和出版社不能一样')
else:
return attrs

(2)视图函数

  • views.py
class BookView(APIView):
def post(self, request):
# 1 校验前端传入的数据
# 2 数据校验和反序列化---> 不能传instance,要传data
serializer=BookSerializer(data=request.data)
# 3 校验数据
if serializer.is_valid():
# 校验通过,保存
serializer.save() # 需要在序列化类中重写 create才能存进去
return JsonResponse({'code': 100, 'msg': '保存成功'})
else:
return JsonResponse({'code': 101, 'msg': serializer.errors})

(3)小结

反序列化有三层校验:

  • 字段自己的:写的字段参数:required/max_length ...
  • 局部钩子:写在序列化类中的方法
    • 方法名必须是 validate_字段名
  • 全局钩子:写在序列化类中的方法
    • 方法名必须是 validate

全局钩子和局部钩子都只能写一个,优先级是全局 > 局部

2、保存数据(增加)

  • 上面数据校验完成最后有个数据保存

(1)自定义序列化文件

  • serializer.py
# 数据保存
def create(self, validated_data):
# validated_data: 前端传入,所有校验通过的数据 字典
book = Book.objects.create(**validated_data)
return book # 不要忘了返回

(2)视图函数

  • views.py
class BookView(APIView):
def post(self, request):
# 1 校验前端传入的数据
# 2 数据校验和反序列化---》不能传instance,要传data
serializer=BookSerializer(data=request.data)
# 3 校验数据
if serializer.is_valid():
# 校验通过,保存
serializer.save() # 需要在序列化类中重写 create才能存进去
return JsonResponse({'code': 100, 'msg': '保存成功'})
else:
return JsonResponse({'code': 101, 'msg': serializer.errors})

(3)小结

  • 在后端逻辑中调用 save 方法,然后序列化类中需要重写 create 方法
  • 但是如果调用create方法,校验通过的 validated_data 数据不会自动传入,所以需要我们传入的数据 serializer = BookSerializer(data=request.data)

3、反序列化之修改并保存

(1)自定义序列化文件

  • serializer.py
def update(self, instance, validated_data):
# instance:对象
# validated_data 数据
for i,v in validated_data.items():
setattr(instance, i, v)
instance.save()
return instance

(2)视图函数

  • views.py
# 127.0.0.1:8000/app01/books/1
class BookDetailView(APIView):
def put(self, request, pk):
obj=Book.objects.filter(pk=pk).first()
# 改对象必须传data和instance
serializer=BookSerializer(instance=obj,data=request.data)
if serializer.is_valid():
serializer.save() # 重写update 触发序列化类的update
return Response({'code':100,'msg':'成功'})
else:
return Response({'code': 100, 'msg': serializer.errors})

(3)小结

  • 序列化类中重写 update 方法,在后端逻辑中调用 save 方法
  • 需要传入的参数 ser = BookSerializer(instance=book,data=request.data)

五、save方法加分析

  • 在视图类中,无论是保存还是修改,都是调用序列化类的 save() 方法
  • 底层实现逻辑是根据 instance 判断需要做出的对应的方法
def save(self, **kwargs):
# 根据传入的 instance 对象,进行方式的判断 update/create
# 如果是 update 方法 需要传入需要需要修改的独享和修改的数据
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
# 如果是 create 需要传入修改的数据
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
  • 如果是 update 方法 需要传入需要需要修改的独享和修改的数据
  • 如果是 create 需要传入修改的数据

六、read_only 和 write_only详细介绍

1、常用字段参数

  • read_only:表明该字段仅用于序列化输出,默认False(只能查看,不能修改)
  • write_only:表明该字段仅用于反序列化输入,默认False(只能修改,不能查看)

2、案例展示

(1)路由

  • urls.py
path('books/<int:pk>', BookDetailView.as_view()),

(2)模型表

  • models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=64)
price = models.IntegerField()
publish = models.CharField(max_length=32)

(3)序列化类

  • serializers.py
from rest_framework import serializers
class BookSerializer2(serializers.Serializer):
name = serializers.CharField(max_length=8, min_length=3)
# read_only=True:该字段只用于序列化输出,只能看不能修改
price = serializers.IntegerField(max_value=100, min_value=10,read_only=True)
# write_only=True:该字段只用于反序列化输入,只能改不能查看
publish = serializers.CharField(write_only=True)
def update(self, instance, validated_data):
for k,v in validated_data.items():
setattr(instance, k, v)
instance.save()
return instance

(4)视图函数

  • views.py
from mydrf.serializers import BookSerializer2
from mydrf import models
from rest_framework.views import APIView
from rest_framework.response import Response
class BookDetailView(APIView):
def get(self, request,pk):
# 从数据库中获取到Book的对象
obj = Book.objects.filter(pk=pk).first()
# 将book_obj放进我们写的序列化器里面进行序列化
book_ser = BookSerializer(obj)
# [序列化对象].data就是序列化后得到的字典
return Response(book_ser.data)
def put(self, request, pk):
obj = Book.objects.filter(pk=pk).first()
# 改对象必须传data和instance
serializer = BookSerializer(instance=obj, data=request.data)
if serializer.is_valid():
serializer.save() # 重写update 触发序列化类的update
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': serializer.errors})

(5)测试

  • 首先发送get请求从数据库中获取id为1的数据
  • 发现获取不到publish的值,因为我们设置了write_only

image

  • 然后发送put请求携带参数在数据库中修改id为1的数据

image

  • 修改成功了,但是数据库中price值并没有发生改变

image

  • 最后对publish进行测试
  • 在发送put请求的时候我并没有携带publish的键值对数据
  • 于是返回结果为这个字段是必填项的提示信息

image

七、补充:翻译函数

在 Django REST framework (DRF) 中,翻译函数通常用于处理序列化类中字段的翻译工作。DRF 提供了 django.utils.translation.ugettext_lazy 函数来实现字段值的翻译。这个函数用于延迟翻译字符串,以便在渲染时执行实际的翻译。

下面是一个简单的示例,演示如何在 DRF 的序列化类中使用翻译函数:

from django.utils.translation import ugettext_lazy as _
class MySerializer(serializers.Serializer):
name = serializers.CharField(label=_("Name"))
age = serializers.IntegerField(label=_("Age"))
# 在视图中使用这个序列化类
serializer = MySerializer(data=data)
serializer.is_valid()

在这个示例中,_() 函数被用来标记需要翻译的字段标签。当这个序列化类被实例化时,标签会被翻译成当前语言环境下的字符串。

另外一定要确保在项目的设置中配置了正确的语言和翻译设置,以便让翻译函数生效。

八、序列化类实现增删改查接口

1、需求

  • 数据库表 : Book, Author, AuthorDatail, Publish, 表之间建好关联
  • 实现出版社查全部, 查某条, 新增, 修改, 删除接口
  • 出版社名字不能有"sb"敏感字
  • 书名和出版社不能一样

2、代码实现

(1)创建表模型

  • models.py
from django.db import models
class Book(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField()
publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
def __str__(self):
return self.name
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
author_detail = models.OneToOneField(to='AuthorDetail', to_field='nid', unique=True, on_delete=models.CASCADE)
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
telephone = models.BigIntegerField()
birthday = models.DateField()
addr = models.CharField(max_length=64)
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
city = models.CharField(max_length=32, null=True)
email = models.EmailField()
def __str__(self):
return self.name

(2)序列化类

  • serializers.py
class BookSerializer(serializers.Serializer):
# nid = serializers.IntegerField(required=False)
book_id = serializers.IntegerField(source='nid', required=False)
name = serializers.CharField(max_length=32)
# book_name = serializers.CharField(source='name')
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField()
# publish_id = serializers.IntegerField()
publish_name = serializers.CharField(source='publish.name')
def validate_name(self, name):
if 'sb' in name:
# 不合法,抛异常
raise ValidationError('书名不能有sb')
else:
# 合法,返回原来的数据
return name
# 局部钩子,一个字段只能写一个
# 全局钩子,也只能写一个
# 3 全局钩子 多个字段校验
def validate(self, attrs):
# attrs 是前端传入,经过字段自己校验和局部钩子校验都通过的数据 [字典]
name = attrs.get('name')
price = attrs.get('price')
if name == '海底两万里' and price == 100:
raise ValidationError('该书已被列入黑名单')
return attrs
def create(self, validated_data):
res = models.Book.objects.create(**validated_data)
return res # 不要忘了返回
def update(self, instance, validated_data):
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
return instance

(3)视图函数

  • views.py
class BookView(APIView):
def get(self, request):
book_qs = models.Book.objects.all()
book_str = BookSerializer(instance=book_qs, many=True) # 多条数据一定要添加many
return Response(book_str.data)
def post(self, request):
book_ser = BookSerializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response(book_ser.errors)
class BookDetailView(APIView):
def get(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
book_ser = PublishSerializer(instance=book_obj)
return Response(book_ser.data)
def put(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
book_ser = BookSerializer(instance=book_obj, data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response(book_ser.errors)
def delete(self, request, pk):
# 直接删除数据,拿到影响条数的一个列表
rows = models.Book.objects.filter(pk=pk).delete()
if rows[0] > 0:
return Response('数据删除成功!')
else:
return Response('数据不存在!')

(4)路由

  • urls.py
path('books/', BookView.as_view()),
path('books/<int:pk>/', BookDetailView.as_view())

九、多表关联序列化

1、定制返回格式之source

用来指定要序列化的字段(数据表中的字段), 一般使用在一个字段情况, 可以跨表, 可以执行方法并执行

  • 不指定,默认就是字段名,必须跟数据库对应
  • 指定了source就可以给字段改名了
# 更改字段名
"[另取的名字]" = serializers.CharField(source='nid') # 指定需要序列化的字段是publish
# 执行方法,指定一个定义在模型类内方法的内存地址,会自动加括号执行
publish_date = serializers.CharField(source='方法名')
  • 如果想跨表查询的话,本表中一定要有所要跨的表的字段
比如下面的publish,从BOOK表我们就可以跨表查询到Publish表中的字段
# 1 source 定制返回字段名,跟name必须对应
book_name = serializers.CharField(source='name')
# 2 显示出版社名字--> 字符串类型--> CharField 可以跨表查询
publish_name=serializers.CharField(source='publish.name')
# 3 所有字段都可以被转成CharField
publish_id=serializers.IntegerField(source='publish.id')

image

2、定制返回字段

我想实现如下格式返回的数据该怎么操作

{name:书名,price:价格,publish:{name:出版社名,addr:地址},authors:[{},{}]}

(1)在表模型中定义方法

  • models.py
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField()
publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE)
authors=models.ManyToManyField(to='Author') # 数据库中不会有这个字段---》生成第三张表--》多对多
def book_name(self):
# 通过这种方式,可以修改返回的格式
return self.name+'sb'
@property
def publish_detail(self):
return {'name':self.publish.name,'city':self.publish.city}
@property
def authors_list(self):
l=[]
for author in self.authors.all():
l.append({'name':author.name,'age':author.age})
return l
def __str__(self):
return self.name
  • serializer.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField()
# 出版社对象
# publish = serializers.CharField() # 如果这样写,会把publish对象,打印的样子返回给前端
publish_detail = serializers.DictField() # 表里有这个字段吗? 有
# 所有作者
authors_list = serializers.ListField() # 表里没有这个字段,表模型中有

(2)通过SerializerMethodField

  • 一定要配合一个方法--> get_字段名
  • serializer.py
class BookSerializer(serializers.Serializer):
book_name = serializers.SerializerMethodField()
def get_book_name(self,obj):
return obj.name+'ssssb'
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField()
# 一定要配合一个方法--> get_字段名
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self,obj):
# obj 就是当前序列化到的book对象
return {'name':obj.publish.name,'city':obj.publish.city,'id':obj.pk}
authors_list = serializers.SerializerMethodField()
def get_authors_list(self,obj):
l=[]
for author in obj.authors.all():
l.append({'name':author.name,'age':author.age,'id':author.id})
return l

(3)子序列化

  • 需要创建一个子序列化类
  • 子序列化类必须写在上方, 且只能对外键字段进行重写
  • 使用了子序列化的外键字段就不能再进行反序列化过程
  • 子序列化单个数据需填写 many=False, 多个数据需填写 many=True
# serializers.py 文件中创建子序列化类,并将子序列化类加入序列化类中
# 子序列化类 必须写在上方
class PublishSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
city = serializers.CharField()
email = serializers.EmailField()
# 子序列化类 必须写在上方
class AuthorSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
age = serializers.CharField()
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField()
# 书写子序列化字段
publish_detail = PublishSerializer(source='publish')
# 多条 many=True
authors_list = AuthorSerializer(source='authors',many=True)

十、多表关联反序列化

  • 序列化的字段和反序列化字段不一致
    • 1.笨办法:再写个序列化类,单独用来反序列化
      1. 通过 read_only write_only 控制

1、反序列化保存一对多关联字段

  • publish字段一对多关系绑定了Publish表,因此在Book表中显示的是publish_id

image

  • 在反序列化校验Book对象时,django并不认识Publish表,因此只会识别publish_id,那么前端发送请求时也要传入publish_id
# serializers.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField()
# 这个字段用来做序列化
publish_detail = PublishSerializer(source='publish', read_only=True)
# 反序列化字段--> 只用来保存---> 多表关联
publish_id = serializers.IntegerField(write_only=True) # 前端传入数字---》自动跟出版社id做对应

在反序列化校验publish_id时为了不影响原先的序列化校验,要在不需要反序列化校验的参数后面加入read_only=True参数,同时publish_id加入write_only参数只作用于反序列化校验

read_only=True:该字段只会用于序列化校验
write_only=True:该字段只会用于反序列化校验

(1)重写create方法

def create(self, validated_data):
publish_id = validated_data.pop('publish_id')
book_obj = models.Book.objects.create(**validated_data, publish_id=publish_id)
return book_obj
  • 为什么要先pop掉publish_id呢
  • 如果这是我直接book_obj = models.Book.objects.create(**validated_data)会发生什么?
  • print(book_obj.publish_id) ---> None

这是因为,虽然validated_data包含了publish_id字段,但是此时它并不具备外键属性,Django仅将他作为一个普通字段进行反序列化,那么理所当然的后端的序列化结果也不会成功,因为publish_detail是根据外键属性取值的,所以此时这里应该明确用publish_id作为publish_id字段的参数

# 先将publish_id参数从vaildated_data中踢出,让前两个参数以**方式传入后再指定publish_id
publish_id = validated_data.pop('publish_id')
# 左边的publish_id是字段,右边的publish_id是前端传来的数据
book_obj = models.Book.objects.create(**validated_data, publish_id=publish_id)
# 这种方法和上面的 **validated_data 含义相同,用其中一种即可
book_obj = models.Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'), publish_date=validated_data.get('publish_date'), publish_id=publish_id)

(2)重写upadate方法

  • views.py
def put(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
book_ser = BookSerializer(instance=book_obj, data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response(book_ser.errors)
  • serializers.py
def update(self, instance, validated_data):
publish_id = validated_data.pop('publish_id')
book_qs = Book.objects.filter(pk=instance.pk)
book_qs.update(**validated_data, publish_id=publish_id)
instance = book_qs.first()
return instance
  • updata相比于create就多了个instance参数
  • instance就是views层传过来的模型对象,先pop掉publish_id,然后直接update即可

2、反序列化保存多对多关联字段

  • 首先定义一个author字段,与Author表绑定多对多关系字段
class Book(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField(auto_now=True)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()

那么此时的数据便不会存在于BookAuthor中,而是自动新建一个book_author表,例如:

image

其次在序列化类中添加字段

class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField(required=False,default=datetime.now)
# 这个字段用来做序列化
publish_detail = PublishSerializer(source='publish',read_only=True)
# 这个字段用来做序列化
authors_list = AuthorSerializer(source='authors', many=True,read_only=True)
# 反序列化字段--> 只用来保存---> 多表关联
publish=serializers.IntegerField(write_only=True) # 前端传入数字---> 自动跟出版社id做对应
authors=serializers.ListField(write_only=True) # ListFieled字段
# 反序列化字段-->可以随意命名,跟表字段没关系--》但是后续保存和修改要对应好才行
# publish_id=serializers.IntegerField(write_only=True) # 前端传入数字---> 自动跟出版社id做对应
# authors_xx=serializers.ListField(write_only=True)

(1)重写create方法

  • serializers.py
def create(self, validated_data):
authors=validated_data.pop('authors')
book=Book.objects.create(**validated_data)
book.authors.add(*authors) # 向中间表中插入数据
return book
  • 首先将authors踢出,然后将其单独插入中间表,具体原因跟一对多关系相同

(2)重写update方法

  • serializers.py
def update(self, instance, validated_data):
authors=validated_data.pop('authors')
book_qs=Book.objects.filter(pk=instance.pk) # 查询qs对象
book_qs.update(**validated_data) # 使用qs更新
instance=book_qs.first() # 单个对象
instance.authors.set(authors) # 向中间表中插入数据
# instance.author.clear()
# instance.authors.add(*authors)
return instance

知识回顾:模型层多表操作

  • book_obj.authors.add (*列表)
  • book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除
  • book_obj.authors.clear() #清空被关联对象集合
  • book_obj.authors.set(列表) #先清空再设置

3、反序列化保存一对一关联字段

  • models.py
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
author_detail = models.OneToOneField(to='AuthorDetail', to_field='nid', unique=True, on_delete=models.CASCADE)
class Meta:
db_table = 'author'
def author_dict(self):
return {'电话号码': self.author_detail.telephone, '生日': self.author_detail.birthday, '地址': self.author_detail.addr}
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
telephone = models.BigIntegerField()
birthday = models.DateField()
addr = models.CharField(max_length=64)

(1)重写create方法

  • serializers.py
class AuthorSerializer(serializers.Serializer):
# 序列化和反序列化字段
name = serializers.CharField()
age = serializers.IntegerField()
# 序列化字段(在模型中定义方法)
author_dict = serializers.DictField()
# 重写create方法
def create(self, validated_data):
print(validated_data)
author_dict = validated_data.pop('author_dict')
author_detail = AuthorDetail.objects.create(**author_dict)
author_obj = Author.objects.create(**validated_data, author_detail=author_detail)
return author_obj
  • 这里需要将author_dict里需要的字段全部在前端传入,例:
{
"name": "xuyuan",
"age": 23,
"author_dict": {
"电话号码": 150,
"生日": "2001-12-12",
"地址": "芜湖"
}
}

(2)重写update方法

  • 前面的都不需要动,只需重写update方法

  • views.py

def put(self, request, pk):
author_obj = models.Author.objects.filter(pk=pk).first()
if not author_obj:
return Response('没有此作者')
author_ser = AuthorSerializer(instance=author_obj, data=request.data)
if author_ser.is_valid():
author_ser.save()
return Response(author_ser.data)
else:
return Response(author_ser.errors)
  • serializers.py
def update(self, instance, validated_data):
# 与create相同,要在中间表更新数据
author_dict = validated_data.pop('author_dict')
AuthorDetail.objects.filter(pk=instance.author_detail.pk).update(**author_dict)
# 其他字段不变,author_detail_id字段不上传,因为是一对一绑定关系所以没必要
instance.name = validated_data.get('name')
instance.age = validated_data.get('age')
instance.save()
return instance
  • instance.author_detail.pkinstance对象获取到中间表author_detail的pk

4、注意事项

# 不要有误区---> 如果增加图书,只是增加图书,选择作者和出版社的字段应该使用id代替(传:id)
{name:书名,price:11,publish:{'name':'北京出版社',city:北京},authors:[{},{}]}
{name:书名,price:11,publish:2,authors:[1,2]}
  • read_only、 write_only 控制序列化类中某些字段,只用来序列化或反序列化
  • 重写updata和create,保存逻辑,我们自己写
  • 视图类中 serializer = BookSerializer(instance=pk, data=request.data)
    • 后续在序列化类中的update中def update(self, instance, validated_data):
    • instance就是当时给的

十一、ModelSerializer类下的序列化和反序列化

  • 之前写序列化类,没有显示指明跟哪个表一一对应

  • ModelSerializer 可以跟表做一一对应关系

    • 序列化类中,就不需要一个个写字段了--》跟表有对应关系
    • 序列化类中,就不需要重写create和update
  • 局部钩子全局钩子跟之前一样(注意层级)

1、模型表

  • models.py
class Book(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField(auto_now=True)
publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
def __str__(self):
return self.name

2、视图函数

  • views.py
class BookView(APIView):
def post(self, request):
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
print(serializer.validated_data) # 校验过后的数据
serializer.save()
return Response('成功')
else:
return Response({'code': 100, 'msg': serializer.errors})
class BookDetailView(APIView):
def put(self, request, pk):
obj = Book.objects.all().filter(pk=pk).first()
# 传入的instance是什么,到了 update中,instance就是什么
serializer = BookSerializer(instance=obj, data=request.data)
if serializer.is_valid():
serializer.save()
return Response('成功')
else:
return Response({'code': 100, 'msg': serializer.errors})

3、序列化类

  • serializer.py
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model=Book # 与模型表做一一对应
fields='__all__'
# fields=['id','name','publish_detail','authors_list']
extra_kwargs={
'name':{'max_length':8}, # 限制name不能超过8
'publish':{'write_only':True},
'authors':{'write_only':True},
}
# 自己再重写的字段
# 这个字段用来做序列化
publish_detail = PublishSerializer(source='publish',read_only=True)
# 这个字段用来做序列化
authors_list = AuthorSerializer(source='authors', many=True,read_only=True)
# 不需要写create了,但是字段必须是:publish和authors
  • 此时序列化类中就不需要一个个写字段了,ModelSerializer会自动跟表做对应关系
  • model:需要对应的表
  • fields:用于指定需要被序列化的字段,__all__为全部['name', 'age']为指定
  • extra_kwargs:类似钩子函数,将对应字段加上限制
posted @   Xiao0101  阅读(127)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示

目录