DRF之序列化组件

【二】序列化组件

【1】序列化与反序列化的概念

  • 序列化(Serialization):将对象转换为可传输或可存储的格式的过程。在序列化过程中,对象的属性和数据被转换为一个字节流或字符串,以便在网络上传输或保存到文件中。常见的序列化格式包括 JSON、XML、Protocol Buffers 等。序列化后的数据可以在不同的系统、编程语言或应用程序之间进行交换和共享。

  • 反序列化(Deserialization):将序列化后的数据恢复为原始对象的过程。在反序列化过程中,从序列化格式(例如 JSON 字符串)中解析出对象的属性和数据,并重新构建原始对象。反序列化的过程与序列化过程相反,它将序列化后的数据转换回原始对象,以便进行进一步的处理、操作或显示。

  • 简单来说:

    • 序列化:将对象转换为可传输的格式

    • 反序列化:从 JSON 字符串、XML 文档、字节流等中解析出对象的属性和数据。

【2】序列化类(Serializer)

【2.1】定义序列化类

# 定义序列化类需要继承drf.Serializer
from rest_framework import serializers

'''简单示例'''
class MySerializer(serializers.Serializer):
    字段名 = serializers.字段类型(字段参数)
    
'''实例'''
class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)

    def create(self, validated_data):
        """
        根据提供的验证过的数据创建并返回一个新的`Snippet`实例。
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        根据提供的验证过的数据更新和返回一个已经存在的`Snippet`实例。
        """
        instance.title = validated_data.get('title', instance.title)
        return instance
  • 序列化器类的第一部分定义了序列化/反序列化的字段。
  • create()update()方法定义了在调用serializer.save()时如何创建和修改完整的实例。

【2.2】常见字段及参数

【2.2.1】常用字段类型
字段 字段构造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices) choices与Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)
【2.2.2】选项参数
参数名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最小值
min_value 最大值
【2.2.3】通用参数
参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息

【3】序列化类的基本操作

【3.1】序列化

# 导入构建好的序列化类
from xx import Serializer
# 创建序列化类对象
ser = Serializer(instance=obj对象,data=request.data,...)
# 获取序列化后的数据  # 以json格式返回
ser.data
【3.1.1】序列化对象的常用参数
  1. instance:要序列化的模型实例。如果需要对现有对象进行序列化,则传递该实例。
  2. data:要反序列化的数据。如果需要从数据中创建对象,则传递该数据。通常用于创建或更新对象。
  3. context:上下文数据,可以在序列化器的各个方法中使用。通常用于在序列化器之间传递额外的信息。
  4. many:指定是否序列化多个对象。默认为 False。如果设置为 True,则可以序列化多个对象的查询集。
  5. partial:指定是否部分更新对象。默认为 False。如果设置为 True,则可以部分更新对象而不需要提供所有字段的值。

【3.2】反序列化校验

  • 序列化器在反序列化时通常会执行一系列验证操作,以确保输入的数据符合预期的格式和约束。
  • 这些验证功能可以帮助确保用户提供的数据是有效的,并且可以在存储到数据库之前进行预处理。
【3.2.1】校验级别
【3.2.1.1】字段级别的验证
class BookSerializer(serializers.Serializer):
    name = serializers.CharField(min_length=5, max_length=32, error_messages={
        'min_length': '最少不得少于5个字符',
        'max_length': '最多不得多于32个字符'
    })
    price = serializers.IntegerField(min_value=10, max_value=999, error_messages={
        'min_value': '最少不得少于10元',
        'max_value': '最多不得多于999元'
    })
    publish = serializers.CharField(min_length=3, max_length=255, error_messages={
        'min_length': '最少不得少于3个字符',
        'max_length': '最多不得多于255个字符'
    })
【3.2.1.2】自定制校验器validators
  • 与forms组件一样,可以指定validators参数自定制验证器
def func(value):
    '''验证数据格式'''
    if xxx:
        # 指定条件
        # 抛出异常
        raise ValidationError("错误信息")
    return value

