Fork me on GitHub

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.\nOriginal exception was:\n %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/

 

posted @ 2019-09-08 14:30  iveBoy  阅读(655)  评论(0编辑  收藏  举报
TOP