Django REST Framework: 如何为GET请求修改PrimaryKeyRelatedField查询集
DRF 的 PrimaryKeyRelatedField 自带的 get_queryset 方法本身仅作用于写入(POST/PUT/PATCH)阶段的参数合法性校验,读取序列化阶段不会触发,不需要强行修改其触发逻辑,直接扩展序列化和查询层的公共逻辑即可满足需求,以下是低冗余、高性能的实现方案:
方案实现
1. 扩展自定义关联字段,保留写入校验能力
你原有自定义字段的写入校验逻辑不需要改动,只需要新增序列化阶段的权限过滤逻辑即可:
class CharacterPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): def get_queryset(self): # 原有写入校验逻辑保留:仅允许选择同campaign的角色 return Character.objects.filter( campaign_id=self.context.get("view").kwargs.get("campaign_id") ) def to_representation(self, value): request = self.context.get("request") # 非DM用户隐藏标记为隐藏的角色 if not request.membership.is_dm and value.is_hidden: return None return super().to_representation(value)
如果是多值关联场景,可在序列化器中新增通用逻辑过滤掉返回结果中的None值,避免无权限的角色ID出现在列表中。
2. 通用Mixin统一解决查询、预取、更新返回值问题
针对你提到的PUT请求更新后返回结果仍带隐藏角色、多视图重复样板代码、prefetch失效的问题,写一个公共Mixin全局复用即可:
from django.db.models import Prefetch from rest_framework.response import Response class CampaignRelatedPrefetchMixin: def get_queryset(self): queryset = super().get_queryset() request = self.request campaign_id = self.kwargs["campaign_id"] # 统一配置角色过滤规则 character_qs = Character.objects.filter(campaign_id=campaign_id) if not request.membership.is_dm: character_qs = character_qs.filter(is_hidden=False) # 统一预取关联字段,所有继承该Mixin的视图自动生效,无N+1问题 return queryset.filter(campaign_id=campaign_id).prefetch_related( Prefetch("characters", queryset=character_qs) ) def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) # 更新后重新走过滤查询逻辑,保证返回结果符合权限要求 instance = self.get_queryset().get(pk=instance.pk) serializer = self.get_serializer(instance) return Response(serializer.data) def perform_create(self, serializer): # 统一写入campaign_id,不需要每个视图单独实现 serializer.save(campaign_id=self.kwargs["campaign_id"])
3. 视图层直接复用,无额外冗余代码
所有涉及campaign关联的视图只要继承该Mixin即可,不需要写重复逻辑:
class QuestController(CampaignRelatedPrefetchMixin, viewsets.ModelViewSet): serializer_class = QuestSerializer # 不需要再单独写get_queryset、perform_create、update方法
方案优势
- 逻辑高度复用:关联字段的写入校验、查询层的过滤预取规则都仅需写一次,所有同类场景自动生效,新增其他关联模型只要在Mixin中加一条Prefetch配置即可
- 性能无损耗:统一用prefetch_related预取关联数据,不会出现N+1查询问题
- 全场景覆盖:GET查询、POST/PUT更新后的返回结果都会自动按权限过滤,不存在遗漏场景
内容的提问来源于stack exchange,提问作者Kevin Renskers
相关产品推荐
相关产品推荐

