[Django]-Context in nested serializers django rest framework

49👍

Ok i found a working solution. I replaced the ChildSerializer assignment in the Parent class with a SerializerMethodField which adds the context. This is then passed to the get_fields method in my CustomModelSerializer:

class ChildSerializer(CustomModelSerializer):
    class Meta:
        fields = ('c_name', )
        model = Child


class ParentSerializer(CustomModelSerializer):

    child = serializers.SerializerMethodField('get_child_serializer')

    class Meta:
        model = Parent
        fields = ('p_name', 'child')

    def get_child_serializer(self, obj):
        serializer_context = {'request': self.context.get('request') }
        children = Child.objects.all().filter(parent=obj)
        serializer = ChildSerializer(children, many=True, context=serializer_context)
        return serializer.data

and in my CustomModelSerializer:

class CustomModelSerializer(rest_serializer_classes.HyperlinkedModelSerializer):

    def __init__(self, *args, **kwargs):
        """
            Make sure a user is coupled to the serializer (needed for permissions)
        """
        super().__init__(*args, **kwargs)
        if not self.context:
            self._context = getattr(self.Meta, 'context', {})
        try:
            self.user = self.context['request'].user
        except KeyError:
            self.user = None


    def get_fields(self):
        ret = OrderedDict()

        if not self.user:
            print("No user associated with object")
            return ret

        fields = super().get_fields()

        # Bypass permission if superuser
        if self.user.is_superuser:
            return fields

        for f in fields:
            if has_right(self.user, self.Meta.model.__name__.lower(), f, "read"):
                ret[f] = fields[f]

        return ret

This seems to work fine, and fields of the child are discarded in the serializer when i either revoke read-rights on Child.c_name or on Parent.child

28👍

If you can not change the nature of you child serializer, as in @Kirill Cherepanov and @Robin van Leeuwen answers, a light but not full-integrated solution would be to manually pass the context in __init__() function :

class ChildSerializer(CustomModelSerializer):
    class Meta:
        fields = ('c_name', )
        model = Child


class ParentSerializer(CustomModelSerializer):

    child = ChildSerializer(many=True, read_only=True)

    class Meta:
        model = Parent
        fields = ('p_name', 'child')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # We pass the "upper serializer" context to the "nested one"
        self.fields['child'].context.update(self.context)

10👍

You can use serialziers.ListField instead. ListField automatically passes context to it’s child. So, here’s your code

class ChildSerializer(ModelSerializer):
    class Meta:
        fields = ('c_name', )
        model = Child


class ParentSerializer(ModelSerializer):
    child = serializers.ListField(read_only=True, child=ChildSerializer())

    class Meta:
        model = Parent
        fields = ('p_name', 'child')

8👍

Ok, I have found an ultimate solution that will do exactly what was asked – pass context down to nested serializers.
To achieve that one need to override to_representation(self, instance) of the nested serializer, so it looks like:

def to_representation(self, instance):
    # here we update current serializer's context (access it as self._context)
    # to access parent's context we use parent.context
    # if there is no parent than it's the first serializer in the chain and it doesn't need any context except for itself's
    # for example (after all the checks)
    self._context["request"] = self.parent.context["request"]
    # and that is it! The modified context will be used for serialization as if it was passed as usually
    return super().to_representation(instance)

4👍

If you are trying to limit the queryset of the child serializer field, then go ahead and use

self.parent.context

inside the child serializer to access the parent context.

like so:

def get_fields(self):
    fields = super().get_fields()
    fields['product'].queryset = Product.objects.filter(company=self.parent.context['company'])
    return fields

This answer led me to find this via debugging and looking at the available variables in child’s get_fields function.

3👍

I know this is an old question, but I had the same question in 2019. Here is my solution:

class MyBaseSerializer(serializers.HyperlinkedModelSerializer):

    def get_fields(self):
        '''
        Override get_fields() method to pass context to other serializers of this base class.

        If the context contains query param "omit_data" as set to true, omit the "data" field
        '''
        fields = super().get_fields()

        # Cause fields with this same base class to inherit self._context
        for field_name in fields:
            if isinstance(fields[field_name], serializers.ListSerializer):
                if isinstance(fields[field_name].child, MyBaseSerializer):
                    fields[field_name].child._context = self._context

            elif isinstance(fields[field_name], MyBaseSerializer):
                fields[field_name]._context = self._context

        # Check for "omit_data" in the query params and remove data field if true
        if 'request' in self._context:
            omit_data = self._context['request'].query_params.get('omit_data', False)

            if omit_data and omit_data.lower() in ['true', '1']:
                fields.pop('data')

        return fields

In the above, I create a serializer base class that overrides get_fields() and passes self._context to any child serializer that has the same base class. For ListSerializers, I attach the context to the child of it.

Then, I check for a query param “omit_data” and remove the “data” field if it’s requested.

I hope this is helpful for anybody still looking for answers for this.

👤chaimp

3👍

I am using djangorestframework 3.12.xx and the context is automatically propagated to nested serializers.

0👍

I read the source code and I believe that it is not necessary to pass the context to nested field serializers as they have access to it.
Calling for the nested field serializer’s context will return the root context. Here is the code and it has not been changed for 9 years.
If you want some explanation, in this example consider field is child. Calling child.context will call the above mentioned property function which within itself uses self.root. root property function will be resolved to ParentSerializer in this case.
So calling child.context will result in calling parent.context.

0👍

With newest releases of Django REST Framework, the context is passed from parent to child serializer without need to override __init__ method, all you have to do is pass context when you call child serializer:


class ChildSerializer(serializers.ModelSerializer):

   class Meta:

      model = ChildModel
      fields = ['title']

    title = serializers.SerializerMethodField()

    def get_title(self, instance):

       request = self.context.get('request')

       #TODO

       return


class ParentSerializer(serializers.ModelSerializer):

   class Meta:

      model = ParentModel
      fields = ['details']

   details = serializers.SerializerMethodField()

   def get_details(self, instance):

      child_instances = ChildModel.objects.all()

      return ChildSerializer(
            instance=child_instances,
            many=True,
            read_only=True,
            context=self.context
      ).data

Leave a comment