class MySerializer(serializers.Serializer):
    # 可以指定多个验证器
    name = serializers.CharField(max_length=32,validators=[func,func2...])
【3.2.1.3】钩子函数
  • 局部钩子:validate_字段(self,字段)
  • 全局钩子:validate(self,attrs)
  • 校验失败 抛出指定异常ValidationError
    • from rest_framework.exceptions import ValidationError
# 局部钩子
def validate_task_name(self, name: str):
    if 'sb' in name:
        # 抛出指定异常
        raise ValidationError('不得包含侮辱性词汇')
    # 注意需要返回该字段
    return name

# 全局钩子
def validate(self, attrs):
    if attrs.get('name') == attrs.get('publish'):
        raise ValidationError('书名不得与出版社名一致')
    # 需要返回字段
    return attrs
【3.2.2】校验完成的数据 校验失败的错误信息
  • ser.is_valid():返回布尔值,True表示所有数据验证通过,False表示有错误数据

    • 通过传递raise_exception=True参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应
  • ser.data:包含了序列化后字段及其对应数值的字典,可以直接用于生成 JSON 或其他格式的响应数据

  • ser.errors:校验失败的错误提示信息,由error_message参数指定或使用默认

# views.py
'''不需要携带参数的视图类'''
class BookViewCR(APIView):
    def get(self, request):
        book_all = Book.objects.all()
        # 将需要序列化的数据给 【instance】参数  # 如果是多条 需要传递 【many】参数
        ser = BookSerializer(instance=book_all, many=True)
        # 返回 一个数组【[{},{}]】  # 指定了many参数将视为多条数据 并按照restful规范返回数组格式
        return Response({'code': 100,'msg': 'get','results': ser.data})

    def post(self, request):
        data = request.data
        # 新增数据  # 直接将数据传入data参数中
        ser = BookSerializer(data=data)
        if ser.is_valid():
            # 校验数据
            # 校验通过  # 执行保存方法
            ser.save()
            return Response({'code': 100, 'msg': '添加成功', 'result': ser.data})
        else:
            # 校验失败
            # 返回错误信息
            return Response({'code': 101, 'msg': ser.errors})
      
'''需要携带 pk 参数 的视图类'''
class BookViewRUD(APIView):
    # 查询指定pk数据
    def get(self, request, tid):
        # 获取对象
        book = Book.objects.filter(pk=tid).first()
        if not book:
            return Response({'code': 101, 'msg': '当前书籍不存在'})
        # 将查询到的对象传入instance
        ser = BookSerializer(instance=book)
        # 返回序列化后的数据
        return Response({'code': 100, 'msg': f'查询指定【{tid}】成功', 'result': ser.data})
    
    # 修改指定数据
    def put(self, request, tid):
         # 获取对象
        book = Book.objects.filter(pk=tid).first()
        if not book:
            return Response({'code': 101, 'msg': '当前书籍不存在'})
        # 将对象传入instance  # 将需要更新的数据传入data
        ser = BookSerializer(instance=book, data=request.data)
        if ser.is_valid():
            # 进行数据校验
            # 数据校验通过调用save方法保存
            ser.save()
            return Response({'code': 100, 'msg': f'修改指定【{tid}】成功', 'result': ser.data})
        else:
            # 校验失败返回错误信息
            return Response({'code': 101, 'msg': ser.errors, })

【3.2】反序列化保存 create update

  • 序列化器判断是新建对象还是更新对象的标准就是,是否传递了instance参数,数据将由data参数接受
class xxx(serializers.Serializer):
    # ... 
    def create(self, validated_data):
        '''可以对校验过的数据进行再次处理'''
        # 新增数据
        book = Book.objects.create(**validated_data)
        '''
        title = validated_data.pop('title')
        title += '爆款'
        # 为标题加上爆款二字
        Book.objects.create(**validated_data,title=title)
        '''
        return book

    def update(self, instance, validated_data):
        # 由于instance是一个对象,没办法直接使用 【queryset.update(**kwargs)】方法
        # 所以需要使用反射的方法为对象更新属性
        for key in validated_data.keys():
            setattr(instance, key, validated_data.get(key))
        instance.save()
        # 将对象返回
        return instance
