笔记: Django Rest Framework 序列化和反序列化

1, 先写一个APP, 名字叫dsj

1.1 urls.py文件内容如下:

from django.urls import path, re_path
from . import views

urlpatterns = [
    re_path(r'^roles/$', views.RolesView.as_view()),
    re_path(r'^userinfo/$', views.UserInfoView.as_view()),
    re_path(r'^group/(?P\d+)$', views.GroupView.as_view(), name='gp'), #注意这里的pk,默认系统里面这么写的,可以在view中改掉.,源码在: class HyperlinkedRelatedField(RelatedField): lookup_field = 'pk' 这个pk就是人家默认规定的就是这个pk,但是pk不对,因为pk是id,id是变化的,二分组是固定的.
    re_path(r'^usergroup/$', views.UserGroupView.as_view())
]

1.2 models.py 文件内容如下,新建数据表

from django.db import models

class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP客户'),
        (3, 'SVIP')
    )
    user_type = models.IntegerField(choices=user_type_choices) #注意这里是choice的

    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=64)

    group = models.ForeignKey(to='UserGroup', on_delete=models.CASCADE) # 注意这个多对一,外键. 假设一个组对应多个人,一个人只有多个组
    roles = models.ManyToManyField('Role') #注意这个多对多

class UserGroup(models.Model):
    title = models.CharField(max_length=32)

class Role(models.Model):
    title = models.CharField(max_length=32)


class UserToken(models.Model):
    user = models.OneToOneField(to='UserInfo', null=True, on_delete=models.SET_NULL)
    token = models.CharField(max_length=64)

1.3 views.py 文件编写, 写serializer 序列化和反序列化

from django.shortcuts import render, HttpResponse
from django.http import response, JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
import json
from .models import Role, UserInfo, UserGroup


#######################################################################################################################
###########以下示例讲解的是用serializer来解析从数据库中取出来的数据的方法#########################################################

#例子1
# 方法1: 用values然后用list类型把获取出来的Queryset类型的某一列,或者几列,转换成list类型,然后用json,就可以输出.
#class RolesView(APIView):
        # def get(self, request, *args, **kwargs):
    #     roles = Role.objects.all().values('pk', 'title')
    #     roles = list(roles)
    #     ret = json.dumps(roles, ensure_ascii=True)
    #     return Response(roles)


#例子2
# 方法2:用rest framework里面的serializer序列化处理.
# 首先定义一个能勾完成序列话的类,然后用这个类 去处理我们从数据库中拿出的数据,因为拿出来的数据是Queryset类型,所以需要序列化之后,输出
from rest_framework import serializers

#定义一个用来实例化的类
class RolesSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField() #注意这个title 要和数据库中的字段名称保持一致,当然也可以不用保持一致,需要指定source参数就可以,如下一行代码所示:
    # xxx = serializers.CharField(source='title')

#定义View类
class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        roles = Role.objects.all()
        ser = RolesSerializer(instance=roles, many=True) #注意这就用到了上面写的那个序列化的类.传递两个参数,必写的.instance是我们上面从数据库中获得实例对象, many的值:True表示多条数据,False表示单个实力
        #上一行代码就获得了序列化之后的实例对象, ser.data就是序列化之后的结果.
        print(ser.data) #打印看下有没有值,有值,且类型是:[OrderedDict([('title', '医生')]), OrderedDict([('title', '学生')]), OrderedDict([('title', '元昊')])]
        return Response(ser.data) #这个是采用rest framework组件里面的Response,本身可以解析json格式数据,如果用系统的httpResponse(),就必须在上一步用json.dumps()来先格式化一下,在输出.如下:

        #采用httpResponse()来做
        # ret = json.dumps(ser.data, ensure_ascii=False) # ensure_ascii=False 这个保证输出的中文,不是十六进制的字符
        # return HttpResponse(ret)


#例子3
# 方法3: 用serializer序列化操作复杂的数据表,这里models.py里面有: choice选择, 外键ForeignKey, 多对多ManyToMany,三种类型
# 针对每种类型,注意下面的操作步骤.
#第1步.创建序列化的类
class UserInfoSerializer(serializers.Serializer):
    #想要序列化什么字段,就写上什么字段
    username = serializers.CharField()
    password = serializers.CharField()

    #第一个重点: 解析choice选择.
    # user_type = serializers.IntegerField(source='user_type') #这个只会输出user_type就是数据库里面的纯数字.并不能显示出来choice对应的选项,如下解决choice.
    # user_type = serializers.IntegerField(source='get_user_type_display') #直接一步到位的说,使用source 关键字,然后用get_xxx_display的方式指定,中间的xxx就是带有choice的field字段,当时注意不能用和原来的一样的字段,因为这里显示中文,所以需要换成Charfield(),不然报错
    user_type = serializers.CharField(source='get_user_type_display') # 正确答案: 使用get_xxx_display的方式,同时用Charfield()类型,就可以搞出来对应的中文



    # 第二个重点: 解析外键ForeignKey() 如何使用.
    # group = serializers.CharField() #这个常规操作1,只会输出这个对象对应的外键的对象,注意是对象,是UserGroup object类型,并不是可以直观看到的内容.
    # group = serializers.CharField(source='group') #这个常规操作2,是会报错的,因为最开始的group和source里面指定的group是重复的,系统会报错,如果想要用source=group指定,前面的group就要改名字,如下:
    # gp = serializers.CharField(source='group') #这个就不会报错,但是在返回的变量名就是gp 不在是group了. 但是下面的一句就不会报错,因为source指向的是另外一个表的内容了..
    group = serializers.CharField(source='group.title') #直接一步到位的说: 因为是外键,所以需要应用指向外键的那个表里面的字段,所以这里就用'点'操作了,直接用这个表里面的外键字段去'点'指向的那个外键表里面的字段,UserGroup表里面id, pk, title都可以,都行


    # 第三个重点: 解析多对多ManyToManyField() 如何使用
    #这里操作角色,用户和角色是多对多的,这里看操作
    # roles = serializers.CharField(source='roles.title') #现根据之前的外键的经验,做出如此的写法,,但是是错误的.
    # roles = serializers.CharField(source='roles.all') #直接一步到位的说: 用source指定roles.all可以获取到一个user 用户对应的所有的角色,注意是多对多的关系,仔细思考,一个角色对应另外一个表里面的所有的关联数据. 但是这个获取到的只是, , ]>,不是具体的数据,这种用source指定的做法,歇菜了.
    # 上面两行都不行,不能用source来做,最多只能取出来对象,但是不能取出来对象里面具体的值,可以获取到值的做法,参考如下:

    #正确的方法1:    这个方法也可以发扬光大.对于choice 和ForeignKey来说都可以用这种先获取对应表里面的所有数据,然后遍历.
    roles = serializers.SerializerMethodField() #注意这里的Field已经变化了,还有roles
    def get_roles(self, r): #注意这里的函数名字: get_roles() 和上面的field要对应,加上开头的get_
        role_obj_list = r.roles.all() #注意这里程序取出来的是QuerySet对象,针对数据库里面的每一个user用户,这里取出来的就是那个用户所对应的多对多表里面的全部数据,这个全部数据是多个,所以用到遍历,然后添加到字典或者其它数据结构中..
       # print(role_obj_list) #测试上面一句的输出内容.输出2个QuerySet对象,因为数据库中有2个user用户,下面的get视图中,也是取出全部的用户,所有是2个QuerySet对象.
        ret = [] #先定义,给下面使用的.
        for item in role_obj_list: #这个role_obj_list是QuerySet类型.针对每一个用户 在多对多表中的全部数据,遍历拿到里面的每一条数据,注意这个每一条数据的意思,就跟excel表格中的一行是一样的,所以可以在下面这一行代码中用'点'操作.
            ret.append({'id':item.pk, 'title':item.title}) #这个点操作后面的名称,也不是瞎写的,是根据就数据库表中的字段来的.
        return ret



