Django-filter无法使用RawQuerySet的技术问题求助
解决方案:RawQuerySet兼容Django-filter问题
你遇到的核心问题是DjangoFilterBackend依赖标准QuerySet的接口(如.all()、.filter()),而RawQuerySet不支持这些方法。下面提供几种可行的解决思路:
方案1:手动拼接过滤条件到原生SQL
直接在get_queryset中提取请求的过滤参数,动态修改原生SQL的WHERE子句,生成带过滤逻辑的RawQuerySet。这种方式无需依赖DjangoFilterBackend,完全自己控制过滤逻辑,适合复杂原生SQL场景。
from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin class RegionAssetViewSet(ListModelMixin, GenericViewSet): serializer_class = AssetServerInfoSerializer def get_queryset(self): # 基础原生SQL base_sql = """ SELECT asset.id, region.id as region_id, asset.ip, asset.os_type, asset.status, asset.device_type_id FROM region_info AS region JOIN computer_info AS computer ON region.id = computer.region_id JOIN computer_cabinet_info AS cabinet ON computer.id = cabinet.computer_id JOIN asset_location_info AS location ON cabinet.id = location.cabinet_id JOIN asset_server_info AS asset ON location.asset_id = COALESCE ( asset.parent_id, asset.id ) WHERE asset.del_flag = 0 """ filters = [] params = [] query_params = self.request.query_params # 根据你的ServerFilter定义的字段,逐个处理过滤参数 # 示例:假设过滤字段包括os_type、status、device_type_id、region_id if 'os_type' in query_params: filters.append("asset.os_type = %s") params.append(query_params['os_type']) if 'status' in query_params: filters.append("asset.status = %s") params.append(query_params['status']) if 'device_type_id' in query_params: filters.append("asset.device_type_id = %s") params.append(query_params['device_type_id']) if 'region_id' in query_params: filters.append("region.id = %s") params.append(query_params['region_id']) # 拼接过滤条件到SQL if filters: base_sql += " AND " + " AND ".join(filters) return RegionAssetListVirtual.objects.raw(base_sql, params)
方案2:将原生SQL转为标准QuerySet(Django 3.2+)
如果你的RegionAssetListVirtual模型字段和原生SQL返回的字段完全匹配,可以用QuerySet的from_raw_query方法将原生SQL转为标准QuerySet,这样就能直接使用DjangoFilterBackend。
from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin from django.db.models import QuerySet from django_filters.rest_framework import DjangoFilterBackend class RegionAssetViewSet(ListModelMixin, GenericViewSet): serializer_class = AssetServerInfoSerializer filter_backends = [DjangoFilterBackend] filter_class = ServerFilter def get_queryset(self): region_asset_sql = """ SELECT asset.id, region.id as region_id, asset.ip, asset.os_type, asset.status, asset.device_type_id FROM region_info AS region JOIN computer_info AS computer ON region.id = computer.region_id JOIN computer_cabinet_info AS cabinet ON computer.id = cabinet.computer_id JOIN asset_location_info AS location ON cabinet.id = location.cabinet_id JOIN asset_server_info AS asset ON location.asset_id = COALESCE ( asset.parent_id, asset.id ) WHERE asset.del_flag = 0 """ # 将原生SQL转为标准QuerySet queryset = QuerySet(model=RegionAssetListVirtual, using=self.db).from_raw_query(region_asset_sql) return queryset
方案3:自定义FilterBackend兼容RawQuerySet
如果不想大幅修改现有代码,可以自定义一个FilterBackend,手动处理RawQuerySet的过滤逻辑,保留DjangoFilterBackend的过滤类配置。
from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin from django_filters.rest_framework.backends import DjangoFilterBackend class RawQuerySetFilterBackend(DjangoFilterBackend): def filter_queryset(self, request, queryset, view): # 仅处理RawQuerySet if hasattr(queryset, 'raw_query'): filter_class = self.get_filter_class(view, queryset) if filter_class: filter_instance = filter_class(request.query_params, queryset=queryset) filters = [] params = [] # 从过滤实例中提取已验证的过滤参数 for field_name, value in filter_instance.form.cleaned_data.items(): if value is not None: # 根据过滤字段对应SQL中的表/别名调整字段名 if field_name == 'region_id': filters.append(f"region.id = %s") else: filters.append(f"asset.{field_name} = %s") params.append(value) if filters: # 拼接过滤条件到原SQL original_sql = queryset.raw_query original_sql += " AND " + " AND ".join(filters) # 返回新的RawQuerySet return queryset.model.objects.raw(original_sql, params) # 非RawQuerySet走默认逻辑 return super().filter_queryset(request, queryset, view) class RegionAssetViewSet(ListModelMixin, GenericViewSet): serializer_class = AssetServerInfoSerializer filter_backends = [RawQuerySetFilterBackend] filter_class = ServerFilter def get_queryset(self): return RegionAssetListVirtual.objects.raw(region_asset_sql)
内容的提问来源于stack exchange,提问作者陈俊汝
相关产品推荐
相关产品推荐

