嵌套序列化程序中的上下文django rest框架

Rob*_*wen 25 django serialization python-3.x django-rest-framework

如果我有一个嵌套的序列化程序:

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


class ParentSerializer(ModelSerializer):

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

    class Meta:
        model = Parent
        fields = ('p_name', 'child')
Run Code Online (Sandbox Code Playgroud)

我想访问嵌套序列化器中的上下文,我该怎么做?据我所知,上下文没有传递给孩子.

我希望能够在字段上为每个用户实现一个权限模型,因为我重写了ModelSerializer的get_fields()方法:

def get_fields(self):
    fields = super().get_fields()
    ....
    for f in fields:
        if has_rights(self.context['request'].user, f, "read"):
            ret_val[f] = fields[f]
    ....
    return ret_val
Run Code Online (Sandbox Code Playgroud)

这适用于常规序列化程序,但是当嵌套子项传递给get_fields()时,上下文以及请求和用户不可用.串行器嵌套时如何访问上下文?

Rob*_*wen 20

好的,我找到了一个有效的解 我用ParentizerMethodField替换了Parent类中的ChildSerializer赋值,后者添加了上下文.然后将其传递给CustomModelSerializer中的get_fields方法:

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
Run Code Online (Sandbox Code Playgroud)

在我的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
Run Code Online (Sandbox Code Playgroud)

这似乎工作正常,当我撤销Child.c_nameParent.child上的读取权限时,在序列化程序中丢弃子字段

  • 如果我希望嵌套的序列化器是可写的,这将无济于事。 (2认同)

Tim*_*ion 10

如果您不能改变子序列化程序的性质,如@Kirill Cherepanov 和@Robin van Leeuwen 的回答,一个简单但不是完全集成的解决方案是在__init__()函数中手动传递上下文:

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)
Run Code Online (Sandbox Code Playgroud)

  • 这就是我一直在寻找的。接受的答案是它通常如何用于读取序列化器。但这对于可写的来说更好。 (3认同)

Vla*_*ich 9

好的,我找到了一个最终的解决方案,它将完全按照要求进行操作 - 将上下文传递给嵌套序列化器。要实现这一目标,需要重写to_representation(self, instance)嵌套序列化器,因此它看起来像:

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)
Run Code Online (Sandbox Code Playgroud)


Kir*_*nov 8

你可以serialziers.ListField改用。ListField自动将上下文传递给它的孩子。所以,这是你的代码

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')
Run Code Online (Sandbox Code Playgroud)