如何将过滤器应用于 Django REST 框架中的嵌套资源?

Posted

技术标签:

【中文标题】如何将过滤器应用于 Django REST 框架中的嵌套资源?【英文标题】:How can I apply a filter to a nested resource in Django REST framework? 【发布时间】:2013-05-25 04:09:56 【问题描述】:

在我的应用中,我有以下模型:

class Zone(models.Model):
    name = models.SlugField()

class ZonePermission(models.Model):
    zone = models.ForeignKey('Zone')
    user = models.ForeignKey(User)
    is_administrator = models.BooleanField()
    is_active = models.BooleanField()

我正在使用 Django REST 框架创建一个资源,该资源返回区域详细信息以及一个嵌套资源,该资源显示经过身份验证的用户对该区域的权限。输出应该是这样的:


    "name": "test", 
    "current_user_zone_permission": 
        "is_administrator": true, 
        "is_active": true
    
 

我已经创建了这样的序列化程序:

class ZonePermissionSerializer(serializers.ModelSerializer):
    class Meta:
        model = ZonePermission
        fields = ('is_administrator', 'is_active')

class ZoneSerializer(serializers.HyperlinkedModelSerializer):
    current_user_zone_permission = ZonePermissionSerializer(source='zonepermission_set')

    class Meta:
        model = Zone
        fields = ('name', 'current_user_zone_permission')

问题在于,当我请求特定区域时,嵌套资源会返回 所有 具有该区域权限的用户的 ZonePermission 记录。有什么方法可以将request.user 上的过滤器应用于嵌套资源?

顺便说一句,我不想​​为此使用 HyperlinkedIdentityField(以尽量减少 http 请求)。

解决方案

这是我根据以下答案实施的解决方案。我将以下代码添加到我的序列化程序类中:

current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission')

def get_user_zone_permission(self, obj):
    user = self.context['request'].user
    zone_permission = ZonePermission.objects.get(zone=obj, user=user)
    serializer = ZonePermissionSerializer(zone_permission)
    return serializer.data

非常感谢您的解决方案!

【问题讨论】:

【参考方案1】:

你必须使用filter而不是get,否则如果返回多条记录你会得到Exception。

current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission')

def get_user_zone_permission(self, obj):
    user = self.context['request'].user
    zone_permission = ZonePermission.objects.filter(zone=obj, user=user)
    serializer = ZonePermissionSerializer(zone_permission,many=True)
    return serializer.data

【讨论】:

【参考方案2】:

我会以两种方式之一来做。

1) 在您的视图中通过预取来完成:

    serializer = ZoneSerializer(Zone.objects.prefetch_related(
        Prefetch('zone_permission_set', 
            queryset=ZonePermission.objects.filter(user=request.user), 
            to_attr='current_user_zone_permission'))
        .get(id=pk))

2) 或者通过 .to_representation 执行:

class ZoneSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Zone
        fields = ('name',)

    def to_representation(self, obj):
        data = super(ZoneSerializer, self).to_representation(obj)
        data['current_user_zone_permission'] = ZonePermissionSerializer(ZonePermission.objects.filter(zone=obj, user=self.context['request'].user)).data
        return data

【讨论】:

【参考方案3】:

如果您在多个地方使用 QuerySet / 过滤器,则可以在模型上使用 getter 函数,然后甚至删除 Serializer / Field 的“源”kwarg。 DRF 在使用 get_attribute 函数时自动调用函数/可调用对象

class Zone(models.Model):
    name = models.SlugField()

    def current_user_zone_permission(self):
        return ZonePermission.objects.get(zone=self, user=user)

我喜欢这种方法,因为它使您的 API 在底层与 HTTP 上的 api 保持一致。

class ZoneSerializer(serializers.HyperlinkedModelSerializer):
    current_user_zone_permission = ZonePermissionSerializer()

    class Meta:
        model = Zone
        fields = ('name', 'current_user_zone_permission')

希望这对某些人有所帮助!

注意:名称不需要匹配,如果需要/想要匹配,您仍然可以使用 source kwarg。

编辑:我刚刚意识到模型上的函数无法访问用户或请求。所以也许自定义模型字段/ListSerializer 会更适合这项任务。

【讨论】:

【参考方案4】:

现在您可以使用我在此处描述的方法对 ListSerializer 进行子类化:https://***.com/a/28354281/3246023

您可以继承 ListSerializer 并覆盖 to_representation 方法。

默认情况下,to_representation 方法在嵌套查询集上调用 data.all()。因此,您实际上需要在调用该方法之前使 data = data.filter(**your_filters) 。然后,您需要将您的子类 ListSerializer 添加为嵌套序列化程序元上的 list_serializer_class。

    子类 ListSerializer,覆盖 to_representation 然后调用 super 将子类 ListSerializer 添加为嵌套 Serializer 上的元 list_serializer_class

【讨论】:

我也更喜欢这种方法,因为它仍然允许在需要时保持字段可写。【参考方案5】:

我面临着同样的情况。我发现的最佳解决方案是使用SerializerMethodField 并让该方法查询并返回所需的值。通过self.context['request'].user,您可以通过该方法访问request.user

不过,这似乎有点像 hack。我是 DRF 的新手,所以也许有更多经验的人可以加入。

【讨论】:

感谢您的建议。 SerializerMethodField 可以返回一个结构还是一个平面字段? 它可以返回一个结构。 我会尝试这种方法 - 谢谢。如果没有其他“官方”建议得到通过,我会接受这个作为答案。 您的建议有效,谢谢!我已将我在序列化程序中使用的代码添加为对上述问题的编辑。

以上是关于如何将过滤器应用于 Django REST 框架中的嵌套资源?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 objects.filter() 将字典过滤为 django rest 框架中的 POST 方法

如何使用 django rest 框架从 GET 请求的查询参数中过滤多个 id?

Django REST框架外键和过滤

在Django rest框架中过滤给定距离内的用户

如何使用 Django Rest 过滤器限制查询结果

django rest框架过滤器