【6.0】DRF之序列化组件高级

发布时间 2023-07-31 12:24:29作者: Chimengmeng

【一】序列化高级之Source

【补充】on_delete的参数详解

models.CASCADE(级联删除):

当删除与该字段关联的对象时,所有相关的对象将被级联删除。例如,如果一个出版社对象被删除了,与该出版社相关联的所有图书对象也会被删除。

models.SET_DEFAULT:(设置为默认值):

当删除与该字段关联的对象时,该字段的值将设置为字段的默认值。这要求你同时定义了一个默认值。例如,如果一个出版社对象被删除了,与该出版社相关联的图书对象的出版社字段将被设置为默认的出版社。

models.SET_NULL:(设置为空):

  • 当删除与该字段关联的对象时,该字段的值将被设置为NULL,即空。这要求你将该字段设置为可为空。例如,如果一个出版社对象被删除了,与该出版社相关联的图书对象的出版社字段将被设置为空。

models.SET():(设置为指定值或执行函数):

  • 当删除与该字段关联的对象时,该字段的值将被设置为指定的值或执行指定的函数。你可以传递一个值作为参数,也可以传递一个函数,该函数将在删除关联对象时执行并返回一个值。例如,你可以设置一个函数,将与删除的出版社对象创建时间相关的特定日期作为图书对象的出版日期。

models.DO_NOTHING():(什么都不做):

  • 当删除与该字段关联的对象时,不会进行任何操作。这意味着数据库中的关联完整性约束将被忽略,并且你需要确保在应用层面处理好相关的逻辑。要在使用这个选项时生效,你需要将db_constraint参数设置为False,以避免在数据库级别建立外键约束。

【补充】逻辑外键

  • 逻辑外键是指在数据库中没有直接建立外键约束,但在应用程序的逻辑层面上表达了两个实体之间的关联关系。
  • 与物理外键不同,逻辑外键不会限制或强制执行数据的完整性。

优点:

  • 增删查改数据快:
    • 由于没有物理外键的约束,操作数据时不需要考虑维护和检查外键完整性,因此执行增删查改操作的效率更高。

缺点:

  • 容易出现脏数据:

    • 没有物理外键约束的情况下,存在风险引入脏数据或不一致数据。
    • 例如,当删除了一个关联对象时,没有物理外键约束可以阻止该对象在其他地方被引用。
  • 在实际应用中是否建立物理外键约束往往取决于具体的需求、系统设计和团队工作流程。

    • 在某些情况下,使用逻辑外键而不建立物理外键可以提高数据库操作的性能和灵活性。
    • 然而,这也意味着需要在应用层面上自行处理关联对象的完整性和数据一致性。
    • 团队需要通过其他方式来控制和保障数据的完整性,例如编写合适的验证逻辑或者使用事务等技术手段。
  • 总结而言,逻辑外键提供了一种灵活的方式来表达实体之间的关联关系,并且可以在特定情况下提高数据库操作的性能。

  • 然而,它也带来了管理和维护数据完整性的挑战,需要开发团队仔细评估和权衡使用该方法的利弊以及适应性。

【准备数据】

from django.db import models


# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)

    # on_delete
    # models.CASCADE: 级联删除,只要删除 publish ,跟publish关联的book,全部被删除
    # models.SET_DEFAULT: 只要删除 publish ,跟publish关联的book,publish的字段都会变成默认值,(建议配合default一起使用)
    # models.SET_NULL: 只要删除 publish ,跟publish关联的book,publish的字段都会变成空,(建议配合null一起使用)
    # models.SET(): (放个值),只要删除 publish ,跟publish关联的book,publish的字段都会set设的值或执行函数
    # models.DO_NOTHING(): 什么都不做,但是它需要跟db_constraint=False 配合,表示不建立外键约束,创建逻辑外检,而不是物理外检
    # 逻辑外检:虽然有外键关系但不会约束外键的数据存储要求
    # 不建立物理外键的好处?
    # 优点:增删查改数据快
    # 缺点:容易出现脏数据
    # 在实际应用中,都不建立物理外检而是建立逻辑外键
    publish = models.ForeignKey(to="Publish", on_delete=models.SET_NULL, null=True, blank=True)
    authors = models.ManyToManyField(to="Author")


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


class Author(models.Model):
    name = models.CharField(max_length=32)
    phone = models.CharField(max_length=11)
    # 本质上就是foreignkey,但是唯一,多的方是唯一,形成了一对一
    author_detail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)


class AuthorDetail(models.Model):
    email = models.CharField(max_length=32)
    age = models.IntegerField()

【1.0】指定前端数据的格式

  • 后端
class BookView(APIView):
    def get(self, request):
        book_queryset = models.Book.objects.all()
        book_ser = BookSerializer(instance=book_queryset, many=True)

        return Response({"code": 200, "msg": "请求成功", "result": book_ser.data})
  • 序列化类
from rest_framework import serializers


class BookSerializer(serializers.Serializer):
    # 这些字段名字要和模型表中相应的字段名字一致
    book_name = serializers.CharField(source="name")

    price = serializers.CharField()
  • 前端返回数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "name": "西游记",
            "price": "999"
        }
    ]
}

【2.0】需求:将前端展示的数据名字自定义

(1)放一个对象的属性

  • 序列化类
class BookSerializer(serializers.Serializer):
    # 这些字段名字要和模型表中相应的字段名字一致
    # 需求:展示给前端的字段是 book_name
    # 如果直接在字段前书写 book_name 会报错,因为数据库对应的表中没有 book_name 字段
    # 解决方式:可以通过 source 传递参数,指定某个字段
    book_name = serializers.CharField(source="name")

    price = serializers.CharField()
  • 前端返回的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "book_name": "西游记",
            "price": "999"
        }
    ]
}
  • 【问题】

  • 不能前面的字段名与source指定的字段名一样

from rest_framework import serializers


class BookSerializer(serializers.Serializer):

    name = serializers.CharField(source="name")

    price = serializers.CharField()
AssertionError: It is redundant to specify `source='name'` on field 'CharField' in serializer 'BookSerializer', because it is the same as the field name. Remove the `source` keyword argument.
  • 同一个字段在序列化器内可以序列化多次
class BookSerializer(serializers.Serializer):
    # 这些字段名字要和模型表中相应的字段名字一致
    # 需求:展示给前端的字段是 book_name
    # 如果直接在字段前书写 book_name 会报错,因为数据库对应的表中没有 book_name 字段
    # 解决方式:可以通过 source 传递参数,指定某个字段
    book_name = serializers.CharField(source="name")
    name = serializers.CharField()

    price = serializers.CharField()
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "book_name": "西游记",
            "name": "西游记",
            "price": "999"
        }
    ]
}

(2)放一个对象的方法

  • 模型层
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)

    # on_delete
    # CASCADE: 级联删除,只要删除 publish ,跟publish关联的book,全部被删除
    # SET_DEFAULT: 只要删除 publish ,跟publish关联的book,publish的字段都会变成默认值,(建议配合default一起使用)
    # SET_NULL: 只要删除 publish ,跟publish关联的book,publish的字段都会变成空,(建议配合null一起使用)
    # SET(): (放个值),只要删除 publish ,跟publish关联的book,publish的字段都会set设的值或执行函数
    publish = models.ForeignKey(to="Publish", on_delete=models.SET_NULL, null=True, blank=True)
    authors = models.ManyToManyField(to="Author")

    def nb_name(self):
        return self.name + '_nb'
  • 序列化类
class BookSerializer(serializers.Serializer):
    # 这些字段名字要和模型表中相应的字段名字一致
    # 需求:展示给前端的字段是 book_name
    #
    # (1)如果直接在字段前书写 book_name 会报错,因为数据库对应的表中没有 book_name 字段
    # 解决方式:可以通过 source 传递参数,指定某个字段
    # book_name = serializers.CharField(source="name")
    # name = serializers.CharField()

    # (2)第二种方法,放一个对象的方法,比如在书籍模型表中定义了一个 nb_name 方法
    name = serializers.CharField(source="nb_name")

    price = serializers.CharField()
  • 返回给前端的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "name": "西游记_nb",
            "price": "999"
        }
    ]
}