# 例子4:用serializers.ModelSerializer()这个其实就是把字段进行省略写法,对于处理choice ForeignKey, ManyToMany来说还是一样要像serializer.Serializer()一样处理,个人感觉不太实用.
class UserInfoModelSerializer(serializers.ModelSerializer):
    # 也支持混合自定义用
    xxx = serializers.CharField(source='get_user_type_display') #自定义字段,然后用source来指定对应的字段
    rls = serializers.SerializerMethodField() #自定义字段

    class Meta:
        model = UserInfo #指定要连接的表
        # fields = "__all__" # 指定全部的字段都需要处理
        fields = ['username', 'password', 'xxx', 'rls'] # 进行部分的字段处理
        # exclude = ['username', 'password'] # 这个是排除的字段,除了这个的字段,其它的都要进行序列化.

    def get_rls(self, r): #注意这里的函数名字: get_roles() 和上面的自定义的字段rls要对应,加上开头的get_
        role_obj_list = r.roles.all() #注意这里程序取出来的是QuerySet对象,针对数据库里面的每一个user用户,这里取出来的就是那个用户所对应的多对多表里面的全部数据,这个全部数据是多个,所以用到遍历,然后添加到字典或者其它数据结构中..
       # print(role_obj_list) #测试上面一句的输出内容.输出2个QuerySet对象,因为数据库中有2个user用户,下面的get视图中,也是取出全部的用户,所有是2个QuerySet对象.
        ret = [] #先定义,给下面使用的.
        for item in role_obj_list: #这个role_obj_list是QuerySet类型.针对每一个用户 在多对多表中的全部数据,遍历拿到里面的每一条数据,注意这个每一条数据的意思,就跟excel表格中的一行是一样的,所以可以在下面这一行代码中用'点'操作.
            ret.append({'id':item.pk, 'title':item.title}) #这个点操作后面的名称,也不是瞎写的,是根据就数据库表中的字段来的.
        return ret



#例子5:用depth=x来拿对应的嵌套数据nest ,用两种类都来验证一下
#1, 验证类serializer.Serializer()
class UserInfoDepthSerializer(serializers.Serializer):
    user_type = serializers.CharField()
    username = serializers.CharField()
    password = serializers.CharField()
    group = serializers.CharField()
    roles = serializers.CharField()

    class Meta:
        depth=2 #没有起任何作用,输出的仍然是QuerySet类型,不是具体的数据库表中的值.

#2, 验证类serializer.ModelSerializer()
class UserInfoDepthModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = UserInfo
        fields = '__all__'
        depth = 1 #只有在ModelSerializer 起作用, 但是对于那个choice 还是不能用,还是会输出对应的id,而不是类型.

#3, 验证类serializer.ModelSerializer() depth 对choice的改进, 完美写法!
class UserInfoDepthModelSerializer_1(serializers.ModelSerializer):
    user_type = serializers.CharField(source='get_user_type_display') #在这里用source=get_xxx_display的方法,注意user_type我们这里是相当于覆盖掉UserInfo表里面的的那个user_info字段了,我们也可以自定义写成xxx但是这个xxx就要在fields里面体现了.
    class Meta:
        model = UserInfo
        fields = "__all__"
        depth = 1


#例子6: 反向生成URL,
# 就是在序列化的时候,在提供数据库信息的时候,同时针对某些字段,我们不仅要获取这个字段的值,还想要让系统反向帮我们生成针对这个字段的URL,比如:
# 在系统给我们提供用户信息的时候,我们不仅要知道用户属于那个组,还要求提供那个组的URL,这样我们点击那个组,就可以跳转到其它的页面, 思考:这里其实就是针对
# 那个需要反向的字段进行reverse...所以第一步我们需要给这个字段创建url,同时提供这个path的view name,才可以反转,所以我们在urls.py中添加一条针对
# group的path
# 这里用到group 所以还需要对 UserGroup这个表进行序列化,同时写上view,

#先处理 组 的视图和序列化类.groupserializer 和groupview
class GroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserGroup
        fields = '__all__'

class GroupView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk') #先获取url中的pk,注意这个里面都是在kwargs里面了,没有放到args
        #根据传入的pk到数据库中查找对象的pk,然后返回给get请求
        obj = UserGroup.objects.filter(pk=pk).first()
        ser = GroupSerializer(instance=obj, many=False) #因为这里就从数据库中拿到了一个数据,根据PK拿的.
        ret = json.dumps(ser.data, ensure_ascii=False) #ensure_ascii=False 就可以显示中文.
        return HttpResponse(ret) #这里HttpResponse()用的是系统原生的.


