drf(七)—序列化
阿新 • • 發佈:2022-04-10
drf(七)—序列化
說明:由於序列化涉及內容較多且關係到資料庫(ORM),本次先說明使用方式,隨後再進行部分原始碼的剖析;
1. 簡單使用
1.1 Serializer使用
# 編寫序列化類 class RoleSerializer(serializers.Serializer): # 其中的變數名應該是資料庫中應該存在的值。 id=serializers.IntegerField() title=serializers.CharField() class RoleView(APIView): authentication_classes = [] permission_classes = [] throttle_classes = [] def get(self,*args,**kwargs): role=models.Role.objects.all() ser=RoleSerializer(instance=role,many=True) #many =True表示多條資訊。 data=json.dumps(ser.data) return HttpResponse(data)
1.2 自定義欄位
class RoleSerializer(serializers.Serializer):
# id=serializers.IntegerField()
# 自定義變數的名稱,但是需要指定資料庫中欄位的引數,使用source進行傳參。
idd=serializers.CharField(source="id")
title=serializers.CharField()
- 自定義變數的名稱,但是需要指定資料庫中欄位的引數,使用
source
進行傳參。
import json from django.shortcuts import render,HttpResponse from rest_framework.views import APIView from rest_framework import serializers from api import models class UserInfoSerializer(serializers.Serializer): xxxxx = serializers.CharField(source="user_type") oooo = serializers.CharField(source="get_user_type_display") username = serializers.CharField() password = serializers.CharField() gp = serializers.CharField(source="group.title") rls=serializers.SerializerMethodField() 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}) return ret class UserInfoView(APIView): authentication_classes = [] throttle_classes = [] permission_classes = [] 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) return HttpResponse(ret)
補充:
class MyField(serializers.CharField):
def to_representation(self, value):
print(value)
return "xxxxx"
# 繼承基本欄位得到自定義的欄位型別,使得定義的序列化類中存在自定義的型別,
1.3 ModelSerializer使用
說明:本內容的知識與django
的form(ModelForm)
知識高度相似,可以對比學習。
class UserInfoSerializer(serializers.ModelSerializer): class Meta: model=models.UserInfo fields='__all__' depth=1 # 取到外來鍵關聯一般設定為0到3 # 該類也可以想modelform的類一樣編寫鉤子函式,進行驗證
-
生成url
使用較少,一般只返回資料不拼url。
# re_path(r'^api/(?P<version>[v1|v2]+)/group/(?P<xxx>\d+)$', views.GroupView.as_view(),name='gp'), class UserInfoSerializer(serializers.ModelSerializer): # group = serializers.HyperlinkedIdentityField(view_name="gp", lookup_field='group_id', lookup_url_kwarg='xxx') group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='xxx') class Meta: model=models.UserInfo # fields='__all__' fields = ['id', 'username', 'password', 'group', 'roles'] depth=0 # 取到外來鍵關聯一般設定為0到3 class UserInfoView(APIView): authentication_classes = [] throttle_classes = [] permission_classes = [] 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) return HttpResponse(ret)
1.4 序列化驗證的使用
自定義驗證類使用較少,不做過多的解釋。
# 自定義驗證類
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)
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 UserGroupSerializer(serializers.Serializer):
title=serializers.CharField(error_messages={"required":'標題不能為空'},validators=[XXValidator('老男人')])
class UserGroupView(APIView):
authentication_classes = []
permission_classes = []
throttle_classes = []
def post(self,request,*args,**kwargs):
ser=UserInfoSerializer(data=request.data)
if ser.is_valid():
print(ser.validated_data['title'])
return HttpResponse("提交資料")
else:
print(ser.errors)
return HttpResponse("錯誤")
鉤子函式的使用
class UserGroupSerializer(serializers.Serializer):
title = serializers.CharField(error_messages={'required':'標題不能為空'},validators=[XXValidator('老男人'),])
def validate_title(self, value): #使用 鉤子函式進行驗證。
from rest_framework import exceptions
raise exceptions.ValidationError('看你不順眼')
return value
2. 原始碼剖析
個人使用的時候
class RoleSerializer(serializers.Serializer):
# id=serializers.IntegerField()
idd=serializers.CharField(source="id")
title=serializers.CharField()
# 自定義的沒有構造方法去父類中去找
class RoleView(APIView):
authentication_classes = []
permission_classes = []
throttle_classes = []
def get(self,*args,**kwargs):
role=models.Role.objects.all()
ser=RoleSerializer(instance=role,many=True) #many =True表示多條資訊。
# 例項化物件,例項化的時候執行__new__和__init__方法。
data=json.dumps(ser.data) #執行ser.data
return HttpResponse(data)
原始碼:
class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
@property
def data(self):
ret = super().data
return ReturnDict(ret, serializer=self) #將結果返回為字典的資料型別。
pass #繼續繼承父類,接著找
class BaseSerializer(Field):
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)
def __new__(cls, *args, **kwargs):
# We override this method in order to automatically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False): #當 many 不存在時預設執行False
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)# 執行父類的new方法
@property
many_init
@classmethod
def many_init(cls, *args, **kwargs):
allow_empty = kwargs.pop('allow_empty', None)
max_length = kwargs.pop('max_length', None)
min_length = kwargs.pop('min_length', None)
child_serializer = cls(*args, **kwargs)
list_kwargs = {
'child': child_serializer,
}
if allow_empty is not None:
list_kwargs['allow_empty'] = allow_empty
if max_length is not None:
list_kwargs['max_length'] = max_length
if min_length is not None:
list_kwargs['min_length'] = min_length
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)
# 使用反射獲取ListSerializer的屬性,進而使得多條資料時返回的是列表形式。
# 返回的是不同的物件。
return list_serializer_class(*args, **list_kwargs)
檢視 ser.data的方法
@property
def data(self):
ret = super().data #獲取父類中的data屬性
return ReturnDict(ret, serializer=self) #將結果返回為字典的資料型別。
父類中的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.'
)
# 使用反射檢視,當前物件是否包含`initial_data` 或者不包含 `_validated_data`的時候 丟擲該異常。
raise AssertionError(msg)
if not hasattr(self, '_data'): #
# 不存在時執行to_representation方法
if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
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
listserlizer中的to_representation
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
# 迴圈可迭代物件(資料庫中查到的結果)並生成結果,返回型別。
self.child.to_representation(item) for item in iterable
]
生成url的類及每個欄位的型別處理
說明:每個欄位在序列化類中都是物件,而每個物件在執行data
的時候要執行to_representation
方法;
class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
def to_representation(self, instance):
ret = OrderedDict() # 使用有序字典
fields = self._readable_fields
for field in fields:
try:
# 迴圈每一個物件並過獲取其
# 去資料庫過去對相應的值。
# HyperlinkedIdentityField取到的是物件。
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:
'''
{
id:1 ,Charfiled
group:2, HyperlinkedIdentityField
}
'''
# 執行每一個欄位的`to_representation`方法,如果是物件則執行
ret[field.field_name] = field.to_representation(attribute)
return ret
# CharField中的to_representation方法。
class CharField(Field):
def to_representation(self, value):
return str(value) # 將物件以字串返回。
url生成類中
class HyperlinkedIdentityField(HyperlinkedRelatedField):
"""
A read-only field that represents the identity URL for an object, itself.
This is in contrast to `HyperlinkedRelatedField` which represents the
URL of relationships to other objects.
"""
def __init__(self, view_name=None, **kwargs):
assert view_name is not None, 'The `view_name` argument is required.'
kwargs['read_only'] = True
kwargs['source'] = '*'
super().__init__(view_name, **kwargs) # 尋找父類的方法
def use_pk_only_optimization(self):
# We have the complete object instance already. We don't need
# to run the 'only get the pk for this relationship' code.
return False
HyperlinkedRelatedField 父類程式碼
class HyperlinkedRelatedField(RelatedField):
def __init__(self, view_name=None, **kwargs):
if view_name is not None:
self.view_name = view_name
assert self.view_name is not None, 'The `view_name` argument is required.'
# 接收我們設定的lookup_field引數。
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
self.format = kwargs.pop('format', None)
# We include this simply for dependency injection in tests.
# We can't add it as a class attributes or it would expect an
# implicit `self` argument to be passed.
self.reverse = reverse # 進行反向生url.
super().__init__(**kwargs)
def to_representation(self, value):
assert 'request' in self.context, (
"`%s` requires the request in the serializer"
" context. Add `context={'request': request}` when instantiating "
"the serializer." % self.__class__.__name__
)
request = self.context['request']
format = self.context.get('format')
# By default use whatever format is given for the current context
# unless the target is a different type to the source.
#
# Eg. Consider a HyperlinkedIdentityField pointing from a json
# representation to an html property of that representation...
#
# '/snippets/1/' should link to '/snippets/1/highlight/'
# ...but...
# '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
if format and self.format and self.format != format:
format = self.format
# Return the hyperlink, or error if incorrectly configured.
try:
# 執行get_url方法。
url = self.get_url(value, self.view_name, request, format)
except NoReverseMatch:
msg = (
'Could not resolve URL for hyperlinked relationship using '
'view name "%s". You may have failed to include the related '
'model in your API, or incorrectly configured the '
'`lookup_field` attribute on this field.'
)
if value in ('', None):
value_string = {'': 'the empty string', None: 'None'}[value]
msg += (
" WARNING: The value of the field on the model instance "
"was %s, which may be why it didn't match any "
"entries in your URL conf." % value_string
)
raise ImproperlyConfigured(msg % self.view_name)
if url is None:
return None
return Hyperlink(url, value)
get_url()方法
def get_url(self, obj, view_name, request, format):
if hasattr(obj, 'pk') and obj.pk in (None, ''):# 獲取物件中是否存在引數
return None
lookup_value = getattr(obj, self.lookup_field)
# 使用反射根據lookup_field傳入只去資料庫中查詢
kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
驗證原始碼
class UserGroupView(APIView):
authentication_classes = []
permission_classes = []
throttle_classes = []
def post(self,request,*args,**kwargs):
ser=UserInfoSerializer(data=request.data)
if ser.is_valid():
print(ser.validated_data['title'])
return HttpResponse("提交資料")
else:
print(ser.errors)
return HttpResponse("錯誤")
檢視is_valid()
函式
def is_valid(self, raise_exception=False):
assert hasattr(self, 'initial_data'), (
'Cannot call `.is_valid()` as no `data=` keyword argument was '
'passed when instantiating the serializer instance.'
)
if not hasattr(self, '_validated_data'):
try:
self._validated_data = self.run_validation(self.initial_data)
# 執行當前物件的`run_validation`
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors) #有錯誤不通過
run_validation()
def run_validation(self, data=empty):
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value: # 檢查欄位是否為空
return data
value = self.to_internal_value(data) #執行本方法
try:
self.run_validators(value)
value = self.validate(value)
assert value is not None, '.validate() should return the validated data'
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
to_internal_value方法
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, Mapping):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
for field in fields:
# 使用反射獲取物件中是否存在`validate_filed`的方法,不存在則返回為None
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
# 首先執行紫衫引數中的正則驗證
validated_value = field.run_validation(primitive_value)
if validate_method is not None: # 執行定義的鉤子函式。
validated_value = validate_method(validated_value)
except ValidationError as exc:
# 此處在處理異常說明不通過驗證的時候可以觸發本異常
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
通過本部分原始碼可知:
自定義的鉤子方法應該是以validate_
開頭加上欄位名
稱進行結尾。也可以進行異常的處理。處理後需要返回的時候在檢視函式中進行json處理。用法參考上面的部分。
3. 內容總結
- 序列化部分原始碼比較混亂,讀原始碼較為困難。
- 深刻體會,反射是框架的靈魂
- 序列化是drf中與資料庫關聯較深的部分,自己編寫的程式碼主要集中在本部分。
- 本章重點主要在於使用。
- 序列化的知識與 django 中的 form 相似,可以對比學習
接著加油幹!