(3)关联查询

【1.0】直接书写字段名

  • 视图层
class BookView(APIView):
    def get(self, request):
        book_queryset = models.Book.objects.all()
        book_ser = BookSerializer(instance=book_queryset, many=True)

        return Response({"code": 200, "msg": "请求成功", "result": book_ser.data})
  • 序列化类
class BookSerializer(serializers.Serializer):

    # (3)关联查询,拿出出版社的名字
    publish_name = serializers.CharField(source="publish")

    price = serializers.CharField()
  • 返回给前端的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "publish_name": "Publish object (1)",
            "price": "999"
        }
    ]
}

【2.0】字段名.name取值

  • 序列化类
class BookSerializer(serializers.Serializer):

    # (3)关联查询,拿出出版社的名字
    publish_name = serializers.CharField(source="publish.name")

    price = serializers.CharField()
  • 返回给前端的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "publish_name": "演武出版社",
            "price": "999"
        }
    ]
}

【小结】Source的用法

  • 修改前端看到的字段key值--->source指定的必须是对象的属性
book_name = serializers.CharField(source='name')
  • 修改前端看到的value值,--->source指定的必须是对象的方法
# 表模型中写方法
def sb_name(self):
    return self.name + '_sb'
# 序列化类中
book_name = serializers.CharField(source='sb_name')
  • 可以关联查询(得有关联关系)
publish_name = serializers.CharField(source='publish.name')

【二】序列化高级用法之定制字段的两种方式

【1】方式一:SerializerMethodField

  • 序列化类
class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.CharField()
    # 拿出出版社的ID和名字和addr,放到一个字典中
    # 【1】方式一:
    # SerializerMethodField定制
    # 使用了SerializerMethodField,必须配合get_z字段名方法进行使用,这个方法返回什么,这个字段的值就是什么
    publish_detail = serializers.SerializerMethodField()

    def get_publish_detail(self, obj):
        '''

        :param obj: 序列化的 book对象
        :return:
        '''
        return {"id": obj.publish.pk, "name": obj.publish.name, "addr": obj.publish.addr}
  • 前端返回的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "name": "西游记",
            "price": "999",
            "publish_detail": {
                "id": 1,
                "name": "演武出版社",
                "addr": "梁山"
            }
        }
    ]
}
  • 练习
    • 拿出所有作者的信息
  • 序列化类
class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.CharField()
    # 拿出出版社的ID和名字和addr,放到一个字典中
    # 【1】方式一:
    # SerializerMethodField定制
    # 使用了SerializerMethodField,必须配合get_z字段名方法进行使用,这个方法返回什么,这个字段的值就是什么
    publish_detail = serializers.SerializerMethodField()

    def get_publish_detail(self, obj):
        '''

        :param obj: 序列化的 book对象
        :return:
        '''
        return {"id": obj.publish.pk, "name": obj.publish.name, "addr": obj.publish.addr}

    author_list = serializers.SerializerMethodField()

    def get_author_list(self, obj):
        lis = []
        for author in obj.authors.all():
            lis.append({"id": author.pk, "name": author.name, "phone": author.phone, "age": author.author_detail.age})

        return lis
  • 返回给前端的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "name": "西游记",
            "price": "999",
            "publish_detail": {
                "id": 1,
                "name": "演武出版社",
                "addr": "梁山"
            },
            "author_list": [
                {
                    "id": 1,
                    "name": "小叮当",
                    "phone": "13256568989",
                    "age": 18
                },
                {
                    "id": 2,
                    "name": "小顽皮",
                    "phone": "15236369898",
                    "age": 28
                }
            ]
        }
    ]
}

【2】方式二:

  • 序列化类
