Serializers组件详解
阅读原文时间:2023年07月09日阅读:4

Serializers组件

使用背景

因为每个语言都有自己的数据类型,不同语言要想数据传输,就必须指定一种各种语言通用的数据类型,如json,xml等等

序列化器允许把像查询集和模型实例这样的复杂数据转换为可以轻松渲染成JSONXML或其他内容类型的原生Python类型

示例(我将用3个例子引出今天的主题drf中的serializers)

from django.db import models

__all__ = ['Book','Publisher','Author']
CHOICES = [(1,'python'),(2,'go')]
class Book(models.Model):

    title = models.CharField('书名',max_length=12)
    category = models.IntegerField('类别',choices=CHOICES)
    pub_time = models.DateField('出版时间')
    publisher = models.ForeignKey('Publisher',verbose_name='出版社')
    authors = models.ManyToManyField('Author',verbose_name='作者')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = '书籍'
        verbose_name_plural = verbose_name
class Author(models.Model):
    name = models.CharField('姓名',max_length=12)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '作者'
        verbose_name_plural = verbose_name

class Publisher(models.Model):
    title = models.CharField('出版社',max_length=12)
    def __str__(self):
        return self.title
    class Meta:
        verbose_name = '出版社'
        verbose_name_plural = verbose_name

全文我都会用上面的model来做讲解

第一版(使用json.dumps序列化python数据类型,通过HttpResponse返回)

class BookView(View):
    def get(self,request,*args,**kwargs):
        book_queryset = models.Book.objects.values('id','title','category')
        str_book = json.dumps(list(book_queryset),ensure_ascii=False)
        return HttpResponse(str_book)
        // 因为python内置的json不支持序列化时间类型的数据,所以使用json.dumps存在局限性

第二版(使用django自带的JsonResponse返回所需的数据)

class BookView(View):
    def get(self,request,*args,**kwargs):
        book_queryset =models.Book.objects.values('id','title','pub_time','category','publisher')
        book_list = list(book_queryset)
        return JsonResponse(book_list,safe=False)
打印结果
[{"id": 1, "title": "金品梅", "pub_time": "2020-06-04", "category": 2, "publisher": 1}, {"id": 2, "title": "曾经爱过", "pub_time": "2020-06-04", "category": 2, "publisher": 2}]
        // 通过JsonResponse支持序列化时间类型的数据,但通过观察数据可发现,category和publisher并不是我们想要的数据格式,我们更希望传给前端的数据是具体的,而不是id,所以使用JsonResponse序列化的数据也不尽人意

第三版(使用django中serializers序列化数据)

class BookView(View):
    def get(self,request,*args,**kwargs):
        book_queryset = models.Book.objects.all()
        ret = serializers.serialize('json',book_queryset,ensure_ascii=False)
        return HttpResponse(ret)
//打印出的数据
[{"model": "app.book", "pk": 1, "fields": {"title": "金品梅", "category": 2, "pub_time": "2020-06-04", "publisher": 1, "authors": [1, 2]}}, {"model": "app.book", "pk": 2, "fields": {"title": "曾经爱过", "category": 2, "pub_time": "2020-06-04", "publisher": 2, "authors": [2, 3]}}]
        //通过打印后的结果可以看出,同样存在局限性,有很多我们不想要的数据,所以django自带的serializers也不是很给力

drf中serializers

通过前几版的序列化组件可以看出,django中自带的序列化不能很好的满足我们的需求,所以就引出restframework中的serializers

首先我们要遵循drf中的标准,使用CBV模式,且继承APIView,响应时返回Response

序列化

app/serializer
// 首先创建一个py文件
from rest_framework import serializers
class PublisherSerializers(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=12)

class AuthorSerializers(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=12)