【3.2.1】save(**kwargs)
  • 对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()update()中的validated_data参数获取到
# views.py
class MyView(APIView):
    def post(self,request):
        ser = Ser(data=reuqest.data)
        ser.is_valid(raise_exception=True)
        ser.save(other='xxx')
        
###############################################
# serializer.py
class Ser(serializer.Serializer):
    ...
    def create(self,validated_data):
        other = validated_data.pop('other')
        ...

【4】source参数

  • source 参数用于指定要序列化的字段的源。通常,它用于在序列化器中访问模型实例的属性或方法,并将其值包含在序列化后的数据中。
  • 当你需要访问模型实例的属性或方法,并将其值序列化到响应中时,可以使用 source 参数来指定该属性或方法的名称。这个参数告诉序列化器从哪里获取数据以进行序列化。

【4.1】source参数跨表查询

# models.py
class Books(models.Model):
    name = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 如果是外键字段  # 不使用source参数指定将会返回整个publish模型实例
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)

###############################################

# serializer.py
class Ser(serializers.Serializer):
    ...
    '''可以指定返回外键字段对象所对应的属性值'''
    publish_name = serializers.CharField(source='publish.name')
    publish_addr = serializers.CharField(source='publish.addr')
    
    '''更改字段序列化时显示的名称'''
    book_name = serializers.CharField(source='title')  # book_name的值就是模型表中的title字段

【4.2】source参数指定执行模型表中的方法

'''指定执行模型表中的方法'''
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)

    def name2desc(self):
        '''执行一些代码'''
        return self.name + self.desc
    
############################################
# ser.py
class TaskSer(serializers.Serializer):
    # 通过source参数指定模型表中方法  # 传对应的函数名即可
    name_desc = serializers.CharField(source='name2desc')

【4.3】source参数指定显示模型表中字段名称

# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)

############################################
# ser.py
class TaskSer(serializers.Serializer):
    # task_name将作为序列化返回给前端的json数据中展示name字段的键
    task_name = serializers.CharField(source='name')

【4.4】总结

# ser.py

from rest_framework import serializers

class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    # 可以使用source参数自定义显示给前端的json数据中的键
    task_name = serializers.CharField(source='name')
    # 模型表中的方法也可以使用同名的字段名进行调用
    name2desc = serializers.CharField()
    # 也可以使用source参数执行模型表中的方法
    name_desc = serializers.CharField(source='name2desc')
    # 直接使用外键字段将回返回当前模型表对象对应的外键对象
    user = serializers.CharField()
    # 可以使用source参数进行跨表查询
    user_name = serializers.CharField(source='user.username')
    
    
###############################################
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)
    user = models.ForeignKey(to='User', on_delete=models.CASCADE, default=1)

    def name2desc(self):
        '''执行一些代码'''
        return self.name + self.desc


class User(models.Model):
    username = models.CharField(max_length=32, default='aaa')

image-20240422153057758

【5】使用 SerializerMethodField 定制字段

# 基本语法
字段 = serializers.SerializerMethodField()
    def get_字段(self,obj):
        '''此处的obj就是当前正在序列化的模型表对象'''
        return ...
class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    desc = serializers.CharField()
    # 可以使用source参数进行跨表查询
    user_name = serializers.CharField(source='user.username')
    xxx = serializers.SerializerMethodField()

    def get_xxx(self, obj):
        '''
        此处的self为序列化类的对象,应当返回模型对象中的数据
        :param obj: 当前模型表对象
        :return: 想要展示的值
        '''
        return obj.desc + '这是描述哟'

image-20240422155215255

