• rest framework之序列化组件


    一、序列化

    1、序列化准备

    以下列表进行举例说明:

    class Book(models.Model):
        title=models.CharField(max_length=32)
        price=models.IntegerField()
        pub_date=models.DateField(null=True,blank=True)
        publish=models.ForeignKey("Publish",on_delete=models.CASCADE)
        authors=models.ManyToManyField("Author")
        def __str__(self):
            return self.title
    
    class Publish(models.Model):
        name=models.CharField(max_length=32)
        email=models.EmailField()
        def __str__(self):
            return self.name
    
    class Author(models.Model):
        name=models.CharField(max_length=32)
        age=models.IntegerField()
        def __str__(self):
            return self.name

    2、Serializer

    • 自定制显示方式

    Serializer相当于django中Form,必须自己自定义字段,以Book表为例:

    Book表中既有ForeignKey字段又有ManyToMany字段,对于ForeignKey字段的显示可以使用source参数,对于ManyToMany字段可以定义钩子函数,利用SerializerMethodField

    字段。

    class BookSerializer(serializers.Serializer):
    
        title = serializers.CharField(max_length=32)
        price = serializers.IntegerField()
        pub_date = serializers.DateField()
        publish = serializers.CharField(source="publish.name") #外键publish相当于publish对象
        authors = serializers.SerializerMethodField()
    
        def get_authors(self,row):
            """
            :param row:表示每一行的对象,也就是Book对象
            :return:
            """
            return "".join([item.name for item in row.authors.all()])

    在view中,注意取出的值是多条,需要传入many=True参数

    class BookView(APIView):
    
        def get(self,request):
            book_list=models.Book.objects.all()
    
            bs=BookSerializer(book_list,many=True)
            return Response(bs.data)
    [
        {
            "title": "语文",
            "price": 12,
            "pub_date": "2019-09-09",
            "publish": "北京出版社",
            "authors": "张三、李四、王五"
        },
        {
            "title": "数学",
            "price": 22,
            "pub_date": "2019-09-17",
            "publish": "天津出版社",
            "authors": "张三、王五"
        },
        {
            "title": "英语",
            "price": 56,
            "pub_date": "2019-09-19",
            "publish": "北京出版社",
            "authors": ""
        }
    ]
    获取的数据形式

    显然,这样就自定制了外键和ManyToMany字段的显示方式。

    • 嵌套关系表示

    另外还可以进行嵌套关系表示,将外键以及多对多对应的序列化表提前定义,然后进行套用:

    class PublishSerializer(serializers.Serializer):
        id=serializers.IntegerField()
        name = serializers.CharField(max_length=32)
        email = serializers.EmailField()
    
    class AuthorSerializer(serializers.Serializer):
        id=serializers.IntegerField()
        name= serializers.CharField(max_length=32)
        age = serializers.IntegerField()
    PublishSerializer、AuthorSerializer
    class BookSerializer(serializers.Serializer):
    
        title = serializers.CharField(max_length=32)
        price = serializers.IntegerField()
        pub_date = serializers.DateField()
        publish = PublishSerializer()
        authors = AuthorSerializer(many=True) 

    需要注意的是authors是多对多的关系,所以它可能一个Book对象对应多个Author,需要设置many=True的参数,否则报错。

    [
        {
            "title": "语文",
            "price": 12,
            "pub_date": "2019-09-09",
            "publish": {
                "id": 1,
                "name": "北京出版社",
                "email": "25511@qq.com"
            },
            "authors": [
                {
                    "id": 1,
                    "name": "张三",
                    "age": 12
                },
                {
                    "id": 2,
                    "name": "李四",
                    "age": 22
                },
                {
                    "id": 3,
                    "name": "王五",
                    "age": 45
                }
            ]
        },
        {
            "title": "数学",
            "price": 22,
            "pub_date": "2019-09-17",
            "publish": {
                "id": 2,
                "name": "天津出版社",
                "email": "tianjinchubanshe@edu.com"
            },
            "authors": [
                {
                    "id": 1,
                    "name": "张三",
                    "age": 12
                },
                {
                    "id": 3,
                    "name": "王五",
                    "age": 45
                }
            ]
        },
        {
            "title": "英语",
            "price": 56,
            "pub_date": "2019-09-19",
            "publish": {
                "id": 1,
                "name": "北京出版社",
                "email": "25511@qq.com"
            },
            "authors": []
        }
    ]
    获取的数据形式

    3、ModelSerializer

    • ModelSerializer与Django中的ModelForm类似,这样自己不需要自定义字段,只需要将对应的model进行关联即可:
    class BookModelSerializer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Book
                fields = '__all__'

    在views.py中替换序列化器即可:

    class BookView(APIView):
    
        def get(self,request):
            book_list=models.Book.objects.all()
            bs=BookModelSerializer(book_list,many=True)
            return Response(bs.data)
    [
        {
            "id": 1,
            "title": "语文",
            "price": 12,
            "pub_date": "2019-09-09",
            "publish": 1,
            "authors": [
                1,
                2,
                3
            ]
        },
        {
            "id": 2,
            "title": "数学",
            "price": 22,
            "pub_date": "2019-09-17",
            "publish": 2,
            "authors": [
                1,
                3
            ]
        },
        {
            "id": 3,
            "title": "英语",
            "price": 56,
            "pub_date": "2019-09-19",
            "publish": 1,
            "authors": []
        }
    ]
    获取的数据形式
    • 显然,这是数据库中保存的外键以及多对多的数据,页面上展示的还是需要自定制的,所以序列化器需要修改:
    class BookModelSerializer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Book
                fields = '__all__'
    
            publish = serializers.CharField(source="publish.name") #外键publish相当于publish对象
            authors = serializers.SerializerMethodField()
    
            def get_authors(self,row):
                """
                :param row:表示每一行的对象,也就是Book对象
                :return:
                """
                return "".join([item.name for item in row.authors.all()])
    自定制BookModelSerializer

    获取的数据形式:

    [
        {
            "id": 1,
            "publish": "北京出版社",
            "authors": "张三、李四、王五",
            "title": "语文",
            "price": 12,
            "pub_date": "2019-09-09"
        },
        {
            "id": 2,
            "publish": "天津出版社",
            "authors": "张三、王五",
            "title": "数学",
            "price": 22,
            "pub_date": "2019-09-17"
        },
        {
            "id": 3,
            "publish": "北京出版社",
            "authors": "",
            "title": "英语",
            "price": 56,
            "pub_date": "2019-09-19"
        }
    ]
    数据形式
    • 当然也可以想Serializer一样进行嵌套序列化,它可以指定depth参数进行嵌套序列化:
    class BookModelSerializer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Book
                fields = '__all__'
                depth = 1
    嵌套序列化器

    获取的数据形式:

    [
        {
            "id": 1,
            "title": "语文",
            "price": 12,
            "pub_date": "2019-09-09",
            "publish": {
                "id": 1,
                "name": "北京出版社",
                "email": "25511@qq.com"
            },
            "authors": [
                {
                    "id": 1,
                    "name": "张三",
                    "age": 12
                },
                {
                    "id": 2,
                    "name": "李四",
                    "age": 22
                },
                {
                    "id": 3,
                    "name": "王五",
                    "age": 45
                }
            ]
        },
        {
            "id": 2,
            "title": "数学",
            "price": 22,
            "pub_date": "2019-09-17",
            "publish": {
                "id": 2,
                "name": "天津出版社",
                "email": "tianjinchubanshe@edu.com"
            },
            "authors": [
                {
                    "id": 1,
                    "name": "张三",
                    "age": 12
                },
                {
                    "id": 3,
                    "name": "王五",
                    "age": 45
                }
            ]
        },
        {
            "id": 3,
            "title": "英语",
            "price": 56,
            "pub_date": "2019-09-19",
            "publish": {
                "id": 1,
                "name": "北京出版社",
                "email": "25511@qq.com"
            },
            "authors": []
        }
    ]
    数据形式
    •  或者,提前创建外键和多对多字段对应的表的序列化器
    class PublishModelSerlizer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Publish
                fields = '__all__'
    
    class AuthorModelSerlizer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Author
                fields = '__all__'
    
    class BookModelSerializer(serializers.ModelSerializer):
            publish = PublishModelSerlizer()
            authors = AuthorModelSerlizer(many=True) #注意多对多中的many=True参数
    
            class Meta:
                model = models.Book
                fields = '__all__'
    嵌套序列化器
    [
        {
            "id": 1,
            "publish": {
                "id": 1,
                "name": "北京出版社",
                "email": "25511@qq.com"
            },
            "authors": [
                {
                    "id": 1,
                    "name": "张三",
                    "age": 12
                },
                {
                    "id": 2,
                    "name": "李四",
                    "age": 22
                },
                {
                    "id": 3,
                    "name": "王五",
                    "age": 45
                }
            ],
            "title": "语文",
            "price": 12,
            "pub_date": "2019-09-09"
        },
        {
            "id": 2,
            "publish": {
                "id": 2,
                "name": "天津出版社",
                "email": "tianjinchubanshe@edu.com"
            },
            "authors": [
                {
                    "id": 1,
                    "name": "张三",
                    "age": 12
                },
                {
                    "id": 3,
                    "name": "王五",
                    "age": 45
                }
            ],
            "title": "数学",
            "price": 22,
            "pub_date": "2019-09-17"
        },
        {
            "id": 3,
            "publish": {
                "id": 1,
                "name": "北京出版社",
                "email": "25511@qq.com"
            },
            "authors": [],
            "title": "英语",
            "price": 56,
            "pub_date": "2019-09-19"
        }
    ]
    数据形式

     二、保存、更改、删除实例

    上述获取序列化的结果相当于get请求拿到结果,但是如果是post、put请求需要创建、修改实例,这时需要使用create、update方法,create、update方法在Serializer类中需要自己来进行实现,但是在ModelSerizlizer类中已经进行了实现。

    • Seriallze类实现create、update方法:
    class BookSerializer(serializers.Serializer):
    
        title = serializers.CharField(max_length=32)
        price = serializers.IntegerField()
        pub_date = serializers.DateField()
        publish = serializers.CharField(source="publish.pk")
        authors = serializers.CharField(source="authors.all")
    
        def create(self, validated_data):
            book = models.Book.objects.create(
                title = validated_data["title"],
                price = validated_data["price"],
                pub_date = validated_data["pub_date"],
                publish_id=validated_data["publish"]["pk"]
            )
            book.authors.add(*validated_data["authors"])
            return book
    
         def update(self, instance, validated_data):
            """
            更新
            :param instance: 
            :param validated_data: 
            :return: 
            """
            instance.title = validated_data.get("title"),
            instance.price = validated_data.get("price"),
            instance.pub_date = validated_data.get("pub_date"),
            instance.publish = validated_data.get("publish")
            instance.authors.set(*validated_data.get("authors"))
            instance.save()
            return instance
    Serializer类保存更新实例
    •  ModelSerializer类自动实现create、update方法:
        def create(self, validated_data):
            """
            We have a bit of extra checking around this in order to provide
            descriptive messages when something goes wrong, but this method is
            essentially just:
    
                return ExampleModel.objects.create(**validated_data)
    
            If there are many to many fields present on the instance then they
            cannot be set until the model is instantiated, in which case the
            implementation is like so:
    
                example_relationship = validated_data.pop('example_relationship')
                instance = ExampleModel.objects.create(**validated_data)
                instance.example_relationship = example_relationship
                return instance
    
            The default implementation also does not handle nested relationships.
            If you want to support writable nested relationships you'll need
            to write an explicit `.create()` method.
            """
            raise_errors_on_nested_writes('create', self, validated_data)
    
            ModelClass = self.Meta.model
    
            # Remove many-to-many relationships from validated_data.
            # They are not valid arguments to the default `.create()` method,
            # as they require that the instance has already been saved.
            info = model_meta.get_field_info(ModelClass)
            many_to_many = {}
            for field_name, relation_info in info.relations.items():
                if relation_info.to_many and (field_name in validated_data):
                    many_to_many[field_name] = validated_data.pop(field_name)
    
            try:
                instance = ModelClass._default_manager.create(**validated_data)
            except TypeError:
                tb = traceback.format_exc()
                msg = (
                    'Got a `TypeError` when calling `%s.%s.create()`. '
                    'This may be because you have a writable field on the '
                    'serializer class that is not a valid argument to '
                    '`%s.%s.create()`. You may need to make the field '
                    'read-only, or override the %s.create() method to handle '
                    'this correctly.
    Original exception was:
     %s' %
                    (
                        ModelClass.__name__,
                        ModelClass._default_manager.name,
                        ModelClass.__name__,
                        ModelClass._default_manager.name,
                        self.__class__.__name__,
                        tb
                    )
                )
                raise TypeError(msg)
    
            # Save many-to-many relationships after the instance is created.
            if many_to_many:
                for field_name, value in many_to_many.items():
                    field = getattr(instance, field_name)
                    field.set(value)
    
            return instance
    ModelSerializer默认的create方法
        def update(self, instance, validated_data):
            raise_errors_on_nested_writes('update', self, validated_data)
            info = model_meta.get_field_info(instance)
    
            # Simply set each attribute on the instance, and then save it.
            # Note that unlike `.create()` we don't need to treat many-to-many
            # relationships as being a special case. During updates we already
            # have an instance pk for the relationships to be associated with.
            for attr, value in validated_data.items():
                if attr in info.relations and info.relations[attr].to_many:
                    field = getattr(instance, attr)
                    field.set(value)
                else:
                    setattr(instance, attr, value)
            instance.save()
    
            return instance
    ModelSerializer默认的update方法

    当然,在ModelSerializer中自己也可以进行实现,如果自己已经实现了,就不会执行默认的create、update方法

    • 创建实例
    class BookModelSerializer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Book
                fields = '__all__'
    
            def create(self, validated_data):
                print('create',validated_data)
                book = models.Book.objects.create(
                    title=validated_data["title"],
                    price=validated_data["price"],
                    pub_date=validated_data["pub_date"],
                    publish_id=validated_data["publish"].id  #处理ForeignKey字段
                )
                book.authors.add(*validated_data["authors"]) #处理多对多字段,在关系表中添加关系
                return book
    {
            
            "title": "化学",
            "price": 12,
            "pub_date": "2019-06-17",
            "publish": 1,
            "authors": [
                1
            ]
        }
    post请求添加数据的格式
    {
        'publish': <Publish: 北京出版社>,
        'pub_date': datetime.date(2019, 6, 17),
        'authors': [<Author: 张三>],
        'price': 12, 
        'title': '化学'
    }
    validated_data数据格式

    views.py

    class BookView(APIView):
    
        def post(self,request):
             bs=BookModelSerializer(data=request.data)
             if bs.is_valid():
                 bs.save() #执行create()
                 return Response(bs.data)

    post请求返回值就是新添加的对象:

    {
        "id": 4,
        "title": "化学",
        "price": 12,
        "pub_date": "2019-06-17",
        "publish": 1,
        "authors": [
            1
        ]
    }
    • 查看实例

    如果传入对象id,然后对某一个对象进行处理,又该如何呢?此时路由应该接受参数:

    urlpatterns = [
    
        re_path('books/(d+)/$', views.BookDetailView.as_view(), name="booksdetail")
    
    ]

    序列化器

    class BookModelSerializer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Book
                fields = '__all__'
                depth = 1

    views.py

    class BookDetailView(APIView):
        #查看单条数据
        def get(self,request,id):
            obj = models.Book.objects.filter(pk=id).first()
            bs=BookModelSerializer(obj)
            return Response(bs.data)

    假设,传入的地址是http://127.0.0.1:8000/books/1/

    {
        "id": 1,
        "title": "语文",
        "price": 12,
        "pub_date": "2019-09-09",
        "publish": {
            "id": 1,
            "name": "北京出版社",
            "email": "25511@qq.com"
        },
        "authors": [
            {
                "id": 1,
                "name": "张三",
                "age": 12
            },
            {
                "id": 2,
                "name": "李四",
                "age": 22
            },
            {
                "id": 3,
                "name": "王五",
                "age": 45
            }
        ]
    }
    获取的数据
    • 修改实例

    使用ModelSerializer序列化器,内部已经实现了update方法

    class BookModelSerializer(serializers.ModelSerializer):
    
            class Meta:
                model = models.Book
                fields = '__all__'

    views.py

    class BookDetailView(APIView):
    
        #修改某条数据
        def put(self,request,id):
            book_obj = models.Book.objects.filter(pk=id).first()
            bs = BookModelSerializer(book_obj,data=request.data)
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                return Response(bs.errors)

    该put方法的返回值就是当前修改的实例:

    {
        "id": 4,
        "title": "生物",
        "price": 22,
        "pub_date": "2019-06-17",
        "publish": 2,
        "authors": [
            1,
            2,
            3
        ]
    }
    获取的数据形式
    {
            
            "title": "生物",
            "price": 22,
            "pub_date": "2019-06-17",
            "publish": 2,
            "authors": [
                1,2,3
            ]
        }
    put方法提交的数据形式
    • 删除实例

    删除实例只需要执行delete方法即可:

    class BookDetailView(APIView):
    
        #删除某条数据
        def delete(self,request,id):
            models.Book.objects.filter(pk=id).delete()

     三、验证

    反序列化数据的时候,始终需要先调用is_valid()方法,然后再去访问经过验证的数据或保存对象实例。如果发生任何验证错误,.errors属性将包含表示生成的错误消息

    的字典。

    假设现在put方法提交的数据有空数据:

    {
            
            "title": "", #空数据
            "price": 22,
            "pub_date": "2019-06-17",
            "publish": "", #空数据
            "authors": [
                1,2,3
            ]
        }

    views.py

        #修改某条数据
        def put(self,request,id):
            book_obj = models.Book.objects.filter(pk=id).first()
            bs = BookModelSerializer(book_obj,data=request.data)
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                return Response(bs.errors) #返回错误
    {
        "publish": [
            "This field may not be null."
        ],
        "title": [
            "This field may not be blank."
        ]
    }
    错误信息
    • 字段级别的验证

      通过向自己定义Serializer子类中添加.validate_<field_name>方法来指定自定义字段级别的验证。这些类似于Django表单中的.clean_<field_name>方法。这些方法采用单个参数,即需要验证的字段值。validate_<field_name>方法应该返回一个验证过的数据或者抛出一个serializers.ValidationError异常。

    class BookModelSerializer(serializers.ModelSerializer):
    
            title = serializers.CharField(max_length=32)
    
            def validate_title(self,value):
                """
                :param value: 需要验证的值
                :return: 
                """
                if value != "hhh":
                    raise serializers.ValidationError("输入的值不正确")
                return value
    
            class Meta:
                model = models.Book
                fields = '__all__'

    这时,通过.errors取出错误信息。

    {
        "title": [
            "输入的值不正确"
        ]
    }
    错误信息
    • 对象级别验证

      要执行多个字段的联合验证,添加一个.validate()方法到自定义的Serializer子类中。这个方法采用字段值字典的单个参数,如果需要应该抛出一个 ValidationError异常,或者只是返回经过验证的值。

    class BookModelSerializer(serializers.ModelSerializer):
    
            title = serializers.CharField(max_length=32)
            price = serializers.IntegerField()
    
            def validate(self, attrs):
                """
                :param attrs: 是一个字典
                :return:
                """
                print(attrs)
                if attrs["title"] == "hhh" and attrs["price"] == 23:
                    raise serializers.ValidationError("value incorrectly!")
                return attrs
    
            class Meta:
                model = models.Book
                fields = '__all__'

    同样,通过.errors可以取出错误信息。

    {
        "non_field_errors": [
            "value incorectly!"
        ]
    }
    错误信息
    • 验证器

    序列化器上的各个字段都可以包含验证器,通过在字段实例上声明:

    定义某个字段的验证器(在序列化器的外部定义):

    def price_necessary(value):
        if value not in [45,23,33]:
            raise serializers.ValidationError("不是需要的值")
        return value

    在字段实例上应用验证器:

    class BookModelSerializer(serializers.ModelSerializer):
    
            price = serializers.IntegerField(validators=[price_necessary]) #使用验证器
    
            class Meta:
                model = models.Book
                fields = '__all__'
    {
        "price": [
            "不是需要的值"
        ]
    }
    错误信息
    • 部分更新

    默认情况下,序列化器必须传递所有必填字段的值,否则就会引发验证错误。不过可以使用 partial参数来允许部分更新

        def put(self,request,id):
            book_obj = models.Book.objects.filter(pk=id).first()
            bs = BookModelSerializer(book_obj,data=request.data,partial=True)#使用partial,这样不需要更新的参数可以不用提供
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                print('errors',bs.errors)
                return Response(bs.errors)
    #title值没有提供,没有报错
    {
            
           
            "price": 13,
            "pub_date": "2019-06-17",
            "publish": "2",
            "authors": [
                1,2,3
            ]
        }
    put请求发送的数据形式

     四、源码剖析

    在views.py中需要对序列化类进行实例化,但需要注意的是如果取出多个值,是需要传入mang=True的参数

        def get(self,request):
            book_list=models.Book.objects.all()
            bs=BookModelSerializer(book_list,many=True) #对序列化类进行实例化
            return Response(bs.data)

    执行父类BaseSerializer中__new__方法:

        def __new__(cls, *args, **kwargs):
            # We override this method in order to automagically create
            # `ListSerializer` classes instead when `many=True` is set.
            if kwargs.pop('many', False):
                return cls.many_init(*args, **kwargs)
            return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
    • many=True的情况(对queryset进行处理)

    可以知道当传入的参数有many=True时,返回many_init方法的执行结果,也就是执行ListSerializer类对象下的构造方法:

        def __init__(self, *args, **kwargs):
            self.child = kwargs.pop('child', copy.deepcopy(self.child))
            self.allow_empty = kwargs.pop('allow_empty', True)
            assert self.child is not None, '`child` is a required argument.'
            assert not inspect.isclass(self.child), '`child` has not been instantiated.'
            super(ListSerializer, self).__init__(*args, **kwargs)
            self.child.bind(field_name='', parent=self)
    • many=False情况(对object进行处理)

    当many=False时,从BaseSerializer中__new__方法中知道执行当前序列化类的构造方法,如果没有就找父类,也就是执行BaseSerializer中构造方法:

        def __init__(self, instance=None, data=empty, **kwargs):
            self.instance = instance
            if data is not empty:
                self.initial_data = data
            self.partial = kwargs.pop('partial', False)
            self._context = kwargs.pop('context', {})
            kwargs.pop('many', None)
            super(BaseSerializer, self).__init__(**kwargs)

    总结:

    在views中对序列化类进行实例化过程中,两种情况:

    (1)传入queryset和many=True,使用ListSerializer类进行处理

    (2)传入instance和many=False,使用serializer类进行处理

    值得注意的时在处理嵌套序列化时,如果是manyToMany字段时,不要忘了many=True参数,否则会序列化失败。

    参考:https://www.django-rest-framework.org/api-guide/serializers/

  • 相关阅读:
    富文本,NSAttributedString,当需要改变的内容有相同的时候的解决方法
    iOS 如何将证书和描述文件给其他人进行真机调试(Provisioning profile "描述文件的名字" doesn't include the currently selected device "你的手机名字".)
    iOS 去除字符串中的H5标签
    The dependency `AMapSearch (~> 3.0.0)` is not used in any concrete target. cocoapods在update或者install的时候报错解决办法
    iOS 3D Touch 五个快捷键的问题
    根据内容计算cell的高度
    iOS UIPickerView 可能会出现选中行不显示或者去掉上下两条横线的问题解决,
    iOS 开发 常用的正则验证表达式:电话 、邮箱等等
    使用MLeaksFinder检测项目中的内存泄露
    iOS 当请求到的数据是double类型,会失去精准度,并且去掉小数点后的0
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11462289.html
Copyright © 2020-2023  润新知