Django REST Framework创建记录前校验关联字段归属用户公司方法
DRF 跨公司越权问题解决方案
一、关联字段归属校验实现
你当前的代码只通过get_queryset过滤了查询范围,没有对写入操作传入的关联ID做归属校验,才会出现越权提交的问题,按以下步骤补全校验即可:
- 外键字段单字段校验
对于主单据上的warehouse、contragent这类直接关联的外键,在Serializer中通过validate_<字段名>方法做校验,从请求上下文拿到当前登录用户所属公司,判断传入的关联对象是否属于该公司,不属于则直接抛出校验错误。注意校验时必须带上公司过滤条件查询,不能仅判断对象是否存在,避免传入其他公司的资源ID。 - 嵌套数据批量校验
对于items嵌套列表里的product字段,不要在循环里单条查询校验,直接在Serializer的全局validate方法里提取所有商品ID,一次性批量查询当前公司下匹配的商品数量,如果匹配数量和提交的去重后商品ID数量不一致,说明存在不属于当前公司的商品,直接抛出错误即可,性能比循环单查更好。 - 校验逻辑参考代码
直接修改你的ConsignmentNoteSerializer即可:
这套校验逻辑在创建、更新操作时都会自动执行,能覆盖所有写入场景的越权风险。如果外键字段较多,可以封装通用校验函数减少重复代码。class ConsignmentNoteSerializer(serializers.ModelSerializer): creator = serializers.HiddenField(default=serializers.CurrentUserDefault()) items = ConsignmentItemSerializer(many=True) class Meta: model = ConsignmentNote fields = ['id', 'doc_type', "warehouse", 'date', 'number', 'contragent', 'comment', 'creator', 'items'] read_only_fields = ['id' ] def validate_warehouse(self, value): user_company = self.context['request'].user.company if not Warehouse.objects.filter(id=value.id, company=user_company).exists(): raise serializers.ValidationError("所选仓库不属于当前公司") return value # 往来单位如果绑定了公司外键,按同样逻辑校验 def validate_contragent(self, value): user_company = self.context['request'].user.company if not Contragent.objects.filter(id=value.id, company=user_company).exists(): raise serializers.ValidationError("所选往来单位不属于当前公司") return value def validate(self, attrs): user_company = self.context['request'].user.company items = attrs.get('items', []) product_ids = [item['product'].id for item in items] valid_count = Product.objects.filter( id__in=product_ids, company=user_company ).count() if valid_count != len(set(product_ids)): raise serializers.ValidationError({"items": "提交商品包含非当前公司所属数据"}) return attrs def create(self, validated_data): items = validated_data.pop('items') note = ConsignmentNote.objects.create(**validated_data) for item in items: product = item.pop('product') ConsignmentItem.objects.create(consignmentnote=note, product=product ,**item) return note
二、create方法的层级选择
你现在把嵌套单据创建的逻辑写在Serializer层的写法是正确的,两层的职责边界很清晰:
- Serializer层:负责数据校验、格式转换、模型实例的增改逻辑。尤其是你这种涉及嵌套多表写入的场景,把create逻辑放在Serializer里可以实现逻辑复用——不管哪个View、哪个接口调用这个Serializer,创建单据+关联商品条目的逻辑都能统一生效,不需要重复写代码。
- View层:负责请求处理、权限校验、查询集过滤、调用Serializer完成数据操作并返回响应。不要把具体的多表创建逻辑堆在View里,否则后期接口增多后代码会极度冗余难以维护。
只有当创建逻辑和请求上下文强耦合(比如需要根据URL路径参数、特殊请求头做和数据本身无关的分支处理)时,才适合在View层重写perform_create做额外处理,核心的对象创建逻辑始终应该收敛在Serializer层。
内容的提问来源于stack exchange,提问作者sk1p
相关产品推荐
相关产品推荐