【6】使用 子序列化 定制字段

  • 使用子序列化以实现序列化类的复用
class UserSer(serializers.Serializer):
    '''user表的序列化类'''
    username = serializers.CharField()


class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    desc = serializers.CharField()
    # 使用子序列化以实现序列化类的复用
    # 如果字段名与模型表中的外键字段一致  # 当调用子序列化时,将会自动将该外键对象传入子序列化类中
    user = UserSer()
    # 可以使用source参数指定该字段对应的外键字段  # 那么子序列化类将会自动识别外键对象
    user_obj = UserSer(source='user')

image-20240422160909664

  • 如果既不使用与外键字段同名,且不使用source参数指定,将会报错

【7】ListField和DictField

  • ListField 用于序列化和反序列化列表类型的数据。
    • 当你序列化数据时,ListField 会将列表中的每个元素序列化为相应的格式,例如 JSON 数组
    • 当你反序列化时,ListField字段将可以接受数组类型的数据
  • DictField 用于序列化和反序列化字典类型的数据。
    • 当你序列化数据时,DictField 会将字典中的键值对序列化为相应的格式,例如 JSON 对象。
    • 当你反序列化时,DictField字段将可以接受字典类型的数据

【7.1】序列化使用场景

  • 在模型表中,定义某些方法,返回列表或字典格式
  • 【注】对于多对多字段,不能直接使用ListField来进行序列化,可以使用
    • SerializerMethodField定义方法
    • 在模型表中定义方法返回列表
    • 使用子序列化并传递many=True
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)
    user = models.ForeignKey(to='User', on_delete=models.CASCADE, default=1)

    def num_list(self):
        return [1, 2, 3]

    def info_dic(self):
        return {'id': 1, 'username': self.user.username}
##################################################

# ser.py
class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    desc = serializers.CharField()
    # 所有的模型表中的字段类型都可以使用CharField强转  # 但是此时该字段就只是字符串了
    l_str = serializers.CharField(source='num_list')
    # 使用source参数指定
    l = serializers.ListField(source='num_list')
    # 使用同名的字段
    num_list = serializers.ListField()

    info_dic = serializers.DictField()
    dic = serializers.DictField(source='info_dic')

image-20240422162856445

  • 多对多外键字段,序列化演示
class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    desc = serializers.CharField()

    # # 多对多外键字段
    # user = serializers.ListField()  # 将会报错
    # 使用 SerializerMethodField
    user_list = serializers.SerializerMethodField()

    def get_user_list(self, obj):
        return [user.username for user in obj.user.all()]

    # 使用子序列化
    user_list2 = UserSer(source='user', many=True)

【7.2】反序列化使用场景

  • 常用于多对多字段
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)
    user = models.ManyToManyField(to='User')


class User(models.Model):
    username = models.CharField(max_length=32, default='aaa')
    
#################################################

# ser.py
class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    desc = serializers.CharField()

    # 使用ListField接收列表数据  # write_only表示该字段只进行反序列化  # 只用来接收前端的数据并保存
    user = serializers.ListField(write_only=True)
    # user = serializers.CharField(write_only=True)  # "Not a valid string."  # 将会报错

    # 使用DictField接收字典形式数据
    task_info = serializers.DictField(write_only=True)
    
    
    
    def create(self, validated_data):
        # 使用post创建对象时,必须重写create方法
        # 否则会报错
        print(validated_data)
        # {'name': 'task3', 'desc': 'task3-desc', 'user': [1, 2], 'task_info': {'time': 'xxxx', 'style': 'aaaa'}}
        return validated_data  # 正常需要返回创建号的对象  # 此处将会返回序列化类序列化的数据
    
#################################################    
# views.py
class TaskView(APIView):
    def get(self, request):
        ser = TaskSer(Task.objects.all(), many=True)
        return Response(ser.data)

    def post(self, request):
        ser = TaskSer(data=request.data)
        # 反序列化校验
        ser.is_valid(raise_exception=True)
        # 调用create方法
        ser.save()
        return Response(ser.data if ser.is_valid() else ser.errors)

