django rest framework 中的序列化組件,可以說是其核心組件,也是我們平時使用最多的組件,它不僅僅有序列化功能,更提供了數據驗證的功能(與django中的form類似)。 便於展現的序列化操作,我們需要在model添加外鍵、多對多情況。以下是新的models(請刪除原有的資料庫,重 ...
一、簡介 |
django rest framework 中的序列化組件,可以說是其核心組件,也是我們平時使用最多的組件,它不僅僅有序列化功能,更提供了數據驗證的功能(與django中的form類似)。
便於展現的序列化操作,我們需要在model添加外鍵、多對多情況。以下是新的models(請刪除原有的資料庫,重新migrate):
models.py
from django.db import models class UserInfo(models.Model): user_type_choice = ( (1,"普通用戶"), (2,"會員"), ) user_type = models.IntegerField(choices=user_type_choice) username = models.CharField(max_length=32,unique=True) password = models.CharField(max_length=64) group = models.ForeignKey(to='UserGroup',null=True,blank=True) roles = models.ManyToManyField(to='Role') class UserToken(models.Model): user = models.OneToOneField(to=UserInfo) token = models.CharField(max_length=64) class UserGroup(models.Model): """用戶組""" name = models.CharField(max_length=32,unique=True) class Role(models.Model): """角色""" name = models.CharField(max_length=32,unique=True)
二、使用 |
1.基本使用
在urls.py中添加新的角色url,以前的url為了減少干擾,在這裡進行註釋:
from django.conf.urls import url from app01 import views urlpatterns = [ # url(r'^api/v1/auth', views.AuthView.as_view()), # url(r'^api/v1/order', views.OrderView.as_view()), url(r'^api/v1/roles', views.RoleView.as_view()), # 角色視圖 # url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"), ]
views.py
from rest_framework import serializers from rest_framework.views import APIView from django.shortcuts import HttpResponse from app01 import models import json class RolesSerializer(serializers.Serializer): #定義序列化類 id=serializers.IntegerField() #定義需要提取的序列化欄位,名稱和model中定義的欄位相同 name=serializers.CharField() class RoleView(APIView): """角色""" def get(self,request,*args,**kwargs): roles=models.Role.objects.all() res=RolesSerializer(instance=roles,many=True) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False))
使用瀏覽器訪問http://127.0.0.1:8000/api/v1/roles,結果如下:
2.自定義序列化欄位
當數據模型中有外鍵或者多對多時候,這時候就需要自定義序列化了
新增用戶信息url
from django.conf.urls import url from app01 import views urlpatterns = [ # url(r'^api/v1/auth', views.AuthView.as_view()), # url(r'^api/v1/order', views.OrderView.as_view()), url(r'^api/v1/roles', views.RoleView.as_view()), url(r'^api/v1/userinfo', views.UserinfoView.as_view()), #用戶信息 # url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"), ]
UserinfoView和序列化類
from rest_framework import serializers from rest_framework.views import APIView from django.shortcuts import HttpResponse from app01 import models import json class UserinfoSerializer(serializers.Serializer): #定義序列化類 id=serializers.IntegerField() #定義需要提取的序列化欄位,名稱和model中定義的欄位相同 username=serializers.CharField() password=serializers.CharField() #sss=serializers.CharField(source='user_type') #該方法只能拿到user_type的ID sss=serializers.CharField(source='get_user_type_display') #自定義欄位名稱,和數據模型不一致,需要指定source本質調用get_user_type_display()方法獲取數據 gp=serializers.CharField(source='group.name') #本質拿到group對象,取對象的name, #rl=serializers.CharField(source='roles.all.first.name') rl=serializers.SerializerMethodField() #多對多序列化方法一 def get_rl(self,obj): #名稱固定:get_定義的欄位名稱 """ 自定義序列化 :param obj:傳遞的model對象,這裡已經封裝好的 :return: """ roles=obj.roles.all().values() #獲取所有的角色 return list(roles) #返回的結果一定有道是json可序列化的對象 class UserinfoView(APIView): """用戶信息""" def get(self,request,*args,**kwargs): users=models.UserInfo.objects.all() res=UserinfoSerializer(instance=users,many=True) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False))
訪問http://127.0.0.1:8000/api/v1/userinfo,查看結果:
除了以上的Serializer,還可以使用ModelSerializer,ModelSerializer繼承了serializer,其結果和上面示例一樣:
class UserinfoSerializer(serializers.ModelSerializer): id = serializers.IntegerField() # 定義需要提取的序列化欄位,名稱和model中定義的欄位相同 username=serializers.CharField() password=serializers.CharField() #sss=serializers.CharField(source='user_type') #該方法只能拿到user_type的ID sss=serializers.CharField(source='get_user_type_display') #自定義欄位名稱,和數據模型不一致,需要指定source本質調用get_user_type_display()方法獲取數據 #rl=serializers.CharField(source='roles.all.first.name') gp=serializers.CharField(source='group.name') rl=serializers.SerializerMethodField() #多對多序列化方法一 def get_rl(self,obj): #名稱固定:get_定義的欄位名稱 """ 自定義序列化 :param obj:傳遞的model對象,這裡已經封裝好的 :return: """ roles=obj.roles.all().values() #獲取所有的角色 return list(roles) #返回的結果一定有道是json可序列化的對象 class Meta: model = models.UserInfo fields = ['id', 'username', 'password', 'sss','rl','gp'] #配置要序列化的欄位 # fields = "__all__" 使用model中所有的欄位 class UserinfoView(APIView): """用戶信息""" def get(self,request,*args,**kwargs): users=models.UserInfo.objects.all() res=UserinfoSerializer(instance=users,many=True) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False))
3.連表序列化以及深度控制
使用depth進行深度控制,越深其序列化的細讀越高
class UserinfoSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo #fields = "__all__" # 使用model中所有的欄位 fields = ['id', 'username', 'password', 'group','roles'] # 配置要序列化的欄位 depth = 1 #系列化深度,1~10,建議使用不超過3 class UserinfoView(APIView): """用戶信息""" def get(self,request,*args,**kwargs): users=models.UserInfo.objects.all() res=UserinfoSerializer(instance=users,many=True) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False))
請求http://127.0.0.1:8000/api/v1/userinfo,結果如下:
4.序列化欄位url
urls.py新加入組url
urlpatterns = [ # url(r'^api/v1/auth', views.AuthView.as_view()), # url(r'^api/v1/order', views.OrderView.as_view()), url(r'^api/v1/roles', views.RoleView.as_view()), url(r'^api/v1/userinfo', views.UserinfoView.as_view()), url(r'^api/v1/group/(?P<xxx>\d+)', views.GroupView.as_view(),name='gp'), # 新加入組url # url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"), ]
views.py
class UserinfoSerializer(serializers.ModelSerializer): group=serializers.HyperlinkedIdentityField(view_name='gp',lookup_field='group_id',lookup_url_kwarg='xxx') #view_name,urls.py目標url的視圖別名(name),這裡是UserGroup的視圖別名 #lookup_field 給url傳遞的參數,也就是正則匹配的欄位 #lookup_url_kwarg,url中正則名稱,也就是kwargs中的key class Meta: model = models.UserInfo #fields = "__all__" # 使用model中所有的欄位 fields = ['id', 'username', 'password','roles','group'] # 配置要序列化的欄位 depth = 1 #系列化深度,1~10,建議使用不超過3 class UserinfoView(APIView): """用戶信息""" def get(self,request,*args,**kwargs): users=models.UserInfo.objects.all() res=UserinfoSerializer(instance=users,many=True,context={'request': request}) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False #若需生成超鏈接欄位,則需要加context={'request': request} return HttpResponse(json.dumps(res.data,ensure_ascii=False)) class UserGroupSerializer(serializers.ModelSerializer): class Meta: model = models.UserGroup fields = "__all__" depth = 0 class GroupView(APIView): def get(self,request,*args,**kwargs): group_id=kwargs.get('xxx') group_obj=models.UserGroup.objects.get(id=group_id) res=UserGroupSerializer(instance=group_obj,many=False) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False))
此時訪問組信息:http://127.0.0.1:8000/api/v1/group/1,結果如下:
在查看用戶信息,此時生成的組就是超鏈接形式了(便於查看json數據,這裡用postman發請求):
三、源碼剖析 |
1.類的基本知識
-
類是實例化之前會執行__new__方法,用於控制一個類生成實例的過程
- 子類沒有__new__方法執行父類的__new__方法
- __new__方法執行完以後執行__init__構造方法
2.以ModelSerializer為例,無__new__方法,其父類Serializer也沒有,在往上父類BaseSerializer中含有__new__方法,分析請看註釋,下麵是源碼部分:
class BaseSerializer(Field): """ The BaseSerializer class provides a minimal class which may be used for writing custom serializer implementations. Note that we strongly restrict the ordering of operations/properties that may be used on the serializer in order to enforce correct usage. In particular, if a `data=` argument is passed then: .is_valid() - Available. .initial_data - Available. .validated_data - Only available after calling `is_valid()` .errors - Only available after calling `is_valid()` .data - Only available after calling `is_valid()` If a `data=` argument is not passed then: .is_valid() - Not available. .initial_data - Not available. .validated_data - Not available. .errors - Not available. .data - Available. """ def __init__(self, instance=None, data=empty, **kwargs): # many=False後執行的構造方法 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. if kwargs.pop('many', False): # many參數,如果有則執行cls.many_init,沒有則執行super(BaseSerializer).__new__ return cls.many_init(*args, **kwargs) # many=True,表示對QuerySet進行處理,走該邏輯, return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) # many = False ,表示對單獨的對象進行處理
執行玩__new__方法接著執行__init__構造方法,此時有根據many值不同執行不同的構造方法,當many=True時候執行cls.many_init方法,
@classmethod def many_init(cls, *args, **kwargs): # many=True,執行該方法 """ This method implements the creation of a `ListSerializer` parent class when `many=True` is used. You can customize it if you need to control which keyword arguments are passed to the parent, and which are passed to the child. Note that we're over-cautious in passing most arguments to both parent and child classes in order to try to cover the general case. If you're overriding this method you'll probably want something much simpler, eg: @classmethod def many_init(cls, *args, **kwargs): kwargs['child'] = cls() return CustomListSerializer(*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) list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer) return list_serializer_class(*args, **list_kwargs) # 最後使用ListSerializer進行實例化
從上面源碼中我們知道,對於單獨的對象,採用的是Serializer類進行處理,若對象是QuerySet類型(多個對象列表),採用LIstSeriallizer處理,此時我們調用對象的data屬性獲取結果(示例中這使用的是res.data),下麵是源碼(尋找時候先從子類找,無該屬性就去父類找):
@property def data(self): ret = super(Serializer, self).data # 執行父類data屬性 return ReturnDict(ret, serializer=self)
父類BaseSerialize的屬性方法data源碼:
@property def data(self): if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'): #數據驗證時候使用 msg = ( 'When a serializer is passed a `data` keyword argument you ' 'must call `.is_valid()` before attempting to access the ' 'serialized `.data` representation.\n' 'You should either call `.is_valid()` first, ' 'or access `.initial_data` instead.' ) raise AssertionError(msg) if not hasattr(self, '_data'): if self.instance is not None and not getattr(self, '_errors', None):# 判斷有無錯誤,無錯誤進行序列化 self._data = self.to_representation(self.instance) # 將instance(QuerySet對象)傳入,開始序列化 elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None): self._data = self.to_representation(self.validated_data)
else: self._data = self.get_initial() return self._data
從以上源碼中可以看出,序列化方法是通過調用類的self.to_representation方法進行序列化,下麵我們看Serializer類的to_representation方法
def to_representation(self, instance): """ Object instance -> Dict of primitive datatypes. """ ret = OrderedDict() #先將instance轉化為有序字典 fields = self._readable_fields for field in fields: # 迴圈定義的欄位,這個欄位可以是我們自己定義的,也可以是model中的欄位 try: attribute = field.get_attribute(instance) #調用欄位的get_attribute方法(參數是對象),在示例中可以理解為group.get_attribute(group_obj), except SkipField: continue # We skip `to_representation` for `None` values so that fields do # not have to explicitly deal with that case. # # For related fields with `use_pk_only_optimization` we need to # resolve the pk value. 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
以上源碼中,調用field.get_attribute(instance)方法獲取每個欄位的數據,下麵是field.get_attribute(instance)源碼(在Field中)
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) # 執行get_attribute函數,用於根據定義的欄位屬性,獲取不同的數據,
註意該方法沒有帶self,是一個函數,並不是類方法。
self.source_attrs:以'.'分割的列表,會被使用為反射獲取屬性 except (KeyError, AttributeError) as exc: if self.default is not empty: return self.get_default() if self.allow_null: return None if not self.required: raise SkipField() msg = ( 'Got {exc_type} when attempting to get a value for field ' '`{field}` on serializer `{serializer}`.\nThe serializer ' 'field might be named incorrectly and not match ' 'any attribute or key on the `{instance}` instance.\n' 'Original exception text was: {exc}.'.format( exc_type=type(exc).__name__, field=self.field_name, serializer=self.parent.__class__.__name__, instance=instance.__class__.__name__, exc=exc ) ) raise type(exc)(msg)
調用get_attribute函數,進一步出來,需要分析self.source_attrs參數,下麵是self.source_attrs部分源碼:
if self.source == '*': self.source_attrs = [] else: self.source_attrs = self.source.split('.')
#self.source是我們自定義欄位傳入的source參數,如:gp=serializers.CharField(source='group.name'),sss=serializers.CharField(source='get_user_type_display')
最後分割變成['group','name']
以上分析self.source_attrs是一個列表(由source參數按點分割而來),繼續回到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. """
# attrs:['group','name']或者['get_user_type_display',] for attr in attrs: # 迴圈列表 try: if isinstance(instance, collections.Mapping): #若果是model欄位映射(DRF的內部欄位轉化),直接調用model類的 instance = instance[attr]#重新賦值,此時的instance已經改變 else: instance = getattr(instance, attr) #否則,使用反射獲取結果,如instance=getattr(userinfo_obj,group) except ObjectDoesNotExist: return None if is_simple_callable(instance): #判斷是否是可執行,此時如我們示例中的get_user_type_display,其判斷過程在類似下麵TIPS中,這裡不再做過多說明 try: instance = instance() #重新賦值,加括弧進行執行 except (AttributeError, KeyError) as exc: # If we raised an Attribute or KeyError here it'd get treated # as an omitted field in `Field.get_attribute()`. Instead we # raise a ValueError to ensure the exception is not masked. raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc)) return instance
TIPS:判斷是否是可執行方法
import types def func(arg): if isinstance(arg,types.FunctionType,): print('yes') arg() else: print('NO') func(lambda :1) func(111) #執行結果: yes NO
從上面的源碼分析來看,序列化本質是使用了django的orm的QuerSet或者單個model對象特性,利用反射或者方法進行序列化。
四、數據驗證 |
1.基本驗證
DRF的數據驗證功能與django的form有點類似,示例:獲取數據使用的是全局配置的json解析器,在解析器中已經介紹:
class CheckGroupData(serializers.Serializer): id=serializers.IntegerField(error_messages={'required':'id不能為空'}) name=serializers.CharField(error_messages={'required':'組名不能為空'}) class GroupView(APIView): def get(self,request,*args,**kwargs): group_id=kwargs.get('xxx') group_obj=models.UserGroup.objects.get(id=group_id) res=UserGroupSerializer(instance=group_obj,many=False) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False)) def post(self,request,*args,**kwargs): ret=CheckGroupData(data=request.data)#這裡配置了全局json解析器,使用request.data直接獲取數據 if ret.is_valid(): print(ret.validated_data) #獲取某個欄位數據ret.validated_data.get('name') return HttpResponse('數據驗證成功') else: print(ret.errors) return HttpResponse('數據驗證失敗')
使用postman向http://127.0.0.1:8000/api/v1/group/1,發送json數據,結果如下:
後臺結果:
驗證流程生效。
2.自定義驗證
和django form功能一樣,DRF序列化支持自定義數據驗證,示例:
#自定義驗證規則 class MyValidation(object): def __init__(self,base): self.base = base def __call__(self, value): #value是欄位值,預設傳遞 if value == 'wd': message = "關鍵字%s不能是%s"%(self.base,value) raise serializers.ValidationError(message) class MySerializer(serializers.Serializer): name = serializers.CharField(validators=[MyValidation(base='name_field'),]) class GroupView(APIView): def get(self,request,*args,**kwargs): group_id=kwargs.get('xxx') group_obj=models.UserGroup.objects.get(id=group_id) res=UserGroupSerializer(instance=group_obj,many=False) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False)) def post(self,request,*args,**kwargs): ret=MySerializer(data=request.data)#這裡配置了全局json解析器,使用request.data直接獲取數據 if ret.is_valid(): print(ret.validated_data) #獲取某個欄位數據ret.validated_data.get('name') return HttpResponse('數據驗證成功') else: print(ret.errors) return HttpResponse('數據驗證失敗')
發送{"name":"wd"}數據進行驗證結果如下:
3.鉤子函數
對於自定義驗證來說,DRF和django的form組件一樣也給我們內置了鉤子函數,用於驗證。
驗證流程:
is_valid-->self.run_validation-->to_internal_value-->to_internal_value-->validate_欄位名稱(執行欄位驗證,鉤子方法)-->validate_method(鉤子驗證方法)
validate_欄位名稱鉤子方法驗證示例:
class MySerializer(serializers.Serializer): name = serializers.CharField() def validate_name(self,value): # 驗證的欄位值 if value.startswith("w"): raise serializers.ValidationError('name欄位不能以w開頭') else: return value #註意通過驗證,必須返回其值 class GroupView(APIView): def get(self,request,*args,**kwargs): group_id=kwargs.get('xxx') group_obj=models.UserGroup.objects.get(id=group_id) res=UserGroupSerializer(instance=group_obj,many=False) #instance接受queryset對象或者單個model對象,當有多條數據時候,使用many=True,單個對象many=False return HttpResponse(json.dumps(res.data,ensure_ascii=False)) def post(self,request,*args,**kwargs): ret=MySerializer(data=request.data)#這裡配置了全局json解析器,使用request.data直接獲取數據 if ret.is_valid(): print(ret.validated_data) #獲取某個欄位數據ret.validated_data.get('name') return HttpResponse('數據驗證成功') else: print(ret.errors) return HttpResponse('數據驗證失敗')
同樣發送json數據{"name":"wd"}進行驗證,結果如下: