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()
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()])
获取的数据形式:
[ { "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
- 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
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中自己也可以进行实现,如果自己已经实现了,就不会执行默认的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 ] }
{ 'publish': <Publish: 北京出版社>, 'pub_date': datetime.date(2019, 6, 17), 'authors': [<Author: 张三>], 'price': 12, 'title': '化学' }
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 ] }
- 删除实例
删除实例只需要执行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 ] }
四、源码剖析
在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/