序列化

普通的 django 序列化

# demoapiurls.py

urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/roles/$', views.RolesView.as_view()),
]
# demoapiviews.py

class RolesView(APIView):
    def get(self, request, *args, **kwargs):

        # 方式一
        roles = models.Role.objects.all().values("id", "title")
        roles = list(roles)
        ret = json.dumps(roles, ensure_ascii=False)
        
        return HttpResponse(ret)

对 QuerySet 数据进行序列化

# demoapiviews.py

# 方式二:多条数据的 QuerySet
from rest_framework import serializers

class RolesSerializer(serializers.Serializer):
    # 必须和数据库中的字段一致
    id = serializers.IntegerField()
    title = serializers.CharField()

class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        
        roles = models.Role.objects.all()   # QuerySet类型, 有多条数据
        # 实例化一个RolesSerializer对象, many=True表示处理多条数据
        ser = RolesSerializer(instance=roles, many=True)
        # ensure_ascii=False 表示不处理中文字符
        ret = json.dumps(ser.data, ensure_ascii=False)

        return HttpResponse(ret)

# demoapiviews.py

# 方式二:一条数据的 QuerySet
from rest_framework import serializers

class RolesSerializer(serializers.Serializer):
    # 必须和数据库中的字段一致
    id = serializers.IntegerField()
    title = serializers.CharField()

class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        
        role = models.Role.objects.all().first()
        ser = RolesSerializer(instance=role, many=False)	# many=False,一条数据
        ret = json.dumps(ser.data, ensure_ascii=False)

        return HttpResponse(ret)

序列化自定义字段

# demoapiviews.py

from rest_framework import serializers

class UserInfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

现在要显示用户类型,只需要添加 user_type 字段即可,或者使用 source ,source 也是对应数据库的字段

# demoapiviews.py

from rest_framework import serializers

class UserInfoSerializer(serializers.Serializer):
    # 使用了source,前面的字段就不能和数据库字段相同了
    xxx = serializers.CharField(source="user_type")
    username = serializers.CharField()
    password = serializers.CharField()

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
		
        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

但是这样显示的只是用户类型的 id 字段,若想显示用户类型对应的中文,则需要使用 get_???_display ,与前面学习 django 所不同的是,这里的 display 并不需要加上括号

# demoapiviews.py

from rest_framework import serializers

class UserInfoSerializer(serializers.Serializer):
    # 使用了source,前面的字段就不能和数据库字段相同了
    xxx = serializers.CharField(source="user_type")
    yyy = serializers.CharField(source="get_user_type_display")
    username = serializers.CharField()
    password = serializers.CharField()

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
		
        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

为什么 display 不用加括号?

​ 因为源码中会做一个判断,如果拿到的是数据库的字段,直接通过点语法在数据库中取值,如果是 get_???_display ,则会自动加上括号,自动执行

models.py 中的 UerInfo 类有一个 group = models.ForeignKey("UserGroup") ,显示用户所在的组,可以通过点的语法,并且如果后面还有字段,可以一直点下去

# demoapiviews.py

from rest_framework import serializers

class UserInfoSerializer(serializers.Serializer):
    # 使用了source,前面的字段就不能和数据库字段相同了
    # xxx = serializers.CharField(source="user_type")
    yyy = serializers.CharField(source="get_user_type_display")
    username = serializers.CharField()
    password = serializers.CharField()
    # gp = serializers.CharField(source='group')	# 这里取到的只是组的对象
    gp = serializers.CharField(source='group.title')

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
		
        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

罗列当前用户所有的角色

# demoapiviews.py

from rest_framework import serializers

class UserInfoSerializer(serializers.Serializer):
    # 使用了source,前面的字段就不能和数据库字段相同了
    # xxx = serializers.CharField(source="user_type")
    yyy = serializers.CharField(source="get_user_type_display")
    username = serializers.CharField()
    password = serializers.CharField()
    # gp = serializers.CharField(source='group')	# 这里取到的只是组的对象
    gp = serializers.CharField(source='group.title')
    rls = serializers.CharField(source='roles.all')	# 取到所有的角色对象

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
		
        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

上面取到的是所有的角色对象,并不能取出角色对象和 id 的详细信息。所以有另一种方法

class UserInfoSerializer(serializers.Serializer):
    # source指的是对应数据库的字段
    # xxx = serializers.CharField(source="user_type")
    yyy = serializers.CharField(source="get_user_type_display")
    username = serializers.CharField()
    password = serializers.CharField()
    gp = serializers.CharField(source='group.title')
    # rls = serializers.CharField(source='roles.all')
    rls = serializers.SerializerMethodField()       # 自定义显示

    # 自定义显示需要有自定义函数, 函数名是get_字段, 参数是当前UserInfo这一行的对象
    def get_rls(self, row):
        # 取出关联的所有角色, 赋给每一个对象
        role_obj_list = row.roles.all()
        ret = []
        # 取出每一个对象中指定的值, 添加到返回值
        for item in role_obj_list:
            ret.append({"id": item.id, "title": item.title})

        # 返回值会赋给 rls
        return ret
    
class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