class BookSerializers(serializers.Serializer):
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(max_length=12)
    category = serializers.CharField(source='get_category_display') //choices要想返回可读的数据类型,则需要制定source,里面的内容填orm的操作
    pub_time = serializers.DateField()
    publisher = PublisherSerializers()
    authors = AuthorSerializers(many=True) //对于外键的操作,则需要重新定义一个序列化类,里面的字段则是需要序列化的字段
    对于多对多外键,则需要添加many=True
//上述定义的字段,字段名要与model中相同,只能少不能多

app/views
from .serializer import BookSerializers
class BookView(APIView):
    def get(self,request,*args,**kwargs):
        book_queryset = models.Book.objects.all()
        book_sel = BookSerializers(book_queryset,many=True)
        return Response(book_sel.data)
//返回的结果
[
    {
        "id": 1,
        "title": "金品梅",
        "category": "go",
        "pub_time": "2020-06-04",
        "publisher": {
            "id": 1,
            "title": "菊花出版社"
        },
        "authors": [
            {
                "id": 1,
                "name": "will"
            },
            {
                "id": 2,
                "name": "weson"
            }
        ]
    },
    {
        "id": 2,
        "title": "曾经爱过",
        "category": "go",
        "pub_time": "2020-06-04",
        "publisher": {
            "id": 2,
            "title": "奥利给出版社"
        },
        "authors": [
            {
                "id": 2,
                "name": "weson"
            },
            {
                "id": 3,
                "name": "scott"
            }
        ]
    }
]
//很显然,是我们需要的数据格式

反序列化

刚刚上面讲解了后端发数据给前端,自然就有前端发数据给后端,前端发送数据的同时,我们要进行一些校验然后

保存到数据库,当然这些校验工作,drf的serializers也给我们提供了方法,

反序列化用的字段要和序列化区分开

serializer提供了.is_valid() 和.save()方法~~

//前端传给我们的数据不用跟后端一样,一般前端传来的数据都是id之类的,所以我们要定义不同的字段来区分正反
app/serializer
class PublisherSerializers(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=12)

class AuthorSerializers(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=12)

class BookSerializers(serializers.Serializer):
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(max_length=12)
    category = serializers.CharField(source='get_category_display')
    pub_time = serializers.DateField()
    post_publisher = serializers.IntegerField(write_only=True)
    post_authors = serializers.ListField(write_only=True)
    publisher = PublisherSerializers(read_only=True)
    authors = AuthorSerializers(many=True,read_only=True)
//值得注意的是,required=False表示该字段不是必须的,有则序列,无则放过,write_only表示只有在前端传过来时才校验,read_only表示只有在后端发送数据才校验

app/views
    def post(self,request):
        book_sel = BookSerializers(data=request.data) //request.data封装了wsgi原始request.POST的数据流,包含文件和json
        if book_sel.is_valid(): //类似于form表达,用于校验传送过来的数据是否合法
            book_sel.save() //保存,切记该方法的调用一定要在serializer中重写create方法,且返回实例对象
            return Response(book_sel.data) //返回新增的数据
        else:
            return Response(book_sel.errors) //返回错误

当前端要向后端更新数据时,我们要对部分数据进行校验

局部数据更新可用patch,全部更新可用put(当然这只是规范而已,你也可以用put更新局部数据,并不是标准)

我这里就用patch做局部数据更新演示

app/views
    def patch(self,request):
        id = request.data['id']
        book_obj = models.Book.objects.filter(pk=id).first()
        book_sel = BookSerializers(book_obj,data=request.data,partial=True) //这里因为是局部更新,要传入更改的book对象,data传入需要更改的数据,切记partial一定要为True,支持局部更新
        if book_sel.is_valid():
            book_sel.save()
            return Response(book_sel.data)
        else:
            return Response(book_sel.errors)

app/serializer
     def update(self, instance, validated_data):
        instance.title = validated_data.get('title',instance.title)
        instance.category = validated_data.get('category',instance.category)
        instance.pub_time = validated_data.get('pub_time',instance.pub_time)
        instance.publisher = validated_data.get('post_publisher',instance.publisher)
        if validated_data.get('post_authors'):
            instance.authors.set(validated_data.get('post_authors'))
        instance.save()
        return instance //有这更新,无则使用原来的值

