'Get value of another field in Field level Validation in DRF

I am using Field level Validation in my serializer and I have situation where I need value of first field while validating second field. I know in object level validation I can have access to that but my serailzer have many=True and in object level validation I cannot tell client side which iteration have raised the error.

Serailzer :

class Keys_Serializer(serializers.Serializer):
    """
    """
    key_id = serializers.IntegerField(required=True)
    key_name = serializers.CharField(required=True)
    value_id = serializers.IntegerField(required=False)

    def validate_key_id(self, value):
        """
        validate key id
        """
         *** validate key_id here ***
        return value

    def validate_value_id(self, value):
        """
        validate value_id w.r.t key_id
        """
         *** I need key_id of current iteration here so that I can validate value_id. ***
        return value

Is there any way of accessing the value of key_id in value_id validation.



Solution 1:[1]

I dug around codebase of drf a little bit. You can get values of all fields using following approach. This way you can throw serialization error as {'my_field':'error message} instead of {'non_field_error':'error message'}

def validate_myfield(self, value):
   data = self.get_initial() # data for all the fields
   #do your validation

However, if you wish to do it for ListSerializer, i.e for serializer = serializer_class(many=True), this won't work. You will get list of empty values. In that scenario, you could write your validations in def validate function and to avoid non_field_errors in your serialization error, you can raise ValidationError with error message as a dictionary instead of string.

def validate(self, data):
    # do your validation
    raise serializers.ValidationError({"your_field": "error_message"})

Solution 2:[2]

def validate(self, validated_data):
        """
        validate and verifies the user data before getting saved.
        :param validated_data: dict obj
        :return: validated_data
        """

        existing_data = self.to_representation(self.instance)

Solution 3:[3]

So if you're performing a create or update the plain unvalidated data can accessed in:

self.context['view'].get_serializer().data

Although the other solutions are in fact cleaner, we have a single model on a partitioned table and need another field that specifies the partition, since we're not partitioned on the primary key the database has no clue how to look it up.

EDIT: It appears that field is actually blank, you may have luck with the following, depending on how the Serializer is used (for this it must be used in a ViewSet)

self.context['view'].get_serializer().context['request'].data

Solution 4:[4]

One other simple solution is accessing the properties as below

class Keys_Serializer(serializers.Serializer):

key_id = serializers.IntegerField(required=True)
key_name = serializers.CharField(required=True)
value_id = serializers.IntegerField(required=False)

def validate_key_name(self, value):
    #getting other field values as below
    self.initialdata
    #scrape data from initialdata
    # incase of updation time use 
    # self.instance and self.inistialdata together to get respective ones
    return  value

    

Solution 5:[5]

I had a similar problem where I just needed other values for the output, not for validation but this could probably be a good starting point. It is based on https://www.django-rest-framework.org/api-guide/fields/#examples.

You need to override the get_attribute function to return the object instead of the attribute. From the docs:

class ClassNameField(serializers.Field):
def get_attribute(self, instance):
    # We pass the object instance onto `to_representation`,
    # not just the field attribute.
    return instance

Then I was able to do stuff like

def to_representation(self, instance):  # pylint: disable=arguments-renamed
    return instance.my_function()
    or 
    return f"{instance.attribute_1}, {instance.attribute_2}"

Solution 6:[6]

def validate_fieldname(self, value):
   data = self.context['request'].data

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Vijay Anand Pandian
Solution 3
Solution 4 DisappointedByUnaccountableMod
Solution 5 Dakota
Solution 6 rechie