Django Rest Framework Source Analysis (6) - - Serializers

Keywords: Python JSON Attribute Django REST

I. Introduction

The serialization component in the django rest framework can be said to be its core component and the most commonly used component. It not only has serialization function, but also provides data validation function (similar to form in django).

For easy serialization operations, we need to add foreign keys to the model and many-to-many situations. The following are new models (please delete the original database and migrate again):

models.py

from django.db import models

class UserInfo(models.Model):
    user_type_choice = (
        (1,"Ordinary users"),
        (2,"Member"),
    )
    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):
    """user group"""
    name = models.CharField(max_length=32,unique=True)


class Role(models.Model):
    """role"""
    name = models.CharField(max_length=32,unique=True)
Two, use

1. Basic use

Add a new role url to urls.py, where previous URLs are annotated to reduce interference:

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()),  # Role 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): #Define serialized classes
    id=serializers.IntegerField()  #Define the serialized fields to be extracted,Name and model The fields defined are the same
    name=serializers.CharField()
class RoleView(APIView):
    """role"""
    def get(self,request,*args,**kwargs):
        roles=models.Role.objects.all()
        res=RolesSerializer(instance=roles,many=True) #instance accept queryset Object or individual model Object, when there are multiple data, uses many=True,Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

Visit http://127.0.0.1:8000/api/v1/roles using browsers. The results are as follows:

2. Custom serialized fields

When there are foreign keys or many-to-many keys in the data model, you need to customize serialization

Added user information 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()), #User information
    # url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"),
]

UserinfoView and serialized classes

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): #Define serialized classes
    id=serializers.IntegerField()  #Define the serialized fields to be extracted,Name and model The fields defined are the same

    username=serializers.CharField()
    password=serializers.CharField()
    #sss=serializers.CharField(source='user_type') #This method can only get the ID of user_type
    sss=serializers.CharField(source='get_user_type_display') #Custom field names, inconsistent with data models, need to be specified source Essential invocation get_user_type_display()Method to obtain data
    gp=serializers.CharField(source='group.name') #Essence get group Object name,
    #rl=serializers.CharField(source='roles.all.first.name')
    rl=serializers.SerializerMethodField()   #Multi-pair and multi-serialization method I
    def get_rl(self,obj): #Fixed name: get_Defined field name
        """
        //Custom Serialization
        :param obj:Transitive model Object, encapsulated here
        :return:
        """
        roles=obj.roles.all().values() #Get all the roles

        return list(roles)  #The result must be correct. json Serializable objects
class UserinfoView(APIView):
    """User information"""
    def get(self,request,*args,**kwargs):
        users=models.UserInfo.objects.all()
        res=UserinfoSerializer(instance=users,many=True) #instance accept queryset Object or individual model Object, when there are multiple data, uses many=True,Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

Visit http://127.0.0.1:8000/api/v1/userinfo to see the results:

In addition to the above Serializer, you can also use Model Serializer, which inherits the serializer. The result is the same as the previous example:

class UserinfoSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()  # Define the serialized fields to be extracted,Name and model The fields defined are the same
    username=serializers.CharField()
    password=serializers.CharField()
    #sss=serializers.CharField(source='user_type') #This method can only get the ID of user_type
    sss=serializers.CharField(source='get_user_type_display') #Custom field names, inconsistent with data models, need to be specified source Essential invocation get_user_type_display()Method to obtain data
    #rl=serializers.CharField(source='roles.all.first.name')
    gp=serializers.CharField(source='group.name')
    rl=serializers.SerializerMethodField()   #Multi-pair and multi-serialization method I
    def get_rl(self,obj): #Fixed name: get_Defined field name
        """
        //Custom Serialization
        :param obj:Transitive model Object, encapsulated here
        :return:
        """
        roles=obj.roles.all().values() #Get all the roles

        return list(roles)  #The result must be correct. json Serializable objects
    class Meta:
        model = models.UserInfo
        fields = ['id', 'username', 'password', 'sss','rl','gp'] #Configure the fields to be serialized
        # fields = "__all__" Use model All fields in