其他校验手段

除了自定字段时添加的校验字段,drf还为我们提供了局部钩子来进行数据清洗

单个字段

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32)
    # 省略了一些字段 跟上面代码里一样的
    # 。。。。。
    def validate_title(self, value):
            if "python" not in value.lower():
                raise serializers.ValidationError("标题必须含有Python")
            return value

多个字段

 新增了一个上架时间字段
    # 省略一些字段。。都是在原基础代码上增加的
    # 。。。。。。

    # 对多个字段进行验证 要求上架日期不能早于出版日期 上架日期要大
    def validate(self, attrs):
        if attrs["pub_time"] > attrs["date_added"]:
            raise serializers.ValidationError("上架日期不能早于出版日期")
        return attrs

内置校验器和自定义校验器

//自定义校验器
def my_validate(value):
    if "敏感词汇" in value.lower:
        raise serializers.ValidationError("包含敏感词汇,请重新提交")
    return value

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=32, validators=[my_validate])
    # 。。。。。。
    def validate_title(self, value): //内置的局部钩子
            if "python" not in value.lower():
                raise serializers.ValidationError("标题必须含有Python")
            return value
注意:当对一个字段定义内外校验器,则自定义校验器优先级大于局部钩子的

ModelSerializer

通过前面的Serializers案例可以看出,的确满足了我们的需求,但是过于麻烦,且序列化的字段本身就属于model的字段,所以就有了ModelSerializer,方便我们进行数据序列化

定义一个ModelSerializer

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book //你所需要序列化的model
        fields = "__all__" //序列化的字段,__all__表示全部,如果想自定义的话 则用列表表示
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"] //fields和exclude和链式关系,并不是并行关系
        # 分别是所有字段 包含某些字段 排除某些字段
        # 如果只单纯用model和fields,choices和外键关系,默认用引用字段,即数字类型
        depth = 1 //该字段专门解析外键字段,depth 代表找嵌套关系的第几层,默认会取出当前层数的所有字段

自定义字段

我们可以声明一些字段来覆盖默认字段,来进行自定制~

比如我们的选择字段,默认显示的是选择的key,我们要给用户展示的是value。

class BookSerializer(serializers.ModelSerializer):
    category = serializers.CharField(source="get_category_display", read_only=True) //重写model中category字段

    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分别是所有字段 包含某些字段 排除某些字段
        depth = 1
    通过这种方式显然不可取,我们要反序列化就显得很困难了,另外depth默认只可读,所以我们要重新规划下字段

SerializerMethodField

外键关联的对象有很多字段我们是用不到的都传给前端会有数据冗余就需要我们自己去定制序列化外键对象的哪些字段~~

class BookSerializers(serializers.ModelSerializer):
    category_display = serializers.SerializerMethodField(read_only=True) //该字段用于自定义属性类别
    publisher_display = serializers.SerializerMethodField(read_only=True)//因为默认只显示主键,所以设置read_only=True
    authors_display = serializers.SerializerMethodField(read_only=True)

    def get_authors_display(self,obj): //格式get_加字段名,obj为当前model实例
        return [{'id':author.pk,'name':author.name} for author in obj.authors.all()] //返回需要的数据格式

    def get_publisher_display(self,obj):
        return {'id':obj.publisher_id,'title':obj.publisher.title}

    def get_category_display(self,obj):
        return obj.get_category_display()

    class Meta:
        model = models.Book
        fields = '__all__'
        read_only_fields = [xxx]   //只序列化的字段
        extra_kwargs = {'category':{'write_only':True},'publisher':{'write_only':True},'authors':{'write_only':True}} //extra_kwargs类似于字段中添加的属性

参考链接

https://www.cnblogs.com/GGGG-XXXX/articles/9568816.html Serializers 序列化组件