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获取序列化后的字典或者列表(不是json格式字符串),然后交给Response返回json格式的字符串

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、测试

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

在这里插入图片描述

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

在这里插入图片描述

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

1、常用字段

字段字段构造方式详解
BooleanFieldBooleanField()布尔字段用于存储和表示真/假值。构造方法不需要参数。
NullBooleanFieldNullBooleanField()可空布尔字段是可以接受三个值的布尔字段:True、False和None(空值)。构造方法不需要参数。
CharFieldCharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)字符字段用于存储短文本数据。max_length指定字符的最大长度,min_length指定最小长度。allow_blank指定是否允许为空值。trim_whitespace指定是否在保存数据前去除首尾的空格。
EmailFieldEmailField(max_length=None, min_length=None, allow_blank=False)Email字段用于存储和验证电子邮件地址。max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
RegexFieldRegexField(regex, max_length=None, min_length=None, allow_blank=False)正则表达式字段用于存储和验证符合特定模式的数据。regex指定正则表达式,max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
SlugFieldSlugField(max_length=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9_-]+Slug字段用于存储URL友好的文本标识符。max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
URLFieldURLField(max_length=200, min_length=None, allow_blank=False)URL字段用于存储和验证URL地址。max_length指定最大字符长度,min_length指定最小长度。allow_blank指定是否允许为空值。
UUIDFieldUUIDField(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的格式。
IPAddressFieldIPAddressField(protocol=‘both’, unpack_ipv4=False, **options)IP地址字段用于存储和验证IP地址。protocol参数指定所允许的IP地址协议类型,unpack_ipv4参数指定是否拆分IPv4地址。
IntegerFieldIntegerField(max_value=None, min_value=None)整数字段用于存储整数值。max_value指定最大值,min_value指定最小值。
FloatFieldFloatField(max_value=None, min_value=None)浮点数字段用于存储浮点数值。max_value指定最大值,min_value指定最小值。
DecimalFieldDecimalField(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指定最小值。
DateTimeFieldDateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)日期时间字段用于存储日期和时间。format参数指定日期时间的输出格式,input_formats参数指定输入格式。
DateFieldDateField(format=api_settings.DATE_FORMAT, input_formats=None)日期字段用于存储日期。format参数指定日期的输出格式,input_formats参数指定输入格式。
TimeFieldTimeField(format=api_settings.TIME_FORMAT, input_formats=None)时间字段用于存储时间。format参数指定时间的输出格式,input_formats参数指定输入格式。
DurationFieldDurationField()持续时间字段用于存储一段时间的持续时间。构造方法不需要参数。
ChoiceFieldChoiceField(choices) choices与Django的用法相同选择字段用于存储和验证预定义选项中的一个值。choices参数指定可选的选项值。
MultipleChoiceFieldMultipleChoiceField(choices)多选字段用于存储和验证多个预定义选项中的值。choices参数指定可选的选项值。
FileFieldFileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)文件字段用于上传和保存文件。max_length指定文件名的最大长度,allow_empty_file指定是否允许为空文件。use_url指定是否使用文件的URL路径。
ImageFieldImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)图片字段用于上传和保存图片文件。max_length指定文件名的最大长度,allow_empty_file指定是否允许为空文件。use_url指定是否使用图片的URL路径。
ListFieldListField(child=, min_length=None, max_length=None)列表字段用于存储和验证列表类型的数据。child参数指定列表中元素的类型,min_length指定最小长度,max_length指定最大长度。
DictFieldDictField(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)小结

  • 视图函数传入的instance不一定是对象,也可以传id到序列化类来使用
  • 序列化类中重写 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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

七、补充:翻译函数

在 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')

在这里插入图片描述

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 笨办法:再写个序列化类,单独用来反序列化
    • 2 通过 read_only write_only 控制

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

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

在这里插入图片描述

  • 在反序列化校验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表,例如:

在这里插入图片描述

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

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:类似钩子函数,将对应字段加上限制

ut(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

```python
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:类似钩子函数,将对应字段加上限制
  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值