class UserinfoView(APIView):
    """User information"""
    def get(self,request,*args,**kwargs):
        users=models.UserInfo.objects.all()
        res=UserinfoSerializer(instance=users,many=True) #instance accept queryset Object or individual model Object, when there are multiple data, uses many=True,Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

3. Serialization of linked lists and depth control

Depth is used for depth control, the deeper the serialization, the higher the fine reading.

class UserinfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserInfo
        #fields = "__all__" # Use all the fields in the model
        fields = ['id', 'username', 'password', 'group','roles']  # Configure the fields to be serialized
        depth = 1  #Serialization depth, 1~10,Recommended use no more than 3
class UserinfoView(APIView):
    """User information"""
    def get(self,request,*args,**kwargs):
        users=models.UserInfo.objects.all()
        res=UserinfoSerializer(instance=users,many=True) #instance accept queryset Object or individual model Object, when there are multiple data, uses many=True,Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

Request http://127.0.0.1:8000/api/v1/userinfo, the results are as follows:

 

4. Serialized field url

urls.py newly added group 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'),  # Newly added group 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 target url View aliases ( name),Here is UserGroup View aliases
    #lookup_field to url The passed parameter, that is, the field of regular matching
    #lookup_url_kwarg,url The middle regular name, that is kwargs Medium key
    class Meta:
        model = models.UserInfo
        #fields = "__all__" # Use all the fields in the model
        fields = ['id', 'username', 'password','roles','group']  # Configure the fields to be serialized
        depth = 1  #Serialization depth, 1~10,Recommended use no more than 3
class UserinfoView(APIView):
    """User information"""
    def get(self,request,*args,**kwargs):
        users=models.UserInfo.objects.all()
        res=UserinfoSerializer(instance=users,many=True,context={'request': request}) #instance accept queryset Object or individual model Object, when there are multiple data, uses many=True,Single object many=False
        #If you need to generate hyperlink fields, you need to add 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 accept queryset Object or individual model Object, when there are multiple data, uses many=True,Single object many=False
        return HttpResponse(json.dumps(res.data,ensure_ascii=False))

At this time, access group information: http://127.0.0.1:8000/api/v1/group/1, the results are as follows:

When viewing user information, the generated group is in the form of hyperlinks (for viewing json data, postman is used here to send requests):

 

3. Source code analysis

1. Basic knowledge of classes
  • Class is the u new_ method that is executed before instantiation to control the process of generating an instance of a class.

  • Subclasses do not have u new_ methods to execute the u new_ methods of parent classes
  • Execution of u init_ construction method after execution of u new_u method

2. Take ModelSerializer as an example. There is no u new_ method and its parent class Serializer does not exist. The parent class BaseSerializer contains u new_ method. For analysis, see the commentary. The following is the source code section:

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): # Construction of many=False post-execution
        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):    # The man parameter executes cls.many_init if it has one, and super (BaseSerializer). u new_u if it does not.
            return cls.many_init(*args, **kwargs)  # many=True, which means processing QuerySet and following the logic.
        return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) # many = False, which means processing individual objects

Execute the play new method and then execute the init construction method. At this time, different construction methods are executed according to the many value. When many=True, the cls.many_init method is executed.

@classmethod
    def many_init(cls, *args, **kwargs):  # many=True, execute this 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, use ListSerializer to instantiate

From the above source code, we know that for a single object, we use the Serializer class to process. If the object is QuerySet type (multiple object lists), we use LIstSeriallizer to process. At this time, we call the data attribute of the object to get the result (in the example, this is res.data). Here is the source code (search from the subclass first, search from the parent class without this attribute):

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

data source code for attribute method of parent BaseSerialize:

 @property
    def data(self):
        if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):  #Use for data validation
            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):# Determine whether there are errors, serialize without errors
                self._data = self.to_representation(self.instance)    # Pass in instance (QuerySet object) and begin serialization
            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

As you can see from the above source code, the serialization method is serialized by calling the class's self. to_representationmethod. Let's look at the to_representationmethod of the Serializer class.

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

        for field in fields: # A field defined in a loop, which can be defined by ourselves or in a model
            try:
                attribute = field.get_attribute(instance) #Call the get_attribute method of the field (the parameter is the object), which can be understood as group.get_attribute(group_obj) in the example.
            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