class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.CharField()
    # 拿出出版社的ID和名字和addr,放到一个字典中
    # 【2】方式二
    # (1)序列化类中如下写法
    # (2)到表模型中写一个方法必须交publish_detail,这个方法叫什么
    publish_detail = serializers.DictField()
  • 模型表
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)

    # on_delete
    # CASCADE: 级联删除,只要删除 publish ,跟publish关联的book,全部被删除
    # SET_DEFAULT: 只要删除 publish ,跟publish关联的book,publish的字段都会变成默认值,(建议配合default一起使用)
    # SET_NULL: 只要删除 publish ,跟publish关联的book,publish的字段都会变成空,(建议配合null一起使用)
    # SET(): (放个值),只要删除 publish ,跟publish关联的book,publish的字段都会set设的值或执行函数
    publish = models.ForeignKey(to="Publish", on_delete=models.SET_NULL, null=True, blank=True)
    authors = models.ManyToManyField(to="Author")

    def nb_name(self):
        return self.name + '_nb'

    # 做成静态方法也可以
    @property
    def publish_detail(self):
        return {"id": self.publish.pk, "name": self.publish.name, "addr": self.publish.addr}
  • 前端拿到的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "name": "西游记",
            "price": "999",
            "publish_detail": {
                "id": 1,
                "name": "演武出版社",
                "addr": "梁山"
            }
        }
    ]
}
  • 练习

    • 拿出所有作者的信息
  • 序列化类

class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.CharField()
    # 拿出出版社的ID和名字和addr,放到一个字典中
    # 【2】方式二
    # (1)序列化类中如下写法
    # (2)到表模型中写一个方法必须交publish_detail,这个方法叫什么
    publish_detail = serializers.DictField()

    author_list = serializers.ListField()
  • 模型表
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)

    # on_delete
    # CASCADE: 级联删除,只要删除 publish ,跟publish关联的book,全部被删除
    # SET_DEFAULT: 只要删除 publish ,跟publish关联的book,publish的字段都会变成默认值,(建议配合default一起使用)
    # SET_NULL: 只要删除 publish ,跟publish关联的book,publish的字段都会变成空,(建议配合null一起使用)
    # SET(): (放个值),只要删除 publish ,跟publish关联的book,publish的字段都会set设的值或执行函数
    publish = models.ForeignKey(to="Publish", on_delete=models.SET_NULL, null=True, blank=True)
    authors = models.ManyToManyField(to="Author")

    def nb_name(self):
        return self.name + '_nb'

    # 做成静态方法也可以
    @property
    def publish_detail(self):
        return {"id": self.publish.pk, "name": self.publish.name, "addr": self.publish.addr}

    def author_list(self):
        lis = []
        for author in self.authors.all():
            lis.append({"id": author.pk, "name": author.name, "phone": author.phone, "age": author.author_detail.age})
        return lis
  • 返回给前端的数据
{
    "code": 200,
    "msg": "请求成功",
    "result": [
        {
            "name": "西游记",
            "price": "999",
            "publish_detail": {
                "id": 1,
                "name": "演武出版社",
                "addr": "梁山"
            },
            "author_list": [
                {
                    "id": 1,
                    "name": "小叮当",
                    "phone": "13256568989",
                    "age": 18
                },
                {
                    "id": 2,
                    "name": "小顽皮",
                    "phone": "15236369898",
                    "age": 28
                }
            ]
        }
    ]
}

【3】总结

方式一:在序列化类中写

  • 1 写一个字段,对应的字段类是:SerializerMethodField
  • 2 必须对应一个 get_字段名的方法,方法必须接受一个obj,返回什么,这个字段对应的value就是什么

方式二:在表模型中写

  • 1 在表模型中写一个方法(可以使用:property),方法有返回值(字典,字符串,列表)
  • 2 在序列化类中,使用DictField,CharField,ListField

【三】反序列化之保存

【1.0】数据准备

  • 路由层
path('book/add/', BookDetailView.as_view())
  • 模型层
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    publish = models.ForeignKey(to="Publish", on_delete=models.SET_NULL, null=True, blank=True)
    authors = models.ManyToManyField(to="Author")

    def nb_name(self):
        return self.name + '_nb'

    # 做成静态方法也可以
    @property
    def publish_detail(self):
        return {"id": self.publish.pk, "name": self.publish.name, "addr": self.publish.addr}

    def author_list(self):
        lis = []
        for author in self.authors.all():
            lis.append({"id": author.pk, "name": author.name, "phone": author.phone, "age": author.author_detail.age})
        return lis
  • 路由层