# 方案1: 下面处理用户的序列化,不仅序列化用户的数据,还顺便给我们提供针对那个字段的反向URL,这里需要注意的是因为是针对那个字段的URL,所以需要把那个字段的url, view, serializer都写好,要不然都没有view和url,无法反向生成url.
# 但是这种做法是错误的,得到的url不对! HyperlinkedIdentityField 源代码里面lookup_field = 'pk' 也就是说默认pk得到的是表的id来的,很显然不多我们想要的是用户所属组的id,而不是组的id
class UserInfoDepthModelSerializerUrl(serializers.ModelSerializer):
    group = serializers.HyperlinkedIdentityField(view_name='gp')

    class Meta:
        model = UserInfo
        fields = "__all__"
        depth = 1

# 方案2: 下面的操作不仅要求得到用户的分组名称,还要求获得分组的反向URL.就可以结合前面说的方案,一个字段,得到两种结果.得到的东西更多,更完美.
# 但是这种做法是错误的,得到的url不对! HyperlinkedIdentityField 源代码里面lookup_field = 'pk' 也就是说默认pk得到的是表的id来的,很显然不多我们想要的是用户所属组的id,而不是组的id

class UserInfoDepthModelSerializerUrl_id(serializers.ModelSerializer):
    group_id = serializers.CharField(source='group.title')
    group = serializers.HyperlinkedIdentityField(view_name='gp')

    class Meta:
        model = UserInfo
        # fields = "__all__"
        fields = ['username', 'password', 'group_id', 'group'] # 这里可以写需要的,
        depth = 1

#方案3: 下面是最终版,不仅可以修改url.py中的那个pk字符是其它的字符,也可以修正得到的url的路径是正确的,是真正的用户所属组的url.同时还得到了用户组的名称.
class UserInfoDepthModelSerializerUrl_Correct(serializers.ModelSerializer):
    group_name = serializers.CharField(source='group.title') #获得group的名称,用点操作获取.
    #下面一句代码,才可以解决两个问题,一个是url中写的那个pk的问题,这里有所体现,这里可以改成任何的,比如说xxx,但是同时在url和groupview中都要改成xxx.
    #解决的第二个问题就是: 用户获得了对应的正确的group的的id,而不是group表中的pk,因为有很多用户对应的是同一个group,如果用group表里面的id很显然是错误的.
    #第三个注意点: lookup_field='group_id' 这里不是点操作了,是用的下划线操作的.
    group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='pk')

    class Meta:
        model = UserInfo
        # fields = "__all__"
        fields = ['username', 'password', 'group_name', 'group'] # 这里可以写需要的,
        depth = 1


class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
        user = UserInfo.objects.all()
        # ser = UserInfoSerializer(instance=user, many=True) #这个是针对serializer.Serializer()进行序列化
        # ser = UserInfoModelSerializer(instance=user, many=True) # 这个是针对serializer.ModelSerializer()进行序列华
        # ser = UserInfoDepthSerializer(instance=user, many=True) # 这个是测试depth 对serializer.Serializer()看能不能取到嵌套的值.事实证明,不行
        # ser = UserInfoDepthModelSerializer(instance=user, many=True) # 事实证明depth 对serializer.ModelSerializer()才能用.但是choice还是不完美.
        # ser = UserInfoDepthModelSerializer_1(instance=user, many=True) # 用depth的完美用法
        # ser = UserInfoDepthModelSerializerUrl(instance=user, many=True, context={'request': request})
        # ser = UserInfoDepthModelSerializerUrl_id(instance=user, many=True, context={'request': request})

        ser = UserInfoDepthModelSerializerUrl_Correct(instance=user, many=True, context={'request': request})

        #这里直接使用rest framework里面的Response,直接json格式输出,不用json.dumps()
        return Response(ser.data)
        # 用json.dumps(),用django自带的HttpResponse
        # ret = json.dumps(ser.data, ensure_ascii=False)
        # return HttpResponse(ret)


###########以上示例讲解的是用serializer来解析从数据库中取出来的数据的方法#########################################################
#######################################################################################################################



#######################################################################################################################
###########以下示例讲解的是用serializer来校验从前端post发送过来的数据#########################################################

