drf—序列化组件

1、Serializers

Serializer类继承于BaseSerializer类,它的__init__方法中:

第一个参数为instance,用于接收需要被序列化的model对象

第二个参数是data,用于接收被反序列化的数据,如客户端post请求的request.data


1.1 序列化

1、写了哪些字段就序列化哪些字段,即展示给客户端的字段数据

2、字段名,可以是相应的model表类中定义的字段,也可以临时自定义字段属性

3、使用序列化方式,返回的结果是一个对象,返回给前端的数据存储在它的data属性中

from rest_framework import serializers
class UserSerializer(serializers.Serializer):
    # 表类User中的字段
    name = serializers.CharField()
    password = serializers.CharField()
    sex = serializers.CharField()
    # icon = serializers.ImageField()
    
    # 自定义字段
    img = serializers.SerializerMethodField()
    def get_img(self, obj):  
        return f"http://127.0.0.1:8000{settings.MEDIA_URL}{str(obj.icon)}"

    '''
    注意点:
    1	序列化类中的字段就是要展示给用户看的字段
    
    2	自定义方法:
    		字段类型是SerializerMethodField类的空对象
    		get_自定义字段名 作为方法名,提供字段值
    
    3	可用自定义字段的方式,处理数据表字段数据后再展示,如上面的icon不需要再序列化,而是序列化img
    '''

1.2 反序列化

1、反序列化是一个校验过程,类似于forms组件的校验,写了哪些字段,就对哪些字段进行校验

2、除了字段参数的校验,还有局部、全局钩子函数进行校验,校验不通过,使用raise exceptions.ValidationError()抛出异常,若是全局校验,以键值对的形式填写错误信息

3、校验通过的数据存放在validated_data变量中,需要手动重写create方法或者update方法,实现真正的数据入库,并且需要将数据对象返回

4、调用反序列化类得到的对象就是反序列化之后的对象,是入库之后的数据对象或者添加了校验不通过的错误信息的数据对象,前者需要再一次序列化才能展示给前端,后者直接可以调用errors属性展示给前端

from rest_framework import serializers, exceptions

class UserSerializer(serializers.Serializer):
    # 表类User中的字段
    name = serializers.CharField(max_length=8,min_length=3,required=True)
    password = serializers.CharField(max_length=8,min_length=3,required=True)
    re_password = serializers.CharField(max_length=8,min_length=3,required=True)
    
    # 局部钩子: validate_字段名,value是字段值
    def validate_name(self,value):
        if 'g' in value.lower():
            raise exceptions.ValidationError('不能包含g或G')
    	return value
    
    # 全局钩子 
    def validate(self,attrs):
        # attrs是除了全局钩子其他校验都通过了的数据
        # 这一步之后,得到的数据就会存放到validated_data中,用于真正的入库,所以要将一些无法入库或者不需要入库的数据剔除,如仅作校验密码一致性的re_password
        password = attrs.get('password')
        re_password = attrs.pop('re_password')  
    	if password != re_password:
            raise exceptions.ValidationError('re_password':'两次密码不一致')
        return attrs    
		

1.3 调用

1、重写了用于序列化或者反序列化的类之后,就能调用它,对数据进行序列化或者反序列化

2、序列化的数据从属性data中获取

3、反序列化的校验错误信息从属性errors中获取,校验通过得到的是一个数据对象,需要经过序列化,才能展示给前端

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models,serializers

class User(APIView):
    # 伪代码,只写关键部分
    
    # 序列化
    def get(self, request, *args, **kwargs):
        user_obj = models.User.objects.get(pk=pk)
        user_ser = serializers.UserSerializer(user_obj)
        print(user_ser)
        return Response({
            'status':0,
            'msg':0,
            'results': user_ser.data
        })

	# 反序列化
    def post(self, request):
        user_ser = serializers.UserDeSerializer(data=request.data)
        if user_ser.is_valid():
            # 校验通过
            
            # save里面封装了新增和修改操作,在post请求中代表新增,在patch和put请求中代表修改
            user_obj = user_ser.save()
            return Response({
                'status': 0,
                'msg': 'ok',
                'results':serializers.UserSerializer(user_obj).data
            })
            pass
        else:
            # 校验失败
            return Response({
                'status': 0,
                'msg': 'ok',
                'results':user_ser.errors
            })
            pass

1.4 小结:自定义属性的方式

按照属性值的获取方式来区分

插拔式

# 一般用于可以从已有数据得到的新数据的定义
# 如从表类中的存储图片路径的字段icon重定义一个存储了图片url路径的字段img

class User(models.Model):
    # 这里只写相关字段的定义
    icon = models.ImageField(upload_to='icon', default='icon/default.jpg')
    
    @property
    def img(self):
        return f"http://127.0.0.1:8000{settings.MEDIA_URL}{str(self.icon)}"

自定义式

# 不能使用插拔式,即无法从已有数据得到目标数据的情况
# 如获取用户的二次确认密码,只能直接从客户端获取
# 此时,需要在用于序列化或者反序列化的类中自定义,使用

# 伪代码,只写相关字段
class UserSerializer(serializers.Serializer):
    re_password = serializers.CharField(required=True)
    
class UserModelSerializer(serializers.ModelSerializer):
    re_password = serializers.CharField(min_length=3,max_length=8,write_only=True)

2、ModelSerializers

1、定义类中类Meta,绑定表类,使得它知道给谁去create或者update

2、定义字段序列fields,一般用列表或者元组,存放所有的参与序列化和反序列化的字段

3、一般使用插拔式自定义字段,即在表类中使用@property定义属性

