DRF中如何使用has_object_permission校验POST请求存库前权限
核心问题原因
你自定义的WarehouseIsMemberOfCompany权限类默认无法在POST请求写入数据库前触发校验,这是DRF的默认权限执行逻辑决定的:
has_permission方法会在所有请求进入视图业务逻辑前执行,此时还没有对应操作的数据库实例has_object_permission方法仅在视图调用get_object()获取到已存在的数据库实例后才会执行,只会作用于检索、更新、删除这类操作已有资源的请求,POST创建新资源时数据还未入库,没有现成的obj实例,因此不会触发该方法的逻辑。
实现方案
方案1:扩展权限类,在has_permission阶段完成POST场景前置校验
POST创建场景下,你需要校验的关联仓库ID是从请求参数中传入的,不需要等数据入库生成实例,因此可以直接在has_permission中区分请求类型,提前完成校验,修改后的权限类代码如下:
from rest_framework import permissions from .models import Warehouse class WarehouseIsMemberOfCompany(permissions.BasePermission): def has_permission(self, request, view): # 未登录用户直接拒绝 if not request.user.is_authenticated: return False # 非POST请求(操作已有资源)直接放行,后续走对象级权限校验 if request.method != "POST": return True # POST创建场景:从请求参数中取仓库ID做前置校验 warehouse_id = request.data.get("warehouse") if not warehouse_id: return False try: warehouse = Warehouse.objects.get(id=warehouse_id) except Warehouse.DoesNotExist: return False # 校验当前用户所属公司与仓库所属公司一致 return request.user.company.id == warehouse.company.id def has_object_permission(self, request, view, obj): # 操作已有资源时,校验实例关联仓库的公司归属 return request.user.company.id == obj.warehouse.company.id
在视图中正常配置该权限类即可生效:
from rest_framework import viewsets from .permissions import WarehouseIsMemberOfCompany from .models import StockRecord from .serializers import StockRecordSerializer class StockRecordViewSet(viewsets.ModelViewSet): queryset = StockRecord.objects.all() serializer_class = StockRecordSerializer permission_classes = [WarehouseIsMemberOfCompany]
方案2:在序列化器层做校验(更推荐)
如果希望权限校验和参数校验逻辑聚合,返回的错误格式和普通参数错误统一,可以把归属校验逻辑放到序列化器的字段校验方法中,执行时机同样在数据写入数据库之前:
from rest_framework import serializers from .models import StockRecord, Warehouse class StockRecordSerializer(serializers.ModelSerializer): class Meta: model = StockRecord fields = "__all__" def validate_warehouse(self, value): request = self.context["request"] if request.user.company.id != value.company.id: raise serializers.ValidationError("无权操作该归属仓库下的数据") return value
注:使用DRF默认的视图集/泛型视图时,会自动将request传入序列化器上下文,无需额外配置。
注意事项
- 上述两种方案的校验逻辑都在
serializer.save()执行前运行,不会出现校验不通过但数据已经写入数据库的问题。 - 如果你的POST接口是自定义操作而非创建资源,只要从请求参数中提前取出需要校验的关联对象,就可以用相同逻辑在
has_permission阶段完成拦截。
内容的提问来源于stack exchange,提问作者sk1p
相关产品推荐
相关产品推荐

