You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ModelViewSet中限制非超级用户的ManyToManyField访问?

问题原因

当前代码中,非超级用户的站点过滤逻辑是正确的(只返回包含用户有权限设备的站点),但prefetch_related("devices")会预取该站点下所有设备,而非用户有权限的设备。因此序列化时会把站点下全部设备返回,导致用户看到未授权的设备。

解决方案

方法1:使用Prefetch精准预取授权设备(推荐,性能最优)

通过django.db.models.Prefetch对象,指定预取设备时只包含当前用户有权限的设备,避免冗余数据。

修改视图集

from django.db.models import Prefetch

class SiteViewSet(viewsets.ReadOnlyModelViewSet):
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = SiteSerializer

    def get_queryset(self):
        user = self.request.user
        if user.is_superuser:
            return Site.objects.all().prefetch_related("devices")
        else:
            # 定义预取规则:只预取当前用户有权限的设备
            authorized_devices_prefetch = Prefetch(
                'devices',
                queryset=Device.objects.filter(users=user),
                to_attr='authorized_devices'  # 存储到自定义属性中
            )
            return Site.objects.filter(
                devices__users=user
            ).prefetch_related(authorized_devices_prefetch).distinct()

修改序列化器

将序列化器中的devices字段指向预取的authorized_devices属性:

class SiteSerializer(serializers.ModelSerializer):
    # 用source指定预取的自定义属性
    devices = SiteDeviceSerializer(source='authorized_devices', many=True, read_only=True)

    class Meta:
        model = Site
        fields = "__all__"

方法2:在序列化器中过滤设备

通过SerializerMethodField在序列化阶段过滤出用户有权限的设备,适合快速修改场景,但性能略逊于方法1(若未优化可能产生N+1查询)。

修改序列化器

class SiteSerializer(serializers.ModelSerializer):
    devices = serializers.SerializerMethodField()

    def get_devices(self, obj):
        user = self.context['request'].user
        if user.is_superuser:
            target_devices = obj.devices.all()
        else:
            # 过滤出当前用户有权限的设备
            target_devices = obj.devices.filter(users=user)
        return SiteDeviceSerializer(target_devices, many=True).data

    class Meta:
        model = Site
        fields = "__all__"

优化性能(可选)

为避免N+1查询,视图集的get_queryset中仍需保留prefetch_related("devices"),让序列化时直接使用预取的内存数据:

def get_queryset(self):
    user = self.request.user
    if user.is_superuser:
        return Site.objects.all().prefetch_related("devices")
    else:
        return Site.objects.filter(
            devices__users=user
        ).prefetch_related("devices").distinct()

内容的提问来源于stack exchange,提问作者darkvodka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:50:26