ModelSerializer

class UserInfoSerializer(serializers.ModelSerializer):
    # 也支持自定义字段,或者自定义函数显示
    yyy = serializers.CharField(source="get_user_type_display")

    class Meta:
        model = models.UerInfo	# 根据数据库的对应关系自动生成指定的字段
        # fields = "__all__"	# 获取所有的字段,但只是很简陋的信息
        fields = ['id', 'username', 'password', 'yyy']
        dept = 3	# 深度
        
class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

部分总结

写类

class UserInfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()
class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UerInfo
        fields = "__all__"
        fields = ['id', 'username', 'password', ]

字段

title = serializers.CharField(source="x.xx.xxx")
title = serializers.SerializerMethodField()
def get_title(self, xxx):
	do something..
    return xx	# 返回值会交给 title

自定义类(不常用)

class MyField(serializers.CharField):

    def to_representation(self, value):
        print(value)    # value相当于从数据库中取到的字段
        return 'xxx'    # 返回值在页面中显示

class UserInfoSerializer(serializers.ModelSerializer):
    x1 = MyField(source='username')
    yyy = serializers.CharField(source="get_user_type_display")

    class Meta:
        model = models.UerInfo
        # fields = "__all__"
        fields = ['id', 'username', 'password', 'yyy', 'group', 'x1']

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

深度控制

class UserInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UerInfo
        fields = ['id', 'username', 'password', 'group', 'roles']
        depth = 1   # 深度, 表示展现的层级(官方建议不超过10层, 个人建议不超过3层)


class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

生成链接

现在将 depth 设为 0,group 显示的是 id,现在想将 group 设置为一个 URL,让它点击才能查看详情

首先需要添加这个 URL 配置到路由中

url(r'^(?P<version>[v1|v2]+)/group/(?P<pk>d+)$', views.GroupView.as_view(), name='gp'),

然后在 UserInfoSerializer 中,将 groupid , 反向生成 URL,并在序列化的时候添加 context

class UserInfoSerializer(serializers.ModelSerializer):

    # 将 group的id, 反向生成URL
    group = serializers.HyperlinkedIdentityField(view_name='gp')

    class Meta:
        model = models.UerInfo
        fields = ['id', 'username', 'password', 'group', 'roles']
        depth = 0   # 深度, 表示展现的层级(官方建议不超过10层, 个人建议不超过3层)

class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        # 添加 context
        ser = UserInfoSerializer(instance=users, many=True, context={'request': request})

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)


class GroupSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserGroup
        fields = "__all__"
        depth = 0

class GroupView(APIView):

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.UserGroup.objects.filter(pk=pk).first()
        ser = GroupSerializer(instance=obj, many=False)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

可以看到 group 成为一个链接,但又出现一个新的问题,group 中数据库中并没有第 2 组(实际上这里 group 默认用的是上面的 id ),所以需要改进

class UserInfoSerializer(serializers.ModelSerializer):

    # 将 group的id, 反向生成URL
    # 添加两个参数, lookup_url_kwarg 要与路由中的有名分组相同
    # 路由: url(r'^(?P<version>[v1|v2]+)/group/(?P<pk>d+)$', views.GroupView.as_view(), name='gp'),
    group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='pk')

    class Meta:
        model = models.UerInfo
        fields = ['id', 'username', 'password', 'group', 'roles']
        depth = 0   # 深度, 表示展现的层级(官方建议不超过10层, 个人建议不超过3层)


class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True, context={'request': request})

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)


class GroupSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserGroup
        fields = "__all__"
        depth = 0

class GroupView(APIView):

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.UserGroup.objects.filter(pk=pk).first()
        ser = GroupSerializer(instance=obj, many=False)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

点击 group 的链接,也可以跳转到相应详情页面,不过这种情况用的不多

源码流程

ModelSerializer

class UserInfoSerializer(serializers.ModelSerializer):

    # 将 group的id, 反向生成URL
    group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='pk')

    class Meta:
        model = models.UerInfo
        fields = ['id', 'username', 'password', 'group', 'roles']
        depth = 0   # 深度, 表示展现的层级(官方建议不超过10层, 个人建议不超过3层)


class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):

        users = models.UerInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True, context={'request': request})

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

首先是创建类 UserInfoSerializerser = UserInfoSerializer(instance=users, many=True, context={'request': request}) 对其进行实例化,在一个类进行实例化的时候,会执行 __init__ 方法,在执行 __init__ 之前,会执行 __new__ 方法。现在去寻找这两个方法,自己没有就寻找父类。

UserInfoSerializer --> ModelSerializer --> Serializer --> BaseSerializer,在 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)


def __new__(cls, *args, **kwargs):
    # We override this method in order to automagically create
    # `ListSerializer` classes instead when `many=True` is set.
    # 拿取many参数,没有就是False
    if kwargs.pop('many', False):
        # 如果为True,执行这一句,即many=True,表示对QuerySet进行处理
        # 执行 many_init 方法
        return cls.many_init(*args, **kwargs)
    # 如果为False,执行这一句,即many=False,表示对对象进行处理
    # 执行父类的 __new__ 方法,返回一个对象,这个对象返回完之后,其实就是 __init__ 中实例化的对象
    # 接着执行 __init__ 方法
    return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
