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

xeufq47z  于 2023-03-13  发布在  Go
关注(0)|答案(8)|浏览(170)

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

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')

我想访问嵌套序列化器中的上下文,我该怎么做呢?据我所知,上下文没有传递给子进程。
我希望能够实现每个用户对字段的权限模型,为此我覆盖了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

下面哪一种方法适用于常规序列化器,但是当嵌套的子元素传递给get_fields()时,上下文以及请求和用户都不可用。当序列化器嵌套时,我如何访问上下文?

z6psavjg

z6psavjg1#

好的,我找到了一个可行的解决方案,我用添加上下文的SerializerMethodField替换了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

在我的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

这看起来工作得很好,当我撤销 Child.c_nameParent.child 上的读取权限时,序列化器中的子字段将被丢弃

bnlyeluc

bnlyeluc2#

如果你不能改变你的子序列化器的性质,就像@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)
2eafrhcq

2eafrhcq3#

您可以使用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')
yc0p9oo0

yc0p9oo04#

好了,我找到了一个终极解决方案,它可以完全满足我们的要求--将上下文传递给嵌套的序列化程序。要实现这一点,需要重写嵌套的序列化程序的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)
dbf7pr2w

dbf7pr2w5#

我知道这是一个老问题,但我在2019年也有过同样的问题,以下是我的解决方案:

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

在上面的代码中,我创建了一个序列化器基类,它覆盖了get_fields(),并将self._context传递给任何具有相同基类的子序列化器。
然后,我检查查询参数“omit_data”,如果请求,则删除“data”字段。
我希望这对任何仍在寻找答案的人有帮助。

0s0u357o

0s0u357o6#

我使用的是djangorestframework 3.12.xx,上下文会自动传播到嵌套的序列化程序。

erhoui1w

erhoui1w7#

如果您试图限制子序列化程序字段的查询集,则继续使用

self.parent.context

在子序列化程序内部访问父上下文。
像这样:

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

这个answer引导我通过调试和查看child的get_fields函数中的可用变量来发现这一点。

yzxexxkh

yzxexxkh8#

我阅读了源代码,我认为没有必要将上下文传递给嵌套字段序列化程序,因为它们可以访问上下文。
调用嵌套字段序列化程序的上下文将返回根上下文。下面是代码,它已经9年没有更改了。
如果你需要一些解释,在这个例子中,考虑字段是child。调用child.context将调用上面提到的属性函数,该函数本身使用self.root。在这种情况下,根属性函数将被解析为ParentSerializer
因此调用child.context将导致调用parent.context

相关问题