1. 程式人生 > 其它 >drf(七)—序列化

drf(七)—序列化

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使用

說明:本內容的知識與djangoform(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. 內容總結

  1. 序列化部分原始碼比較混亂,讀原始碼較為困難。
  2. 深刻體會,反射是框架的靈魂
  3. 序列化是drf中與資料庫關聯較深的部分,自己編寫的程式碼主要集中在本部分。
  4. 本章重點主要在於使用。
  5. 序列化的知識與 django 中的 form 相似,可以對比學習

接著加油幹!