class BookDetailView(APIView):
    def get(self, request, pk):
        book_queryset = models.Book.objects.filter(pk=pk).first()
        book_ser = BookSerializer(instance=book_queryset)
        return Response({"code": 200, "msg": "请求成功", "result": book_ser.data})

    def post(self, request):
        book_ser = BookSerializer(data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response({"code": 100, "msg": "保存成功", "result": []})
        else:

            return Response({"code": 101, "msg": "保存失败", "result": book_ser.errors})
  • 序列化类
# 序列化类:既用来做序列化,又用来做反序列化,还做数据校验
class BookSerializer(serializers.Serializer):
    # 既用来做序列化,又用来做反序列化
    name = serializers.CharField(max_length=8)
    price = serializers.CharField()


    # 只用来做序列化 - 只用来读 - 读数据的时候触发
    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)

    # 只用来做反序列化 - 只用来写 - 写数据的时候触发
    publish = serializers.IntegerField(write_only=True)
    # 作者可能有多个 - 所以用列表字段存
    authors = serializers.ListField(write_only=True)

    def create(self, validated_data):
        # ****************************validated_data的数据******************************
        print(validated_data)  # {'name': '夏洛特', 'price': '8888', 'publish': 2, 'authors': [1, 2, 3]}

        # ****************************原来的方法******************************
        # 语句执行不成功 :publish 必须传对象,authors是一个列表,解析不出来
        # book_obj = models.Book.objects.create(**validated_data) # Cannot assign "2": "Book.publish" must be a "Publish" instance.

        # ****************************分析******************************
        # 等同于
        # book_obj = models.Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'), publish=validated_data.get('publish'), authors=validated_data.get('authors'))
        # 等同于
        # book_obj = models.Book.objects.create(name= '夏洛特', price=8888, publish=2, authors=[1, 2, 3])
        # 等同于 publish_id (publish字段会执行成功,但是authors还是一个列表解析不出来)
        # book_obj = models.Book.objects.create(name= '夏洛特', price=8888, publish_id=2, authors=[1, 2, 3])

        # ****************************优化******************************
        # [1]先创建 book_obj 对象
        book_obj = models.Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'))
        # [2]修改publish字段值
        book_obj.publish_id = validated_data.get('publish')
        # [3]修改author字段值 -- 列表只用一个 * 打散就可以了
        book_obj.authors.add(*validated_data.get('authors'))
        book_obj.save()
        return book_obj

【2.0】优化序列化类的字段

  • 修改publish字段成 publish_id
class BookSerializer(serializers.Serializer):
    # 既用来做序列化,又用来做反序列化
    name = serializers.CharField(max_length=8)
    price = serializers.CharField()


    # 只用来做序列化 - 只用来读 - 读数据的时候触发
    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)

    # 只用来做反序列化 - 只用来写 - 写数据的时候触发
    publish_id = serializers.IntegerField(write_only=True)
    # 作者可能有多个 - 所以用列表字段存
    authors = serializers.ListField(write_only=True)

    def create(self, validated_data):

        # ****************************优化******************************
        # [1]先创建 book_obj 对象/修改publish字段值
        book_obj = models.Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'),publish_id = validated_data.get('publish'))
        # [3]修改author字段值 -- 列表只用一个 * 打散就可以了
        book_obj.authors.add(*validated_data.get('authors'))
        book_obj.save()
        return book_obj

【3.0】优化语句

  • 先把列表弹出,再打散
def create(self, validated_data):
    # [1]先创建 book_obj 对象/修改publish字段值
    authors = validated_data.pop('authors')
    book_obj = models.Book.objects.create(**validated_data)
    # [3]修改author字段值 -- 列表只用一个 * 打散就可以了
    book_obj.authors.add(*authors)
    book_obj.save()
    return book_obj

【4.0】增减数据

{
    "name":"夏洛特",
    "price":8888,
    "publish_id":2,
    "authors":[1,2,3]

}
  • 后端返回数据
{
    "code": 100,
    "msg": "保存成功",
    "result": []
}

【四】反序列化之修改

  • 路由层
path('book/set/<int:pk>/', BookDetailView.as_view())
  • 视图层
class BookDetailView(APIView):
    def get(self, request, pk):
        book_queryset = models.Book.objects.filter(pk=pk).first()
        book_ser = BookSerializer(instance=book_queryset)
        return Response({"code": 200, "msg": "请求成功", "result": book_ser.data})

    def post(self, request):
        book_ser = BookSerializer(data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response({"code": 100, "msg": "保存成功", "result": []})
        else:

            return Response({"code": 101, "msg": "保存失败", "result": book_ser.errors})

    def put(self, request, pk):
        book_obj = models.Book.objects.filter(pk=pk).first()
        book_ser = BookSerializer(data=request.data, instance=book_obj)
        if book_ser.is_valid():
            book_ser.save()
            return Response({"code": 100, "msg": "更新成功", "result": []})
        else:

            return Response({"code": 101, "msg": "保存失败", "result": book_ser.errors})
  • 序列化类
# 序列化类:既用来做序列化,又用来做反序列化,还做数据校验
class BookSerializer(serializers.Serializer):
    # 既用来做序列化,又用来做反序列化
    name = serializers.CharField(max_length=8)
    price = serializers.CharField()

    # 只用来做序列化 - 只用来读 - 读数据的时候触发
    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)

    # 只用来做反序列化 - 只用来写 - 写数据的时候触发
    publish_id = serializers.IntegerField(write_only=True)
    # 作者可能有多个 - 所以用列表字段存
    authors = serializers.ListField(write_only=True)

    def create(self, validated_data):
        book_obj = models.Book.objects.create(**validated_data)
        # [3]修改author字段值 -- 列表只用一个 * 打散就可以了
        book_obj.authors.add(*authors)
        book_obj.save()
        return book_obj

    def update(self, instance, validated_data):
        authors = validated_data.pop("authors")
        for item in validated_data:
            setattr(instance, item, validated_data[item])

        instance.authors.set(authors)  # 先将原来的数据移除,再新增
        # 等同于
        # instance.authors.remove()
        # instance.authors.add(*authors)

        return instance
  • 前端访问路由
{{host}}app01/book/set/3/
  • 前端传入数据
{
   
    "name":"夏洛特",
    "price":9999,
    "publish_id":2,
    "authors":[1,2,3]

}
  • 后端返回数据
{
    "code": 100,
    "msg": "更新成功",
    "result": []
}

【五】反序列化之校验

  • 视图类中调用:ser.is_valid()---> 触发数据的校验
    • 第一层:字段自己的:max_length,required ...
    • 第二层:字段自己的:配合一个函数name = serializers.CharField(max_length=8,validators=[xxx])
    • 第三层:局部钩子
    • 第四层:全局钩子

【六】ModelSerializer

  • 之前写的序列化类,继承了Serializer
  • 写字段,跟表模型没有必然联系
class XXSerialzier(Serializer)
	id=serializer.CharField()
	name=serializer.CharField()
    
# XXSerialzier既能序列化Book,又能序列化Publish
  • 现在学的ModelSerializer
  • 表示跟表模型一一对应,用法跟之前基本类似

【1.0】修改返回数据格式方式一

class BookSerializer(serializers.ModelSerializer):
    # 好处
    # 【1】不用自己写字段,可以将表模型中的字段映射过来
    class Meta:
        model = Book  # 指定表模型
        # fields = "__all__" # 映射模型表中所有的字段
        fields = ["name", "price", 'publish_detail', 'author_list']  # 映射模型表中指定的字段,自定义字段一定也要在这里注册

    # 【2】自定制字段 : publish_id / authors_list
    # 方式一
    publish_detail = serializers.SerializerMethodField()

    def get_publish_detail(self, obj):
        '''

        :param obj: 序列化的 book对象
        :return:
        '''
        return {"id": obj.publish.pk, "name": obj.publish.name, "addr": obj.publish.addr}

    author_list = serializers.SerializerMethodField()

    def get_author_list(self, obj):
        lis = []
        for author in obj.authors.all():
            lis.append({"id": author.pk, "name": author.name, "phone": author.phone, "age": author.author_detail.age})

        return lis

