context in nested serializers django rest framework

后端 未结 5 2032
有刺的猬
有刺的猬 2021-01-07 16:35

If i have a nested serializer:

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


class ParentSe         


        
相关标签:
5条回答
  • 2021-01-07 16:50

    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')
    
    0 讨论(0)
  • 2021-01-07 17:01

    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.

    0 讨论(0)
  • 2021-01-07 17:04

    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)
    
    0 讨论(0)
  • 2021-01-07 17:07

    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)
    
    0 讨论(0)
  • 2021-01-07 17:12

    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

    0 讨论(0)
提交回复
热议问题