current position:Home>Django serialization (II)

Django serialization (II)

2022-01-31 15:31:03 Waiting for

This is my participation 11 The fourth of the yuegengwen challenge 13 God , Check out the activity details :2021 One last more challenge

One 、 Source analysis

1. Basic knowledge of classes

Class is executed before instantiation __new__ Method , Used to control the process of generating instances of a class

Subclasses don't have __new__ Method executes the of the parent class __new__ Method

__new__ After the method is executed, execute __init__ Construction method

2. With ModelSerializer For example , nothing __new__ Method , Its parent class Serializer either , Upper parent class BaseSerializer contains __new__ Method , For analysis, see the notes , The following is the source code :

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 Construction method executed after 
        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 Parameters , If so, execute cls.many_init, If not, execute super(BaseSerializer).__new__
            return cls.many_init(*args, **kwargs)  # many=True, Said to QuerySet To deal with , Take this logic ,
        return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) # many = False , Represents processing of individual objects 
 Copy code 

Perform play __new__ Method then executes __init__ Construction method , At this time, there is a basis many Different values perform different construction methods , When many=True Time to execute cls.many_init Method ,

@classmethod
    def many_init(cls, *args, **kwargs):  # many=True, Execute the method 
        """ 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)  #  Finally using ListSerializer instantiate 
 Copy code 

From the above source code, we know , For individual objects , It's using Serializer Class for processing , If the object is QuerySet type ( Multiple object lists ), use LIstSeriallizer Handle , At this point, we call the... Of the object data Property to get the result ( In the example, this uses res.data), Here is the source code ( When searching, first look for the subclass , Without this attribute, go to the parent class to find ):

@property
def data(self):
    ret = super(Serializer, self).data  #  Execute parent data attribute 
    return ReturnDict(ret, serializer=self)
 Copy code 

Parent class BaseSerialize Property method of data Source code :

@property
def data(self):
    if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):  # For data validation, use 
        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):#  Judge whether there is a mistake , Serialization without error 
            self._data = self.to_representation(self.instance)    #  take instance(QuerySet object ) Pass in , Start serializing 
        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
 Copy code 

From the above source code, we can see , The serialization method is implemented by calling the class self.to_representation Method to serialize , So let's see Serializer Class to_representation Method

def to_representation(self, instance):
    """ Object instance -> Dict of primitive datatypes. """
    ret = OrderedDict()  # First the instance Into an ordered Dictionary 
    fields = self._readable_fields

    for field in fields: #  Loop defined fields , This field can be defined by ourselves , It can also be model In the field 
        try:
            attribute = field.get_attribute(instance) # Calling field get_attribute Method ( Parameters are objects ), In the example, it can be understood as 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
 Copy code 

In the above source code , call field.get_attribute(instance) Method to get the data of each field , Here is field.get_attribute(instance) Source code ( stay Field in )

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)  #  perform get_attribute function , Used to define field properties according to , Get different data ,                                        Note that this method does not bring self, It's a function , Not class methods .        self.source_attrs: With '.' Split list , Will be used to get properties for reflection 
    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)
 Copy code 

call get_attribute function , Further out , Need to analyze self.source_attrs Parameters , Here is self.source_attrs Part of the source code :

if self.source == '*':   
    self.source_attrs = []
else:
    self.source_attrs = self.source.split('.')  
    #self.source It's our custom field source Parameters , Such as :gp=serializers.CharField(source='group.name'),sss=serializers.CharField(source='get_user_type_display') Finally, the segmentation becomes ['group','name']
 Copy code 

The above analysis self.source_attrs It's a list ( from source Parameters are divided by points ), Continue to back get_attribute function , Here is the source :

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'] perhaps ['get_user_type_display',]for attr in attrs: 
    #  Loop list 
    try:
        if isinstance(instance, collections.Mapping): # If it is model Field mapping (DRF Internal field conversion ), Call directly model Class 
            instance = instance[attr]# Reassign , At this time instance Has been changed 
        else:
            instance = getattr(instance, attr) # otherwise , Use reflection to get results , Such as instance=getattr(userinfo_obj,group)
    except ObjectDoesNotExist:
        return None
    if is_simple_callable(instance): # Determine whether it is executable , At this point, as in our example get_user_type_display, The judgment process is similar to the following TIPS in , I won't go into too much detail here 
        try:
            instance = instance()   # Reassign , Execute with parentheses 
        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
 Copy code 

TIPS: Determine whether it is an executable method

import types

def func(arg):
    if isinstance(arg,types.FunctionType,):
        print('yes')
        arg()
    else:
        print('NO')

func(lambda :1)
func(111)

# Execution results :
yes
NO
 Copy code 

From the above source code analysis , The essence of serialization is to use django Of orm Of QuerSet Or individually model Object properties , Serialization using reflection or methods .

Two 、 data validation

1. Basic verification

DRF Data verification function and django Of form It's kind of similar , Example : The global configuration is used to obtain data json Parser , It has been introduced in the parser :

class CheckGroupData(serializers.Serializer):
    id=serializers.IntegerField(error_messages={'required':'id Can't be empty '})
    name=serializers.CharField(error_messages={'required':' Group name cannot be empty '})
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 Accept queryset Object or single model object , When there are multiple pieces of data , Use many=True, Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

    def post(self,request,*args,**kwargs):
        ret=CheckGroupData(data=request.data)# Global configuration is configured here json Parser , Use request.data Get data directly 
        if ret.is_valid():
            print(ret.validated_data)
            # Get a field data ret.validated_data.get('name')
            return HttpResponse(' Data validation succeeded ')
        else:
            print(ret.errors)
            return HttpResponse(' Data validation failed ')
 Copy code 

Use postman towards http://127.0.0.1:8000/api/v1/group/1 , send out json data , give the result as follows : Background results : Verify that the process is effective .

2. Custom validation

and django form Function as ,DRF Serialization supports custom data validation , Example :

# Custom validation rules 
class MyValidation(object):
    def __init__(self,base):
        self.base = base

    def __call__(self, value): #value Is the field value , Default delivery 
        if value == 'wd':
            message = " keyword %s It can't be %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 Accept queryset Object or single model object , When there are multiple pieces of data , Use many=True, Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

    def post(self,request,*args,**kwargs):
        ret=MySerializer(data=request.data)# Global configuration is configured here json Parser , Use request.data Get data directly 
        if ret.is_valid():
            print(ret.validated_data)
            # Get a field data ret.validated_data.get('name')
            return HttpResponse(' Data validation succeeded ')
        else:
            print(ret.errors)
            return HttpResponse(' Data validation failed ')
 Copy code 

send out {"name":"wd"} The results of data validation are as follows :

3. Hook function

For custom validation ,DRF and django Of form Components also give us built-in hook functions , Used to verify .

Validation process : is_valid-->self.run_validation-->to_internal_value-->to_internal_value-->validate_ Field name ( Perform field validation , Hook method )-->validate_method( Hook verification method )

validate_ Field name hook method validation example :

class MySerializer(serializers.Serializer):
    name = serializers.CharField()
    def validate_name(self,value): #  Verified field values 
        if value.startswith("w"):
            raise serializers.ValidationError('name The field cannot be in w start ')
        else:
            return value # Note that it has passed the verification , Must return its 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 Accept queryset Object or single model object , When there are multiple pieces of data , Use many=True, Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

    def post(self,request,*args,**kwargs):
        ret=MySerializer(data=request.data)# Global configuration is configured here json Parser , Use request.data Get data directly 
        if ret.is_valid():
            print(ret.validated_data)
            # Get a field data ret.validated_data.get('name')
            return HttpResponse(' Data validation succeeded ')
        else:
            print(ret.errors)
            return HttpResponse(' Data validation failed ')
 Copy code 

Also send json data {"name":"wd"} To verify , give the result as follows :  

image.png

copyright notice
author[Waiting for],Please bring the original link to reprint, thank you.
https://en.pythonmana.com/2022/01/202201311531002409.html

Random recommended