rest_framework的序列化

  • 使用restframework的序列化器的时候注意,传入是一个queryset类型的,需要额外设置 many=True
  • 修改数据时,partial=True的含义是 可以对部分进行修改,但是必须也使用 字典包裹。而且外键和关系管理对象 只能使用id更改

1.基本展示

#视图函数中
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.serializer import BookSerializer
#完成获取全部数据和新增数据
class BookList(APIView):
    def get(self, request, *args, **kwargs):
        all_books = models.Book.objects.all()
        ser_obj = BookSerializer(all_books,many=True)
        return Response(ser_obj.data)
    
    def post(self, request, *args, **kwargs):
        ser_obj = BookSerializer(data=request.data)  #data封装着数据 而不是 POST
        if ser_obj.is_valid():
            ser_obj.save()
            return Response(ser_obj.data)
        return Response(ser_obj.errors)
    
#完成获取单个数据和修改,删除单个数据
class Book(APIView):
    def get(self, request, *args, **kwargs):
        book_obj = models.Book.object.filter(pk=pk).first()
        ser_obj = BookSerializer(book_obj)
        return Response(ser_obj.data)
	def put(self, request, pk, *args, **kwargs):
        book_obj = models.Book.objects.filter(pk=pk).first()
        ser_obj = BookSerializer(book_obj,data=request.data, partial=True)
        if ser_obj.is_valid():
            ser_obj.save()
            return Response(ser_obj.data)
        return Response(ser_obj.errors)
    def delete(self, request, pk, *args, **kwargs):
        book_obj = models.Book.objects.filter(pk=pk).first()
        if book_obj:
            book_obj.delete()
            return Response({'msg':'删除成功'})
        return Response({'error':'删除错误'})
    
#序列化器的展示
from rest_framework import serializers
from app import models

class BookSerializer(serializers.Serializer):
	title = serializers.Charfield(max_length=32)
    price = serializers.DecimalField(max_digits=6, decimal_place=2)

2.有外键的序列化器

  • 获取和提交需要使用两个不同的字段,原因是获取和提交的方式不同。同时获取的设置属性 read_only=True 提交的设置属性 write_only=True
  • 获取的时候由于serializer没有外键这个方法,需要另构类实现
#方式一 这种方法 外键的名字必须和model相同
class BookSerializer(serializers.Serializer):
    pub = PublisherSerializer(read_only=True)     #必须是pub
    post_pub = serializers.IntergerField(write_only=True)
    
class PublisherSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    
#方式二   使用SerializeraMethodField
class BookSerializer(serializers.Serializer):
    pub_d1213 = serializer.SerializerMethodField(read_only=True)     #名字随意
    
    def get_pub_d1213(self, obj):
        ser_obj = PublisherSerializer(obj.pub)
        return ser_obj.data
    
class PublisherSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)

3.关系管理对象的序列化器

  • 获取的时候通过SerializerMethodField 调用自己类下的get_这个字段的方法。
  • 获取和提交也和外键一样,需要使用两个字段。
class BookSerializer(serializer.Serializer):
    author = serializers.SerializeraMethodField(read_only=True)
    def get_author(self, obj):
        ser_obj = AuthorSerializer(obj.author.all(), many=True)
        return ser_obj.data
class AuthorSerializer(serializer.Serializer):
    name = serializer.CharField(max_length=32)

4.序列器里新增和修改

  • create
def create(self, validated_data):
    obj = models.Book.objects.create(
        title=validated_data['title'],                                                     price=validated_data['price'],                                                     pub_date=validated_data['pub_date'],                                               pub_id=validated_data['post_pub'])
    obj.author.set(validated_data['post_author'])
    return obj
  • update
def update(self, instance, validated_data):
    instance.title = validated_data.get('title', instance.title)
    instance.price = validated_data.get('price', instance.price)
    instance.pub_date = validated_data.get('pub_date', instance.pub_date)
    instance.pub_id = validated_data.get('post_pub', instance.pub_id)
    instance.save()
    instance.author.set(validated_data.get('post_author', instance.author.all()))
    return instance

5.序列化器里的source

  • 关键字: source 作用是可以获得 一些无法直接通过字段获取的值,比如外键 choice字段 关系管理对象等
from rest_framework import serializers
class UserInfoSerializer(serializers.Serializer):
    xxxx = serializers.CharField(source='user_type')
    ooo = serializers.CharField(source='get_user_type_display')  # 相似于models中的get_字段_display,遇到choice就这么使用
    gp = serializers.CharField(source='group.title') # 通过点来获取值
    rls = serializers.CharField(source='roles.all')  # 多对多时,反向点all,返回的时Queryset列表,里面都都是对象