【2.0】修改返回数据格式方式二

class BookSerializer(serializers.ModelSerializer):
    # 好处
    # 【1】不用自己写字段,可以将表模型中的字段映射过来
    class Meta:
        model = Book  # 指定表模型
        # fields = "__all__" # 映射模型表中所有的字段
        fields = ["name", "price", 'publish_detail', 'author_list']  # 映射模型表中指定的字段,自定义字段一定也要在这里注册

    # 【2】自定制字段 : publish_id / authors_list
    # 方式二
    # (1)序列化类中如下写法
    # (2)到表模型中写一个方法必须交publish_detail,这个方法叫什么
    publish_detail = serializers.DictField()

    author_list = serializers.ListField()

【3.0】反序列化新增数据

class BookSerializer(serializers.ModelSerializer):

    class Meta:
        model = Book  # 指定表模型
        # fields = "__all__" # 映射模型表中所有的字段
        fields = ["name", "price", 'publish_detail', 'author_list', "publish", "authors"]  # 映射模型表中指定的字段,自定义字段一定也要在这里注册
        extra_kwargs = {
            "name": {"max_length": 10, "required": True},  # 可以新增额外的限制条件
            "publish": {"write_only": True},
            "authors": {"write_only": True},
        }

    # 方式二
    # (1)序列化类中如下写法
    # (2)到表模型中写一个方法必须交publish_detail,这个方法叫什么
    publish_detail = serializers.DictField(read_only=True)

    author_list = serializers.ListField(read_only=True)

    # 全局钩子 / 局部钩子
  • 前端传入数据
{
    "name":"夏洛特11",
    "price":8888,
    "publish_id":2,
    "authors":[1,3]

}
{
    "code": 100,
    "msg": "保存成功",
    "result": []
}
  • 这种方法基本不用重写 create / update 方法

【总结】

  • 写序列化类,继承ModelSerializer

  • 在序列化类中,再写一个类,必须叫

    class Meta:
        model=表模型
        fields=[] # 要序列化的字段
    
  • 可以重写字段,一定不要放在class Meta

    • 定制字段,跟之前讲的一样
  • 自定制的字段,一定要在fields中注册一下

  • class Meta: 有个extra_kwargs,为某个字段定制字段参数

  • 局部钩子,全局钩子,完全一致

  • 大部分请情况下,不需要重写 create和update了

【七】反序列化校验源码分析

【1】源码位置分析

  • ser.is_valid:入口

  • BookSerializer--->

    • Serializer--->
    • BaseSerializer--->
    • is_valid--->
    • 继承了Field
  • is_valid 方法

def is_valid(self, *, raise_exception=False):
    # self中没有_validated_data,只有执行完后,才有
    # 先调用 is_valid 方法进行数据校验,数据校验完后才有 _validated_data
    if not hasattr(self, '_validated_data'):
        try:
            # 核心---》这一句
            # 想看它的源代码,按住ctrl+鼠标点击是不对的 ---> 只能找当前类的父类
            #但它真正的执行是,从根上开始找
            self._validated_data = self.run_validation(self.initial_data)
        except ValidationError as exc:
            self._validated_data = {}
            self._errors = exc.detail
        else:
            self._errors = {}

            if self._errors and raise_exception:
                raise ValidationError(self.errors)

                return not bool(self._errors)
  • self.run_validation(self.initial_data)

    • 不能按住ctrl+鼠标点点击,要从根上开始找
  • Serializerrun_validation

def run_validation(self, data=empty):
    # 局部钩子
    value = self.to_internal_value(data)
    try:
        # 全局钩子
        value = self.validate(value) # BookSerializer只要写了,优先执行它的
    except (ValidationError, DjangoValidationError) as exc:
        raise ValidationError(detail=as_serializer_error(exc))

    return value
  • self.to_internal_value(data)--->
    • Serializer类的方法
