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

如何在Django QuerySet中动态添加自定义分组?

动态生成"Total Reports"分组并整合到QuerySet的解决方案

问题描述

我有Report和ReportGroup模型,二者为多对多关系。目前可正常获取所有ReportGroup实例的QuerySet,结构如下:

Group Name 1:
    Report Name 1
    Report Name 2
  Group Name 2:
    Report Name 2
    Report Name 10
  ... and etc.

现在需要在该QuerySet中添加动态生成的"Total Reports"分组,结构如下:

Group All reports:
    Report Name 1
    Report Name 2
    Report Name 5
  ... and other reports from QuerySet[Report]

限制条件:

  • 由于QuerySet受对象权限控制,无法将"Total Reports"分组存入数据库,必须动态生成
  • Serializer要求传入合法的QuerySet

尝试过的代码

我曾尝试以下写法,但调用total.reports.set()时出现错误:needs to have a value for field 'id'...

def get_queryset(self):
  groups = get_objects_for_user(
    user=self.request.user,
    perms=view_reportgroup,
    klass=ReportGroup.actived_objects,
    accept_global_perms=False
  )
  reports = get_objects_for_user(
    user=self.request.user,
    perms=view_report,
    klass=Report.actived_objects,
    accept_global_perms=False
  )
  total = ReportStorage(id=None, name='Total Reports')
  total.reports.set(
    Report.actived_objects.filter(
      Q(id__in=reports) | Q(groups__in=groups)
    ).distinct()
  )
  return (groups | total).prefetch_related(
    Prefetch(
      'reports',
      queryset=ReportStorage.prefetch_related(
        Prefetch(
          'reportstorage',
          queryset=super().get_queryset()
        )
      )
    )
  )

我还考虑过将ReportStorage实例添加到groups._result_cache,但不清楚具体实现。也想过自定义SQL或者把QuerySet转为列表,想知道有没有更合理的方案。

现有ViewSets代码

class ReportStorageGenericViewSet(GenericViewSet):
    """
    Basic view for filtering file storage objects.

    Fields
    -------
    queryset: QuerySet[ReportStorage]
        Basic set of objects.
    query_params_validator: QueryParamValidator
        Class for processing the correctness of the specified date from query_param.
    """
    queryset = ReportStorage.objects
    query_params_validator = QueryParamsValidator()  # getattr(settings, 'DATE_INPUT_FORMATS', None))
    permission_classes = [PortalRDjangoObjectPermissions]

    def _get_date(self) -> date:
        """
        Getting date from query_params.

        :return: Valid date, if the value is not specified, the reporting day is used.
        """
        date_str = self.request.query_params.get('date')
        self.query_params_validator(date_str)
        if date_str:
            return parse_date(date_str)

        return (timezone.now() - timedelta(days=1)).date()

    def get_queryset(self) -> QuerySet[ReportStorage]:
        """
        Generating a list of objects.

        It is guaranteed that the list of files is filtered by date.

        :return: A set of ReportStorage objects.
        """
        return super().get_queryset().filter(validity_period__contains=self._get_date())

    def get_serializer_context(self) -> Dict[str, Any]:
        """
        Adding a queryset to the context during the List operation to further check for the presence of loaded help at the current time.

        :return: Context for the serializer.
        """
        context = super().get_serializer_context()
        if self.action == 'list':
            context['queryset'] = self.get_queryset()

        return context

    def handle_exception(self, exc: APIException) -> Response:
        """
        Handling request execution errors.

        :param exc: Query execution exception.
        :return: If an error occurs, returns a standard {"detail": "message"} JSON response.
        """
        if isinstance(exc, ValidationError):
            return Response({"detail": exc.detail}, status=exc.status_code)

        return super().handle_exception(exc=exc)

class ReportGroupViewSet(ListModelMixin, ReportStorageGenericViewSet):
    """
    View to get a list of reporting groups with enabled reports and file uploads.

    Fields
    -------
    serializer_class: ReportGroupSerializer
        Basic view object serializer.
    """
    serializer_class = ReportGroupSerializer

    def get_queryset(self) -> QuerySet[ReportGroup]:
        """
        Generating a list of objects.

        :return: List of ReportGroup objects.
        """
        #### Override queryset here ####
        return ReportGroup.actived_objects.prefetch_related(
            Prefetch(
                'reports',
                queryset=Report.actived_objects.prefetch_related(
                    Prefetch('storage', queryset=super().get_queryset())
                )
            )
        )

解决方案建议

方案1:将QuerySet转为列表后添加动态实例

这是最直接的实现方式,无需修改Serializer核心逻辑,仅需在get_queryset中将QuerySet转为列表,手动添加动态生成的分组实例:

def get_queryset(self) -> list[ReportGroup]:
    # 获取权限过滤后的分组,预先完成关联数据预取
    groups = get_objects_for_user(
        user=self.request.user,
        perms=view_reportgroup,
        klass=ReportGroup.actived_objects,
        accept_global_perms=False
    ).prefetch_related(
        Prefetch(
            'reports',
            queryset=Report.actived_objects.prefetch_related(
                Prefetch('storage', queryset=super().get_queryset())
            )
        )
    )
    # 获取用户有权限查看的所有报告,同样完成预取
    allowed_reports = get_objects_for_user(
        user=self.request.user,
        perms=view_report,
        klass=Report.actived_objects.prefetch_related(
            Prefetch('storage', queryset=super().get_queryset())
        ),
        accept_global_perms=False
    ).distinct()
    
    # 动态生成Total Reports分组,用负ID区分数据库对象
    total_group = ReportGroup(id=-1, name='Total Reports')
    # 手动绑定reports属性,避免调用set()触发未保存对象的数据库操作
    total_group.reports = allowed_reports
    
    # 转为列表后添加动态分组
    groups_list = list(groups)
    groups_list.append(total_group)
    
    return groups_list

注意:需确保ReportGroupSerializer能处理未保存的实例,只要序列化name和reports字段即可,无需验证数据库存在性。

方案2:在响应层面追加动态数据

如果不需要返回严格的QuerySet,可直接在list方法中先序列化原分组数据,再追加"Total Reports"条目,对原逻辑侵入最小:

def list(self, request, *args, **kwargs):
    # 获取并序列化原分组数据
    queryset = self.filter_queryset(self.get_queryset())
    serializer = self.get_serializer(queryset, many=True)
    response_data = serializer.data
    
    # 获取并序列化用户有权限的所有报告
    allowed_reports = get_objects_for_user(
        user=self.request.user,
        perms=view_report,
        klass=Report.actived_objects.prefetch_related(
            Prefetch('storage', queryset=super().get_queryset())
        ),
        accept_global_perms=False
    )
    report_serializer = ReportSerializer(allowed_reports, many=True, context=self.get_serializer_context())
    
    # 追加Total Reports条目
    response_data.append({
        'id': -1,
        'name': 'Total Reports',
        'reports': report_serializer.data
    })
    
    return Response(response_data)

方案3:自定义SQL构造Union查询(复杂场景适用)

如果必须返回严格的QuerySet,可通过自定义SQL构造虚拟行与原QuerySet做Union,但需要手动处理多对多关联的预取逻辑,实现复杂度较高,仅适合对QuerySet有强依赖的场景。

内容的提问来源于stack exchange,提问作者Qngdjas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:43:16