#1、通过source配置相应得字段,可以解决一对多(正向,一对一)和choices字段去值得问题,但是涉及到多对多时,就不能做到更详细得处理了,只能返回对象  <-----
#2、.all方法,只适用于关系管理对象字段上

6.resframework和model的使用

  • 优点:减少代码量,同时可以完成所有的增删改查,这里需要注意的是,由这种方法产生的外键和关系管理对象字段都是以id的形式最终表示,所以如果想要以关联的其他字段进行表示,需要重新写相关的字段,并标注对应的 write_only 和 read_only属性。
  • 基本逻辑都是,读的时候有外键和关系管理对象的无法直接通过字段读,所以需要构建,写的时候可以直接写。
  • 基本格式
class BookSerializer(serializer.Serializer):
    pub_info = serializzer.SerializerMethodField(read_only=True)  #重写字段
    author_info = serializer.SerializerMethodField(read_only=True)
    def get_pub_info(self, obj):
        ser_obj = PublisherSerializer(obj.pub)
        return ser_obj.data
    def get_author_info(self.obj):
        ser_obj = AuthorSerializer(obj.author.all(), many=True)
        return ser_obj.data

    class Meta:
        model = models.Book
        fields = '__all__'
        depth = 1  # 没写这之前,我们得到的数据都是如group,roles这种涉及到一对多和多对多的字段都是取到的pk,这之后就可以获得多个每个关联对象的所有字段  1~10范围 
        extra_kwargs = {    #相当于form的widget 批量添加属性
            'pub': {'write_only': True},
            'author': {'write_only': True}
        }

7.部分总结

1、写类
	1.1、继承Serializer
		class RoleSerializer(serializers.Serializer):
            id = serializers.IntegerField()
            title = serializers.CharField()  # title必须和models中的字段进行对应
   	1.2、继承ModelSerializer
   		class UserInfoSerializer(serializers.ModelSerializer):
            class Meta:
                model = models.UserInfo
                fields = ['id','username']  # '__all__'
                
2、自定义字段
	2.1、通过source:
		 a. choices字段:xxxx = serializers.CharField(source='user_type')
		 b. 一对多:gp = serializers.CharField(source='group.title')
		 
	2.2、通过SerializerMethodField()和自定义函数
		...
		def get_rls(self,row):  # 自定义函数,get_字段
            role_obj_list = row.roles.all()
            ret =[]
            for item in role_obj_list:
                ret.append({'id':item.id,'title':item.title})
            return ret
        
    2.3、自定义类

8.继续学习ModelSerializer:深度控制

class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        depth = 1  # 没写这之前,我们得到的数据都是如group,roles这种涉及到一对多和多对多的字段都是取到的pk,这之后就可以获得多个每个关联对象的所有字段
           
class UserinfoView(APIView):
    def get(self, *args, **kwargs):
        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        print(ser.data) 
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)
   
 # 所有结构
    [
    {
        "id": 1,
        "user_type": 1,
        "username": "序号话",
        "password": "123",
        "group": {
            "id": 1,
            "title": "A组"
        },
        "roles": [
            {
                "id": 1,
                "title": "医生"
            },
            {
                "id": 2,
                "title": "学生"
            }
        ]
    },
    {
        "id": 2,
        "user_type": 2,
        "username": "民人接",
        "password": "123",
        "group": {
            "id": 2,
            "title": "B组"
        },
        "roles": [
            {
                "id": 3,
                "title": "元昊"
            }
        ]
    }
]
  • 小提示
    1、depth 表示该表中如果都关系管理对象,就打开该对象的所有字段,表示深度,还可以depth=2
    2、官方建议depth=[0~10]
    
  • 通过HyperlinkedIdentityField重写外键管理对象字段,生成动态url(必须关联一个对象)
    class UserInfoSerializer(serializers.ModelSerializer):
        group = serializers.HyperlinkedIdentityField(view_name='gp',lookup_field='group.id',lookup_url_kwarg='xxx')  # 重写group,通过url别名生成路径,必须写别名,我们的url中的pk,系统默认也是取group中的pk,如果换个名字就报错了,可以通过lookup_field重写
        class Meta:
            model = models.UserInfo
            # fields = '__all__'
            fields = ['id', 'username', 'password', 'group', 'roles']
            depth = 0  #  默认为0
            
    class UserinfoView(APIView):   
        def get(self,request, *args, **kwargs):
            users = models.UserInfo.objects.all()
            ser = UserInfoSerializer(instance=users, many=True,context={'request':request})  # 需要写context
            print(ser.data)
            ret = json.dumps(ser.data, ensure_ascii=False)
            return HttpResponse(ret)
        
        
    class GourpSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.UserGroup
            fields = '__all__'
        
    class GourpView(APIView):  # 对每个组进行详细生成url
        def get(self,request, *args, **kwargs):
             pk = kwargs.get('pk')  # 通过路径传入
            obj = models.UserGroup.objects.filter(pk=pk).first()
            ser = GourpSerializer(instance=obj, many=False)
            ret = json.dumps(ser.data, ensure_ascii=False)
            return HttpResponse(ret)
        
     # url.py文件中
    urlpatterns = [
        ...
        url(r'(?P<version>[v1|v2]+)/userinfo/$',views.UserinfoView.as_view()),
        url(r'(?P<version>[v1|v2]+)/group/(?P<pk>\d+)/$',views.GourpView.as_view(),name = 'gp'),
    ]
    
     #[{"id": 1, "username": "序号话", "password": "123", "group": "http://127.0.0.1:8000/api/v1/group/1/", "roles": [1, 2]},{"id": 2, "username": "民人接", "password": "123", "group": "http://127.0.0.1:8000/api/v1/group/2/", "roles": [3]}]
    
    • 小提示:
      1、我们url中有一个pk参数,通过HyperlinkedIdentityField字段后面的别名参数生成url时,需要参数pk,默认是一当前user.pk传入的,显然不符合我们的要求,我们的group中只有两个组,用户只继承A组
      2、对每一条数据进行序列化的时候,需要添加 ser = UserInfoSerializer(instance=users, many=True,context={'request':request})的context参数
      
      
  • 升级版,修正url中的pk不对齐问题
    class UserInfoSerializer(serializers.ModelSerializer):
        group = serializers.HyperlinkedIdentityField(view_name='gp',   # 指定url别名
                                                     lookup_field='group_id', # 指定查找字段,可能是以他为参数,不能是group(对象)
                                                     lookup_url_kwarg='pk')  # 指定参数名
        class Meta:
            model = models.UserInfo
            # fields = '__all__'
            fields = ['id', 'username', 'password', 'group', 'roles']
            depth = 0
            
    class UserinfoView(APIView):
       
        def get(self,request, *args, **kwargs):
            users = models.UserInfo.objects.all()
            ser = UserInfoSerializer(instance=users, many=True,context={'request':request})  # 需要写context
            print(ser.data)
            ret = json.dumps(ser.data, ensure_ascii=False)
            return HttpResponse(ret)
        
      # [{"id": 1, "username": "序号话", "password": "123", "group1":"http://127.0.0.1:8000/api/v1/group/1/", "roles": [1, 2]}, {"id": 2, "username": "民人接", "password": "123", "group1": "http://127.0.0.1:8000/api/v1/group/2/", "roles": [3]}]
    
    
    • 小提示:
      1、在序列化类中定义group1字段,其以group.id作为url中的参数出入别名为'gp'的url中,再生成json返回数据
      	group1 = serializers.HyperlinkedIdentityField(view_name='gp',source='group.id')
          
      2、源码中再HyperlinkedIdentityField中第一句:
      	look_up = 'pk'  # 表示默认以pk字段为参数传入
      	view_name = None
      
      

