Location>code7788 >text

DRF-Serializers Serializer Component Source Code Analysis and Adaptation

Popularity:195 ℃/2024-10-28 22:53:15

1. Source code analysis

Note: The following code snippet has been simplified for ease of understanding, keeping only the code related to the serialization function

The source code for serialization involves the concept of metaclasses, which I'll briefly explain here: a metaclass is a high-level concept used to define the behavior of class creation. Simply put, a metaclass is a class that creates a class, and it determines how the class is created and how it behaves.

Everything is an object in Python, including classes. Every class has a metaclass that defines how the class is created. Typically, Python creates classes using the default metaclass type. However, you can use metaclasses when you want to customize the class creation process, for example:

class Mytype(type)
def __new__(cls,name,bases,attrs): # Class name, inherited parent class, members.
        # Here you can perform operations on the class you want to create.
        del attrs["v1"]
        attrs["name"] = "harry"

        xx = super(). __new__(cls,name,bases,attrs) # call type class to create object (this object is the Bar class)
        retyrn xx


class Bar(object, metaclass=Mytype) # metaclass specifies a custom metaclass
v1 = 123

    def func(self).
        pass

Since the v1 attribute was removed from the metaclass and the name attribute was added, there is no v1 attribute in Bar and there is a name attribute.

Another:parent classIf the metaclass metaclass is specified, itssubcategories(not) at alldefault (setting)is used to create the class with that metaclass

Addendum: Instantiating the Bar class is equivalent to a type object (), and therefore triggers the __call__ method of the type class, which calls Bar's __new__ and __init__, and that's why the __new__ and __init__ methods of the class are automatically triggered when the class is instantiated. Essentially the call methods of the type metaclass are called because of Object();


The Serializers component has two main functions: serialization and data validation.

  1. Serialization section:
    First define a serialization class
class DepartSerializer():
    '''Serializercalibration'''
    # 内置calibration
    title = (required=True, max_length=20, min_length=6)
    order = (required=False, max_value=100, min_value=10)
    count = (choices=[(1, "high level"), (2, "middle level (in a hierarchy)")])

Looking at the parent class of Serializer, it can be seen that it was created via the SerializerMetaclass metaclass

Serializer(BaseSerializer, metaclass=SerializerMetaclass)

SerializerMetaclass metaclass source code:

class SerializerMetaclass(type):
    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, (field_name))  # Fetch by loopfieldfield object (computing)
                  for field_name, obj in list(())
                  if isinstance(obj, Field)]
        (key=lambda x: x[1]._creation_counter)

        known = set(attrs)
        def visit(name):
            (name)
            return name

        base_fields = [
            (visit(name), f)
            for base in bases if hasattr(base, '_declared_fields')
            for name, f in base._declared_fields.items() if name not in known
        ]

        return OrderedDict(base_fields + fields)

    def __new__(cls, name, bases, attrs):
        attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs) # Added to the class_declared_fieldscausality,which encapsulates all theFieldField name and corresponding object
        return super().__new__(cls, name, bases, attrs)

by triggering the serialization process:

    @property
    def data(self):
        ret = super().data # Finding its parent classBaseSerializer(used form a nominal expression)datamethodologies
        return ReturnDict(ret, serializer=self)

Source code for BaseSerializer's data method:

    @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 is not None and not getattr(self, '_errors', None):
                self._data = self.to_representation() # fulfillmentto_representationmethod to get the serialized data
            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

to_representation method source code (core):

    def to_representation(self, instance):
        ret = OrderedDict()
        fields = self._readable_fields # Filter out readable field objects(its internal control over_declared_fieldsFields are deep-copied)

        for field in fields:
            try:
                attribute = field.get_attribute(instance) # Cyclic field object list,and implementationget_attributemethod to get the corresponding value
            except SkipField:
                continue
            check_for_none = 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) # fulfillmentto_representationconversion format,and encapsulates all the data in theretdictionary

        return ret

Source code for the get_attribute method:

def get_attribute(self, instance):
    return get_attribute(instance, self.source_attrs)
def get_attribute(instance, attrs): # attrsbecause ofsourcefield value instancebecause of模型对象
    for attr in attrs:
        try:
            if isinstance(instance, Mapping):
                instance = instance[attr]
            else:
                instance = getattr(instance, attr) # Loop to get the model object's finalattrvalue of
        except ObjectDoesNotExist:
            return None
    return instance # 返回该field value




2. Data validation component
Use the is_valid method to validate the data, get the _errors data, and is_valid returns False if _errors exists. run_validation method is triggered during the execution of this function:

    def is_valid(self, raise_exception=False):
        if not hasattr(self, '_validated_data'):
            try: # It's triggered.run_validationmethodologies
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = {}
                self._errors =
            else:
                self._errors = {}

        if self._errors and raise_exception:
            raise ValidationError()

        return not bool(self._errors)****

run_validation method, note that this method is a method under the Serializer class, not the Field class. Call the field's built-in validation in the to_internal_value method and execute the hook function.

    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) # Calling fields with built-in checks,and execute the hook function
        try:
            self.run_validators(value)
            value = (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

The to_internal_value method, where fileds are deep-copied from _declared_fields, contains only write-only field objects.

    def to_internal_value(self, data):
        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 # Filtering write-only field objects

        for field in fields:
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                validated_value = field.run_validation(primitive_value) # Perform built-in calibration
                if validate_method is not None:
                    validated_value = validate_method(validated_value) # Execute the hook function to perform the checksum
            except ValidationError as exc:
                errors[field.field_name] =
            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

run_validation has built-in calibration:

    def run_validation(self, data=empty):
        if data == '' or (self.trim_whitespace and str(data).strip() == ''):
            if not self.allow_blank:
                ('blank')
            return ''
        return super().run_validation(data)

    # parentrun_validationmethodologies
    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)
        self.run_validators(value) # Calling the field definition of therun_validatorscarry out calibration
        return value

2. Source code adaptation:

  • Custom hooks: allow a field to both support front-end pass-in and customize the value returned by serialization; (SerializerMethodField is read-only by default, so the user can't enter it, and regular fields can't be customized to return values with complex logic)

Idea: determine if there is a hook for custom formatting in the to_representation method after the call to start serialization, and if there is, replace the value of the field object

    def to_representation(self, instance):
        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            if hasattr(self, 'get_%s' % field.field_name):  # Determine if there is a"get_xxx"functions of the form,If then the method is executed and theinstancetransmitted inwards
                value = getattr(self, 'get_%s' % field.field_name)(instance)
                ret[field.field_name] = value
            else:
                try:
                    attribute = field.get_attribute(instance)
                except SkipField:
                    continue

                check_for_none = 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

If you need to use this rewrite method in other classes, you can encapsulate the rewrite method into a class, so that you don't have to rewrite the to_representation method every time you inherit from the other classes.