image-20240422165811807

【8】read_onlywrite_only

  • 当我们希望某些字段只执行序列化,也就是返回给前端时,可以使用read_only
  • 当我们希望某些字段只执行反序列化,也就是要求前端传值交由我们保存,可以使用write_only
  • 默认不填代表着,该字段既做序列化又做反序列化
class TaskSer(serializers.Serializer):
    # 模型表中有的字段将会一一对应
    name = serializers.CharField()
    desc = serializers.CharField()

    # 只做序列化,将用户信息展示给前端
    user = UserSer(many=True, read_only=True)

    # 只做反序列化,需要用户传值
    user_l = serializers.ListField(write_only=True)
    task_info = serializers.DictField(write_only=True)

image-20240422170618201

image-20240422170624778

【补充】其他知识

【1】字段参数validators

  • 在字段中添加validators选项参数,可以补充验证行为,如
def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("图书不是关于Django的")

class BookInfoSerializer(serializers.Serializer):
    """图书数据序列化器"""
    title = serializers.CharField(label='名称', max_length=20, validators=[about_django])
    pub_date = serializers.DateField(label='发布日期', required=False)
    read = serializers.IntegerField(label='阅读量', required=False)
    comment = serializers.IntegerField(label='评论量', required=False)
    image = serializers.ImageField(label='图片', required=False)

【2】反序列化校验的三层校验

  1. 执行字段内部的校验
    • max_length / min_length 等
    • validators :是一个列表,可以传多个校验函数,将会依次执行列表中的所有校验函数
  2. 执行局部钩子:validate_字段
  3. 执行全局钩子:validate

【3】无法直接使用source参数反向查询

  • 直接使用 source 参数是无法实现复杂的反向查询的。source 参数通常用于简单的字段访问,例如直接从模型实例的属性中获取数据。
  • 如果需要进行复杂的反向查询,例如从关联模型中获取相关对象列表,需要使用 SerializerMethodField 并定义一个方法来实现

【9】模型类序列化类(ModelSerializer)

  • DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
  • ModelSerializer与常规的Serializer相同,但提供了:
    • 基于模型类自动生成一系列字段
    • 基于模型类自动为Serializer生成validators,比如unique_together
    • 包含默认的create()和update()的实现
from rest_framework.serializers import ModelSerializer

class XXX(ModelSerializer):
     # 不在模型表中的字段
    字段 = serializers.CharField()
    class Meta:
        # 如果是模型表中的字段  # 可以在Meta中的fields参数中指定即可
        model=对应的表
        # 模型表中的字段将于序列化类的字段类型一一对应
        fields = 需要使用的字段
        #### 【注】此处的fields需要注意,不仅仅要填写在模型表中的字段,还需要填写额外的字段 ####
        extra_kwargs = {
            '字段':{'额外添加的参数'}
        }
   
  • 【注】fields是所有使用到的字段
from .models import Task


class TaskSerV2(serializers.ModelSerializer):
    # 只做反序列化,需要用户传值
    user_l = serializers.ListField(write_only=True)
    task_info = serializers.DictField(write_only=True)
    aaa = serializers.CharField(source='name')

    class Meta:
        model = Task
        fields = '__all__'  # __all__ 表示模型表中的所有字段
        # fields = ['id','name','aaa','desc']  # 相当于
        extra_kwargs = {
            'name': {'max_length': 10}
        }
        
    '''如果所需字段就是表中需要的,就不需要重写create方法了'''
    def create(self, validated_data):
        # 由于Task表中并不需要下面两个字段,所以去除掉
        validated_data.pop('user_l')
        validated_data.pop('task_info')
        # 调用ModelSerializer中的create方法
        task = super().create(validated_data)
        return task

posted @ 2024-04-22 21:48  Lea4ning  阅读(14)  评论(0编辑  收藏  举报