[Solved]-DRF: Retrieve outer input data from nested serializer validate method

6đź‘Ť

Before validate() method, DRF serializers call to_internal_value(self, data). You will get all data of parent serializer there. So as you defined validate() method in serializer, define to_internal_value() method and catch parent serializer’s data.

👤Shaifali

2đź‘Ť

You can access initial_data on the parent serializer from the nested serializers validate() method. I’ve also added some code for using the parent fields run_validation() method, which would validate and return the internal value from to_internal_value(), which might be a better than dealing with the initial data.

class NestedSerializer(serializers.Serializer):

   def validate(self, data):
       # Retrieve the initial data, perhaps this is all you need.
       parent_initial_data = self.parent.initial_data
       info = parent_initial_data.get("info", None)

       # Get the corresponding field and use `run_validation` or `to_internal_value` if needed
       if info:
           info_field = self.parent.fields["info"]
           info = info_field.run_validation(info)
           # info = info_field.to_internal_value(info)  # If you don't want validation, but do want the internal value

       # Do your thing
       return data

👤A. J. Parr

2đź‘Ť

Try self.root.instance to get the parent instance in a nested serializer.

👤sun_jara

1đź‘Ť

It might not be the best idea to do it this way, NestedSerializer should not be aware of the parent object. It would make your code difficult to maintain, also it would make NestedSerializer dependent on OuterSerializer.

Instead, define a validate(self, data) method in the OuterSerializer and run the mutual validation there.

👤marxin

0đź‘Ť

Here’s what I’m doing now but I’m interested to see other answers..

Basically I’ve created a custom field for the field in the parent serializer that needs to be accessed in the child serializer – in this case “customer”. Then override to_internal_value() to add the field’s validated data as an attribute on the parent serializer.

Once it’s been added as an attribute it can be accessed on the child serializer through self.parent.<attribute_name> or on child serializer fields by self.root.<attribute_name>

class CustomerField(serializers.PrimaryKeyRelatedField):
    def to_internal_value(self, data):
        # Set the parent serializer's `customer` attribute to the validated
        # Customer object.
        ret = super().to_internal_value(data)
        self.parent.customer = ret
        return ret

class DebitField(serializers.PrimaryKeyRelatedField):
    default_related_name = {
        'OnAccount': 'onaccounts',
        'Order': 'orders'
    }

    def get_queryset(self):
        # Method must be overridden so the `queryset` argument is not required.
        return super().get_queryset()

    def set_queryset_from_context(self, model_name):
        # Override the queryset depending on the model name.
        queryset = self.default_related_name[model_name]
        self.queryset = getattr(self.parent.customer, queryset)

    def to_internal_value(self, data):
        # Get the model from the `debit_type` and the object id from `debit`
        # then validate that the object exists in the related queryset.
        debit_type = data.pop('debit_type')
        self.set_queryset_from_context(debit_type)
        super().to_internal_value(data)

class PaymentLineSerializer(serializers.ModelSerializer):
    debit = DebitField()

    class Meta:
        model = PaymentLine
        fields = (
            'id',
            'payment',
            'debit_type',
            'debit',  # GenericForeignKey
            'amount',
        )

    def to_internal_value(self, data, *args):
        data['debit'] = {
            'debit': data.pop('debit'),
            'debit_type': data.pop('debit_type'),
        }
        ret = super().to_internal_value(data)
        return ret

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['debit'] = instance.debit._meta.object_name
        return data

class PaymentSerializer(serializers.ModelSerializer):
    customer = CustomerField(queryset=Customer.objects.all())

    class Meta:
        model = Payment
        fields = (
            'id',
            'customer',
            'method',
            'type',
            'date',
            'num_ref',
            'comment',
            'amount',
        )

    def __init__(self, *args, **kwargs):
        self.customer = None
        super().__init__(*args, **kwargs)
        self.fields['lines'] = PaymentLineSerializer(
            context=self.context,
            many=True,
            write_only=True,
        )
👤bdoubleu

0đź‘Ť

You are almost there!!!

Use self.parent.initial_data to access the data given to the parent serializer.

class NestedSerializer(serializers.Serializer):
    value = AttributeValueField(required=True)
    name = serializers.CharField(required=True)

    def validate(self, attrs):
        attrs = super().validate(attrs)
        the_input_data = self.parent.initial_data
        info = the_input_data['info']  # this will not be the "validated data
        # do something with your "info"
        return attrs
👤JPG

0đź‘Ť

Do not hardcode the field_name

self.parent.initial_data[self.field_name]

Leave a comment