普通的 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
中,将group
的id
, 反向生成 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)
首先是创建类
UserInfoSerializer
,ser = 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()
入手)