def to_internal_value(self, data):
    		#序列化类中写的一个个的字段类的对象列表
            for field in fields: 
                # 一个field是name对象,field.field_name字符串 name
                # self是谁的对象:序列化类的对象,BookSerializer的对象  validate_name
                validate_method = getattr(self, 'validate_' + field.field_name, None)
                     try:
                    # 字段自己的校验规则
                    validated_value = field.run_validation(primitive_value)
                    if validate_method is not None:
                        # 局部钩子
                        validated_value = validate_method(validated_value)
                except ValidationError as exc:
                    errors[field.field_name] = exc.detail
                except DjangoValidationError as exc:
                    errors[field.field_name] = get_error_detail(exc)
                except SkipField:
                    pass
                else:
                    set_value(ret, field.source_attrs, validated_value)

            if errors:
                raise ValidationError(errors)

            return ret
  • 总结:
    • ser.is_valid---》
    • 走局部钩子的代码---》
    • 是通过反射获取BookSerializer中写的局部钩子函数,如果写了,就会执行----》
    • 走全局钩子代码---》
    • self.validate(value)--->
    • 只要序列化类中写了,优先走自己的

【大总结】

【1】定制化序列化字段值source,字段参数

  • 指定一个要序列化的对象中得字段

  • 指定一个要序列化的对象中得方法,方法返回什么,字段就是什么

  • 指定连表操作

【2】定制序列化的字段之 两种方式(为了关联查询)

方式一

  • 在序列化类中写 SerializerMethodField
  • get_字段名(self,obj),返回什么这个字段就是什么

ser=BookSerialzier(instance=qs,many=True)---->ListSerializer[序列化对象,序列化对象]

ser=BookSerialzier(instance=book)---->BookSerialzier

  • 基于对象的跨表查询

    • 正向:book.publish
    • 反向:publish.book_set.all()
    正向查询:book.publish
    	正向查询通过外键字段将两个相关模型连接起来,并允许从一个模型(源模型)中访问关联模型的相关数据。
    	在这个例子中,假设有两个模型,分别是Book和Publish,它们之间通过外键字段建立了关系。
    	通过正向查询book.publish,我们可以从Book模型对象访问与其相关联的Publish模型对象的信息。
    
    反向查询:publish.book_set.all()
    	反向查询允许从关联模型对象访问与其相关联的源模型的数据。
    	通过这种方式,可以轻松地查找与目标模型相关联的所有源模型对象。
    	在这个例子中,假设Publish模型是Book模型的外键关联模型。
    	通过反向查询publish.book_set.all(),我们可以从Publish模型对象访问与其相关联的所有Book模型对象的集合。
    

方式二

  • 在表模型中写方法,方法返回结果
  • 在序列化类中,使用ListField,DictField...接收

【3】反序列化的保存和修改

  • 保存:

    • book:{name:xx,price:18,publish_id:1,authors:[1]}
  • 视图函数中:

    • ser=BookSerializer(data=request.data)

    • ser.is_valid()---->校验过后的数据--->ser.validated_data

    • 视图类中:ser.save()

    • 序列化类中:重写 create方法--->自己写保存

  • 修改:

    • book:{name:xx,price:18,publish_id:1,authors:[1]}
  • 视图函数中:

    • ser=BookSerializer(data=request.data,instance=book)
    • ser.is_valid()---->校验过后的数据--->ser.validated_data
    • 视图类中:ser.save()
    • 序列化类中:重写 update方法--->自己写修改

【4】反序列化校验的四层

【5】ModelSerializer 继承了 Serializer

  • 跟表做了关联

  • 序列化类中写:

    class Meta:
        model=Book
        fields=['id','自定制字段']  # 分析明白:哪些字段是读写,哪些字段是只读,哪些字段是只写
        extra_kwargs={'id':{'required':True,'write_only':True}}
        book_name=serializer.CharField(source='name',read_only=True)
    
  • 基本上不需要重写create和update了

  • 局部钩子,全局钩子都一样