10.序列化源码解析

  • 步骤一:代码分析

    class UserInfoSerializer(serializers.ModelSerializer):
        group = serializers.HyperlinkedIdentityField(view_name='gp',
        											lookup_field='group_id',
        											lookup_url_kwarg='pk')  
        class Meta:
            model = models.UserInfo
            # fields = '__all__'
            fields = ['id', 'username', 'password', 'group', 'roles']
            depth = 0
            
    class UserinfoView(APIView):   
        def get(self,request, *args, **kwargs):
            users = models.UserInfo.objects.all()
            ser = UserInfoSerializer(instance=users, many=True,context={'request':request}) 
            
    
            ret = json.dumps(ser.data, ensure_ascii=False)
            return HttpResponse(ret)
        
      # 以上述视图类为例子:
    1、url路径匹配带CBV,再get中对UserInfoSeriazer进行实例化,去该类中找构造函数init,所以往上走 --> ModelSerializer类,任然没有,再往上走 --> Serializer类,没有 --> BaseSerializer中找到__init__(),同时还有__new__方法,且先执行new方法,进行判断many参数
    	    def __new__(cls, *args, **kwargs):
                if kwargs.pop('many', False):  # 分析是否是一个Queryst列表
                    return cls.many_init(*args, **kwargs)
                return super().__new__(cls, *args, **kwargs)  # 对单个对象进行处理,从Field那获取空间self,然后执行自己的init方法
            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().__init__(**kwargs)
    
    2、打开many_init()函数,里面返回了
    		def many_init(cls, *args, **kwargs):
            	...
            	list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
                #调用ListSeralizer进行处理
            	return list_serializer_class(*args, **list_kwargs)
            
    3、回到序列化后我们调用,ser.data,点击查看源码
    	3.1 @property
            def data(self):
                ret = super().data  # 调用了BaseSerializer中的data属性
                return ReturnDict(ret, serializer=self)  # 返回一个有序字典
        3.2、打开父类的data属性:(用于对_data进行返回)
        	def data(self):
                ...
        	  if not hasattr(self, '_data'):
                if self.instance is not None and not getattr(self, '_errors', None): 
                    # 我们再去choices时使用过to_repersentions,用于获取对象中的值
                    self._data = self.to_representation(self.instance)
                    ...
               return self._data
        
        3.3、查找自己的self.to_representation方法,顶层是一个抛异常,最终我们在Serializer中找到了
        	 def to_representation(self, instance):
                """
                Object instance -> Dict of primitive datatypes.
                """
                ret = OrderedDict()
                fields = self._readable_fields # 可读字段,进行了过滤了字段
    
                for field in fields: # 对我们自定义的所有字段,循环
                    try:
                        # 调用CharField.get_attribute方法,instance就是我们传入的对象
                        attribute = field.get_attribute(instance)
                    except SkipField:
                        continue
                  check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) 
                															else attribute
                if check_for_none is None:
                    ret[field.field_name] = None
                else:
                    ret[field.field_name] = field.to_representation(attribute)
            return ret
        
        3.4、找到父父父类Field中的get_attrbute(self,instance)
        	 def get_attribute(self, instance):
                """
                Given the *outgoing* object instance, return the primitive value
                that should be used for this field.
                """
                try:
                    return get_attribute(instance, self.source_attrs) # 这里的source就是我们传入的source进行切割的列表,如'group.id'->[group,id], 我们再点开get_attrbute方法
                ...
        3.5、在进入更内层的get_attribute()
        	  def get_attribute(instance, attrs):
                    """
                    Similar to Python's built in `getattr(instance, attr)`,
                    but takes a list of nested attributes, instead of a single attribute.
                    Also accepts either attribute lookup on objects or dictionary lookups.
                    """
                    # 循环传入的source列表[group,title],或则['get_user_type_display']
                    for attr in attrs:
                        try:
                            if isinstance(instance, Mapping):
                                instance = instance[attr]
                            else:
                                instance = getattr(instance, attr) # 拿到一个对象字段,给instance
                        except ObjectDoesNotExist:
                            return None
                        if is_simple_callable(instance): # 判断是否是可以调用的如get_user_type_display
                            try:
                                instance = instance() # 执行函数
                     	...
                    return instance
    
    • 小提示:
      1、 many=True,将QuerySet列表交给ListSerializer处理
      	many=False ,将对象交给Serliazer处理
      
      2、单个对象:self.to_representation
         Queryset时:self.to_representation
      
      

