基于用户组/权限限制QuerySet的最佳实践探讨
兄弟,先给你划个核心红线:绝对不要在前端做数据过滤!前端限制只是“表面功夫”,用户随便用Postman或者浏览器开发者工具直接调用API,就能绕过前端拿到所有发票数据,敏感信息泄露的风险极高,这是绝对的禁忌。
接下来聊聊DRF里常用的后端解决方案,都是业内公认的最佳实践:
1. 自定义权限类 + 重写get_queryset(最常用、最标准)
这是DRF开发中最主流的做法,把权限校验和数据范围控制分开,逻辑清晰又安全:
第一步:写权限类,校验用户资格
先定义一个权限类,判断用户是Admin(能看所有)还是Broker(只能看自己的),同时阻止无关用户访问:
from rest_framework import permissions class IsAdminOrOwnInvoice(permissions.BasePermission): def has_object_permission(self, request, view, obj): # Admin组用户拥有所有权限 if request.user.groups.filter(name='Admin').exists(): return True # Broker只能访问自己关联的发票 return obj.user == request.user def has_permission(self, request, view): # 先确保用户是Admin或Broker,否则直接拒绝访问 return request.user.groups.filter(name__in=['Admin', 'Broker']).exists()
第二步:在视图里过滤QuerySet
在视图集里重写get_queryset方法,根据用户身份返回对应的数据范围,从根源上限制返回的内容:
from rest_framework import viewsets from .models import Invoice from .serializers import InvoiceSerializer from .permissions import IsAdminOrOwnInvoice class InvoiceViewSet(viewsets.ModelViewSet): serializer_class = InvoiceSerializer permission_classes = [IsAdminOrOwnInvoice] def get_queryset(self): queryset = Invoice.objects.all() # 如果不是Admin,只返回当前用户的发票 if not self.request.user.groups.filter(name='Admin').exists(): queryset = queryset.filter(user=self.request.user) return queryset
这种方式的好处是:权限类负责“能不能访问”,get_queryset负责“能看到什么”,分工明确,代码也容易维护。
2. 自定义Model Manager(复用性更强)
如果多个视图都需要用到相同的发票过滤逻辑,把逻辑封装到模型的Manager里会更优雅,避免重复代码:
from django.db import models class InvoiceManager(models.Manager): def for_user(self, user): # 根据用户身份返回对应的QuerySet if user.groups.filter(name='Admin').exists(): return self.all() return self.filter(user=user) class Invoice(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) # 其他字段... objects = InvoiceManager()
然后在视图里直接调用这个方法就行:
def get_queryset(self): return Invoice.objects.for_user(self.request.user)
把业务逻辑下沉到模型层,视图层会更简洁,适合多视图复用的场景。
3. 自定义FilterBackend(适合复杂过滤场景)
如果需要结合其他过滤条件(比如按日期、发票状态过滤),可以用DRF的FilterBackend来封装权限过滤逻辑:
from rest_framework.filters import BaseFilterBackend class InvoicePermissionFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): if request.user.groups.filter(name='Admin').exists(): return queryset return queryset.filter(user=request.user)
然后在视图里配置这个FilterBackend,还能和DRF自带的SearchFilter、OrderingFilter配合使用:
class InvoiceViewSet(viewsets.ModelViewSet): serializer_class = InvoiceSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [InvoicePermissionFilter, filters.SearchFilter] search_fields = ['invoice_number'] queryset = Invoice.objects.all()
这种方式适合把权限过滤和其他业务过滤解耦,扩展性更强。
关于你提到的“两个权限”思路
你说的创建can_see_all_invoices和can_see_own_invoices两个权限其实也可行,但没必要拆分这么细——一个权限类就能覆盖两种场景,代码更简洁。如果未来有更复杂的角色权限需求,可以结合Django自带的权限系统(比如permission_required装饰器),但对于这种按用户组区分的场景,直接判断组身份会更直观。
最后再强调一遍:前端过滤绝对不可取,所有权限控制和数据过滤必须在后端完成,这是保障数据安全的核心原则。
内容的提问来源于stack exchange,提问作者Milano