@classmethod
def many_init(cls, *args, **kwargs):
    ....	# 省略的内容
    allow_empty = kwargs.pop('allow_empty', None)
    child_serializer = cls(*args, **kwargs)
    list_kwargs = {
        'child': child_serializer,
    }
    if allow_empty is not None:
        list_kwargs['allow_empty'] = allow_empty
    list_kwargs.update({
        key: value for key, value in kwargs.items()
        if key in LIST_SERIALIZER_KWARGS
    })
    meta = getattr(cls, 'Meta', None)
    # 去meta中读取 list_serializer_class 参数,没有就使用默认的 ListSerializer 参数
    '''
    如果是多个 QuerySet对象,看到的是Serializer进行处理,但Serializer内部调用ListSerializer处理
    上面many=True,所以这里使用ListSerializer类处理,实例化的是 ListSerializer 类的对象,接着执行该对象的 __init__ 方法
    如果many=False,则使用BaseSerializer类处理,实例化 BaseSerializer 类的对象,执行该对象的 __init__ 方法
    '''
    list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
    return list_serializer_class(*args, **list_kwargs)

这时实例化的步骤完成,ser = UserInfoSerializer(instance=users, many=True, context={'request': request})users 要么是 QuerySet 对象,要么是一个普通的对象,接着 ret = json.dumps(ser.data, ensure_ascii=False) ,这里调用了 ser.data

@property
def data(self):
    # 执行父类的data属性,去查看一下
    ret = super(Serializer, self).data
    # ReturnDict,封装有序字典
    return ReturnDict(ret, serializer=self)

这里有个 self.to_representation ,回到实例化之后,无论是普通对象还是 QuerySet 对象,都会执行这个方法,来查看一下 to_representation 做了什么

可以看到它提供了很多 to_representation ,所以不应该这样查找,应该从自己所写的 UserInfoSerializer 类中开始查找

UserInfoSerializer --> ModelSerializer --> Serializer,在 Serializer 类中发现 to_representation

def to_representation(self, instance):
    """
    Object instance -> Dict of primitive datatypes.
    """
    # 有序字典
    ret = OrderedDict()
    fields = self._readable_fields
	
    # fields相当于定义和数据库生成的字段
    for field in fields:
        try:
            # 调用字段(CharField)的get_attribute方法
            # 这个instance就是最开始传入的对象
            # 这个对象可以使用点方法,比如 对象.username
            attribute = field.get_attribute(instance)	# 可以看一下这个get_attribute
        except SkipField:
            continue
        ...

通过序列化字段的 CharField 来查找 get_attribute,CharField --> Field,在 Field 中找到 get_attribute

# instance是最开始传入的对象
def get_attribute(self, instance):
    """
    Given the *outgoing* object instance, return the primitive value
    that should be used for this field.
    """
    try:
        # source_attrs是在写序列化自定义字段时传入的source组成的列表,
        # 例如 group.title,get_user_type_display,roles.all
        # source_attrs里面执行了 source.split('.'),把所有的通过点进行分割
        # instance是最开始传入的对象
        return get_attribute(instance, self.source_attrs)	# 再来看一下这个get_attribute
    ...
# 传入上面的参数,attrs就是 self.source_attrs,如果是 group.title
# 这里attrs就是[group, title]
def get_attribute(instance, attrs):
    for attr in attrs:
        try:
            if isinstance(instance, collections.Mapping):
                instance = instance[attr]
            else: 
                instance = getattr(instance, attr)
        except ObjectDoesNotExist:
            return None
        # 如果是 get_user_type_display
        # is_simple_callable内部判断了是方法还是函数
        if is_simple_callable(instance):
            try:
                # display自动加括号
                instance = instance()
            except (AttributeError, KeyError) as exc:
                raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))

    return instance

请求数据校验

class UserGroupSerializer(serializers.Serializer):
    title = serializers.CharField(error_messages={'required': '标题不能为空'})

class UserGroupView(APIView):

    def post(self, request, *args, **kwargs):

        ser = UserGroupSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data['title'])
        else:
            print(ser.errors)

        return HttpResponse('提交数据')

自定义验证规则

写一个标题必须以 "老男人" 开头

class XXValidator(object):
    def __init__(self, base):
        self.base = base

    def __call__(self, value):
        if not value.startswith(self.base):
            message = '标题必须以 %s 为开头' % self.base
            raise serializers.ValidationError(message)

class UserGroupSerializer(serializers.Serializer):
    title = serializers.CharField(error_messages={'required': '标题不能为空'}, validators=[XXValidator('老男人'),])

class UserGroupView(APIView):

    def post(self, request, *args, **kwargs):
        ser = UserGroupSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data['title'])
        else:
            print(ser.errors)

        return HttpResponse('提交数据')

但是一般并不这样使用,因为它有自己的钩子函数 (从 is_valid() 入手)

原文地址:https://www.cnblogs.com/qiuxirufeng/p/10517336.html