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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 21:45:03