11.序列化请求数据校验

  • 简单的从请求体中获取数据,并打印出form中的数据校验
    class UserGroupSerializer(serializers.Serializer):
        titile = serializers.CharField(error_messages={'required':'标题不能为空'}) # 因为继承了Field,所以都有
        
    class UserGourpView(APIView):
        def post(self,request, *args, **kwargs):
            print(request.body)  # 必须再data之前使用,否则data后就没有body了,b'{\n"name":"alex"\n}'
            print(request.data) # 已经配置了解析器 {'name': 'alex'}
            ser = UserGroupSerializer(data=request.data)
            if ser.is_valid():
                print(ser.validated_data)
            else:
                print(ser.errors)
            return HttpResponse('提交数据')
      
     # url中
    urlpatterns = [
    	...
        url(r'(?P<version>[v1|v2]+)/usergroup/$',views.UserGourpView.as_view()),
    ]
    
     # 运行结果
     {'titile': [ErrorDetail(string='标题不能为空', code='required')]}
    
  • 自定义类进行内置校验器校验
    class XXXValidator(object):
        def __init__(self, base):
            self.base = base  # 传入的参数
    
        def __call__(self, value): # 只要实例化就会触发__call__方法
            if not value.startswith(self.base):  # 必须以base开头
                message = '标题必须以 %s 开头.' % self.base
                raise serializers.ValidationError(message)
    
        def set_context(self, serializer_field):
            # 执行验证之前调用,serializer_fields是当前字段对象
            pass
    
    class UserGroupSerializer(serializers.Serializer):
        title = serializers.CharField(error_messages={'required':'标题不能为空'},
        												validators=[XXXValidator('中国'),])
        
    class UserGourpView(APIView):
        def post(self,request, *args, **kwargs):
            print(request.body)  # 必须再data之前使用,否则data后就没有body了,b'{\n"name":"alex"\n}'
            print(request.data) # 已经配置了解析器 {'name': 'alex'}
            ser = UserGroupSerializer(data=request.data)
            if ser.is_valid():
                print(ser.validated_data)
            else:
                print(ser.errors)
            return HttpResponse('提交数据')
    
    • 补充:

      当然可以通过函数进行内置校验器校验:
      def  validate_title(value):
        if 'mjj' in value:
              raise serializers.ValidationError
          
      class UserGroupSerializer(serializers.Serializer):
          title = serializers.CharField(error_messages={'required':'标题不能为空'},validators=[validate_title,])
      
  • 序列化的局部校验(钩子函数)
    class UserGroupSerializer(serializers.Serializer):
        title = serializers.CharField(error_messages={'required':'标题不能为空'},validators=[])
        
        def validate_title(self,value):  # validate_字段名,用来局部验证,局部钩子
            if 'mjj' in value:
                raise serializers.ValidationError
            return value
        
    class UserGourpView(APIView):
        def post(self,request, *args, **kwargs):
            print(request.body)  # 必须再data之前使用,否则data后就没有body了,b'{\n"name":"alex"\n}'
            print(request.data) # 已经配置了解析器 {'name': 'alex'}
            ser = UserGroupSerializer(data=request.data)
            if ser.is_valid():
                print(ser.validated_data)
            else:
                print(ser.errors)
            return HttpResponse('提交数据')
    
  • 序列化的全局钩子
    class UserGroupSerializer(serializers.Serializer):
        title = serializers.CharField(error_messages={'required':'标题不能为空'},validators=[])    
        def validate_title(self,value):  # validate_字段名,用来局部验证
            if 'mjj' in value:
                raise serializers.ValidationError
            return value
        
        def validate(self, attrs):
            print(attrs)  # OrderedDict([('title', 'mj')])
            if attrs['title'] == '666':
                raise serializers.ValidationError  #{'non_field_errors': [ErrorDetail(string='Invalid input.', code='invalid')]}
            return attrs
        
    class UserGourpView(APIView):
        def post(self,request, *args, **kwargs):
            print(request.body)  # 必须再data之前使用,否则data后就没有body了,b'{\n"name":"alex"\n}'
            print(request.data) # 已经配置了解析器 {'name': 'alex'}
            ser = UserGroupSerializer(data=request.data)
            if ser.is_valid():
                print(ser.validated_data)
            else:
                print(ser.errors)
            return HttpResponse('提交数据')
    
  • 使用extra_kwargs进行每个字段的扩展
    class PasswordValidator(object):
        def __init__(self, base):
            self.base = str(base)
    
        def __call__(self, value):
            if value != self.base:
                message = 'This field must be %s.' % self.base
                raise serializers.ValidationError(message)
    
        def set_context(self, serializer_field):
            """
            This hook is called by the serializer instance,
            prior to the validation call being made.
            """
            # 执行验证之前调用,serializer_fields是当前字段对象
            pass
    
    
    class ModelUserSerializer(serializers.HyperlinkedModelSerializer):
        ll = serializers.HyperlinkedIdentityField(view_name='xxxx')
        tt = serializers.CharField(required=False)
    
        class Meta:
            model = models.UserInfo
            fields = "__all__"
            list_serializer_class = serializers.ListSerializer
    
            extra_kwargs = {  # 可以对每个字段进行配置,甚至read_only
                'user': {'min_length': 6},
                'pwd': {'validators': [PasswordValidator(666), ]},
                'url': {'view_name': 'xxxx'},
                'ut': {'view_name': 'xxxx'},
            }
    
    
    class TestView(APIView):
        def get(self, request, *args, **kwargs):
            # # 序列化,将数据库查询字段序列化为字典
            data_list = models.UserInfo.objects.all()
            ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request})
            return Response(ser.data)
    
        def post(self, request, *args, **kwargs):
            # 验证,对请求发来的数据进行验证
            print(request.data)
            ser = ModelUserSerializer(data=request.data)
            if ser.is_valid():
                print(ser.validated_data)
            else:
                print(ser.errors)
            return Response('POST请求,响应内容')
    
posted @ 2019-11-02 17:45  Kn19ht  阅读(105)  评论(0)    收藏  举报