4、定义extra_kwargs字典,存放校验规则和区分字段参与序列化或者反序列化

'''
field:
	1	是一个元组或者列表
	2	定义过的字段,包括表类字段和自定义属性
	3	参与序列化或者反序列化的字段

extra_kwargs:
	1	是一个字典
	2	key是fields中的字段名,value是对应字段的校验规则
	3	校验规则中,用{'write_only':True}代表只参与反序列化,{'read_only':True}代表只参与序列化
	4	自定义的字段,默认{'read_only':True},且不能更改,否则报错,因为数据库中没有相应的字段
'''

class UserModelSerializer(serializers.ModelSerializer):
    re_password = serializers.CharField(min_length=3,max_length=8,write_only=True)  
    class Meta:
        model = models.User
        fields = ('name','password','sex','gender','icon','img','re_password')
        extra_kwargs = {
            'name':{'max_length':8,
                    'min_length':3,
                    'error_messages':{'max_length':'太长','min_length':'太短'}
                    },
            'password':{'write_only':True},
        }

    # 全局钩子与局部钩子的用法跟类Serializer相同
    def validate(self, attrs):
        pass

    def validate_name(self, value):
        pass

3、子序列化

3.1 子序列化的方式

1、子查询,通过关联表的序列化类获取外键字段数据,受到关联表序列化类的fields控制

2、depth深度查询,序列化的关联表的数据全部显示,不受关联表的序列化类中fields控制

3、自定义propoty属性通过orm连表查询获取数据,插拔式获取数据,数据受到自定义控制


3.2 代码演示

子查询

class BookModelSerializer(serializers.ModelSerializer):
    pass

class PublishModelSerializer(serializers.ModelSerializer):
    books = BookModelSerializer(many=True) 
    '''
    1	序列化外键字段,通过外键关联的表的序列化类定义序列化外键字段,
    
    2	由Python从上到下解释的特性知道,子表的序列化类必须提前定义
    
    3	字段名与正反向查询有关,正向查询时,字段名为外键名,反向查询时,字段名为模型表中定义外键时,设置的related_name值
    
    4	结果为多个数据,需要加many=True
    
    5	这样的方式,将会序列化出外键关联的所有相关数据,不仅仅是id
    '''

depth

class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = []

class PublishModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = []
        depth = 1
    '''
    1	depth表示自动查询的子表深度
    2	子表的序列化字段,不受子表的序列化类控制,而是序列化所有的字段
    '''

@property

# models.py

# 作为演示,此处只写外键字段
class Book(BaseModel):
    publish = models.ForeignKey(to='Publish', related_name='books', on_delete=models.SET_DEFAULT, default=1,db_constraint=False)
    
    author = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)

    @property
    def publish_info(self):
        return {
            'name': self.publish.name,
            'address': self.publish.address
        }

    @property
    def author_list(self):
        author_query = self.author.all()
        author_list = []
        for author in author_query:
            author_dic = {'name': author.name}
            try:
                author_dic['mobile'] = author.detail.mobile
            except:
                author_dic['mobile'] = None
            author_list.append(author_dic)

        return author_list

4、多数据操作

操作多个数据,需要注意:

1、查多数据

2、增多个数据的方法是在类ListSerializer中重写的,通过循环调用单个增的方式实现多个数据的增

3、由于无法确定改的数据的格式是否合法,drf没有提供多个数据的更新,需要自己重写


4.1 多数据的增(post)

# views.py
class Books(APIView):
    def post(self,request, *args, **kwargs ):
        if isinstance(request.data, dict):
            many = False
        elif isinstance(request.data, list):
            many = True
        else:
            return Response(data='error')
        print(many)
        book_ser = serializers.BookModelSerializer(data=request.data, many=many)
        book_ser.is_valid(raise_exception=True)
        book_obj_or_list = book_ser.save()
        return response.APIResponse(results=serializers.BookModelSerializer(book_obj_or_list).data)


4.2 多数据的改(put)

1、需要重写update方法

2、视图函数中,需要对提交的数据进行严格的数据格式校验


重写update方法

class BookListSerializer(serializers.ListSerializer):
    def update(self, instance_list, validated_list_data):
        return [
            self.child.update(instance_list[index], attrs) for index, attrs in enumerate(validated_list_data)
        ]

视图函数

# views.py
class Books(APIView):
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            try:
                instance = models.Book.objects.get(is_delete=False, pk=pk)
            except:
                return Response({'detail': 'pk_error'},status=400)
            book_ser = serializers.BookModelSerializer(instance=instance, data=request.data)
            book_ser.is_valid(raise_exception=True)
            book_obj = book_ser.save()
            return response.APIResponse(results=serializers.BookModelSerializer(book_obj).data)

        else:
            request_data = request.data
            try:
                pks = []
                for dic in request_data:
                    pk = dic.pop('pk')
                    pks.append(pk)
                instance_list = models.Book.objects.filter(is_delete=False,pk__in=pks).all()
                if len(pks) != len(instance_list):
                    raise Exception('pk不存在')
            except Exception as e:
                return Response({'detail':e},status=400)

            book_ser = serializers.BookModelSerializer(instance=instance_list, data=request.data, many=True)
            book_ser.is_valid(raise_exception=True)
            book_list = book_ser.save()
            return response.APIResponse(results=serializers.BookModelSerializer(book_list,many=True).data)

4.3 多数据的改(patch)

与put的使用区别,反序列化的时候,多一个参数partial=True

posted @ 2019-12-30 22:11  W文敏W  阅读(295)  评论(0编辑  收藏  举报