#以下示例用usergroup数据表来操作
#倒序插入验证器的类,自定义一个验证器类:用作传入的title必须以 老男人 开头。
class XXValidator(object):
    def __init__(self, base): #这里的base就是实例化传入的那个 老男人
        self.base = base

    def __call__(self, value): #注意这里的value就是你提交过来的值
        if not value.startswith(self.base):
            message = '传入的title %s 必须以老男人开头'%self.base
            raise serializers.ValidationError(message)

    def set_context(self, serializer_field):
        #执行验证之前调用这个,serializer_field是当前字段对象,暂时用不到这个函数,但是必写
        pass

#先创建序列化的类
class UserGroupSerializer(serializers.Serializer):
    #需要验证什么字段,就从数据库中取什么字段进行验证.对什么字段验证,就写什么字段
    #同时可以在CharField中写提示信息
    # title = serializers.CharField() 这样也可以工作
    # title = serializers.CharField(error_messages={'required':'标题不能为空'}) 只添加error_messages,只会在根本不传title才会触发。在里面写‘blank’就会吧英文翻译成这里写的中文。
    title = serializers.CharField(error_messages={'required':'标题不能为空', 'blank': '标题不能为空blank'},validators = [XXValidator('老男人'),]) #这里也可以添加验证器类的实例,或者添加自定义的验证器类的实例,可以传参。

    ######也可以在这里用钩子函数做,什么是钩子函数,就是针对每一个字段写一个验证的函数,这样就不用写那个类了,比如说验证title就可以写:
    #### 注意这个serializer 有两个验证规则,一个是上面的类XXValidator()一个就是下面的函数validate_title(),两个都起作用
    def validate_title(self, value):#注意这里又两个注意点,第一个是函数必须以validate_开头,后面添加需要验证的字段。第二个是传入的参数value,这个就是你前端传入的值。
        print('value is : %s'%value) #验证value是不是前端传入的值,确定value是前端传入的值
        #同时根据源码可以看到这钩子函数是写到try中的,所以在这里如果验证不通过,可以触发源码中的try里面的异常,如下:
        # from rest_framework import exceptions
        # raise exceptions.ValidationError('看你不顺眼')
        if value == "老男人【表情】" or None:
            from rest_framework import exceptions #参考源码要触发下面的异常才引入的,可以放到最上面。
            raise exceptions.ValidationError(detail='看你不顺眼', code='def函数验证改编') #注意这里改系统里面的code,但是一般只要改那个detail,显示原因就行了,不用改系统的code,这里可以参考源码
        return value #如果触发异常就不会执行这个value了,但是这个函数是要有返回值的,人家吧需要验证的数据传给你函数,验证成功了就要返回这个数据,如果验证不成功,就触发异常,或者怎么样。

#定义视图
class UserGroupView(APIView):

    def post(self, request, *args, **kwargs):
        # print(request.data) #注意这里可以输出request.data,可以输出全部的从前台发送过来的值.所有的值
        #方法1:这里可以用django的原生Form表单来做,

        #方法2: 用上面创建的序列化类来校验
        ser = UserGroupSerializer(data=request.data) #注意这里的data参数,因为这里的request是restframework的,所以可以调用request.data获取前端传递过来的data,然后放到serializer里面.
        # print(ser.data) #写到这里是错误的,会提示:AssertionError: When a serializer is passed a `data` keyword argument you must call `.is_valid()` before attempting to access the serialized `.data` representation.
        # You should either call `.is_valid()` first, or access `.initial_data` instead.

        #下面对ser进行校验
        if ser.is_valid():
            # print(ser.validated_data)
            print("ser.data == ",ser.data) # 注意这里!!! 经过is_valid()之后,ser.data有两种情况,第一:不合法的数据,第二:is_valid会把其它一起传入的数据清掉,只保留我们上面serializer里面想要验证的字段对应的数据,其余的都没有了..
            print("ser.validated_data == ",ser.validated_data) # 注意这里,经过is_valid()之后,调用ser.validated_data就是OrderDict()类型的数据,里面包含了验证之后的合法数据.
            print('ser.validated_data.title == ', ser.validated_data['title']) #注意这个是怎么获取orderDict()中的值,跟原生的dict获取键值的方式是一样,用健.只不过这个是改造过的orderDict()而已
            # UserGroup.objects.create(title=ser.data.get("title")) #保存到数据库1
            # UserGroup.objects.create(title=ser.validated_data['title']) #保存到数据库2


        else:
            print(ser.errors)


        return Response('校验')

你可能感兴趣的:(django)