In the above source code, the field.get_attribute(instance) method is called to get the data of each field. The following is the field.get_attribute(instance) source code (in 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) # Execute the get_attribute function to get different data according to the field properties defined.
                                       Note that this method does not take self,It's a function, not a class method.
        self.source_attrs:with'.'Split lists are used to retrieve attributes 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)

To call the get_attribute s function, we need to analyze the self.source_attrs parameter. The following is the 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 passed in source Parameters such as: gp=serializers.CharField(source='group.name'),sss=serializers.CharField(source='get_user_type_display')
Finally, the partition becomes['group','name']

The above analysis of self.source_attrs is a list (split by point from the source parameter) and goes back to the get_attribute s function. The following is its source code:

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'] or ['get_user_type_display',] for attr in attrs: # Loop list try: if isinstance(instance, collections.Mapping): #If it's a model field mapping (DRF's internal field transformation), call the model class directly instance = instance[attr]#Reassignment, at which point instance has 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): #Judgment is executable. At this point, as in our example, get_user_type_display, its judgment process is similar to the following TIPS, so there is no more explanation here? try: instance = instance() #Re-assignment, bracketed execution 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: Is judgment an executable method?

import types

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

func(lambda :1)
func(111)

#Implementation results:
yes
NO

From the source code analysis above, the essence of serialization is to use django's orm's QuerSet or single model object characteristics, and use reflection or method to serialize.

IV. Data Verification

1. Basic Verification

The data validation function of DRF is somewhat similar to that of django's form. Example: The data is obtained using a globally configured json parser. Parser It has been introduced that:

class CheckGroupData(serializers.Serializer):
    id=serializers.IntegerField(error_messages={'required':'id Can not 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 individual model Object, when there are multiple data, uses 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)#The whole picture is configured here. json Parser,Use request.data Direct access to data
        if ret.is_valid():
            print(ret.validated_data)
            #Get a field data ret.validated_data.get('name')
            return HttpResponse('Successful data validation')
        else:
            print(ret.errors)
            return HttpResponse('Data Validation Failure')

Using postman to send json data to http://127.0.0.1:8000/api/v1/group/1, the results are as follows:

Background results:

Verify that the process is in effect.

2. Custom Validation

Like django form functionality, DRF serialization supports custom data validation. Examples:

#Custom Validation Rules
class MyValidation(object):
    def __init__(self,base):
        self.base = base

    def __call__(self, value): #Value is the field value, passed by default
        if value == 'wd':
            message = "Keyword%s Can not 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 individual model Object, when there are multiple data, uses 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)#The whole picture is configured here. json Parser,Use request.data Direct access to data
        if ret.is_valid():
            print(ret.validated_data)
            #Get a field data ret.validated_data.get('name')
            return HttpResponse('Successful data validation')
        else:
            print(ret.errors)
            return HttpResponse('Data Validation Failure')

Send {name":"wd"} data to verify the results as follows:

3. Hook function

For custom validation, DRF, like django's form component, also gives us built-in hook functions for validation.

Verification process:

Is_valid - > self. run_validation - > to_internal_value - > to_internal_value - > validate_field name (perform field validation, hook method) - - > validate_method (hook validation method)

validate_field name hook method validation example:

class MySerializer(serializers.Serializer):
    name = serializers.CharField()

    def validate_name(self,value): # Validated field values
        if value.startswith("w"):
            raise serializers.ValidationError('name Fields cannot be w Start')
        else:
            return value #Note that after validation, its value must be returned

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 individual model Object, when there are multiple data, uses 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)#The whole picture is configured here. json Parser,Use request.data Direct access to data
        if ret.is_valid():
            print(ret.validated_data)
            #Get a field data ret.validated_data.get('name')
            return HttpResponse('Successful data validation')
        else:
            print(ret.errors)
            return HttpResponse('Data Validation Failure')

Similarly, json data {"name":"wd"} is sent for verification, and the results are as follows:

Posted by chelnov63 on Mon, 24 Dec 2018 15:54:06 -0800