如何在Django-filter中对非Django模型字段进行过滤与排序?
这个问题我之前也碰到过,核心原因是你用SerializerMethodField生成的last_run和last_status只存在于序列化阶段,而Django的过滤、排序是在ORM查询数据库的阶段就处理的——ORM根本不知道这些动态计算的字段,所以直接添加到过滤或排序字段里肯定会报“无法解析关键字”的错误。
要解决这个问题,我们需要把这两个字段提前注入到ORM查询集里,用Django的annotate功能把它们变成查询集的“虚拟字段”,这样过滤和排序组件就能识别它们了。下面是具体的实现步骤:
第一步:用annotate将计算字段注入查询集
首先要把你原来在get_last_spider_status里的逻辑,转换成ORM能处理的子查询(Subquery),这样就能一次性把所有Spider的最新运行时间和状态查出来,还能避免N+1查询的性能问题。
假设你的last_run和last_status来自一个关联的运行记录模型(比如叫SpiderRunRecord),修改views.py里的get_queryset方法:
from django.db.models import Subquery, OuterRef from .models import Spider, SpiderRunRecord # 替换成你实际的模型名 class SpiderListView(ListCreateAPIView): permission_classes = [IsAuthenticated] serializer_class = SpiderListSerializer filter_backends = [DjangoFilterBackend, OrderingFilter] filterset_class = SpiderFilter ordering_fields = ['name', 'user', 'project', 'creation_date', 'last_run', 'last_status'] # 现在可以加这两个字段了 def get_queryset(self): # 子查询:获取每个Spider的最新运行记录(按时间倒序取第一条) latest_run = SpiderRunRecord.objects.filter( spider=OuterRef('pk') ).order_by('-run_date')[:1] # 用annotate给每个Spider实例添加last_run和last_status字段 return Spider.objects.annotate( last_run=Subquery(latest_run.values('run_date')), last_status=Subquery(latest_run.values('status')) ).select_related('user', 'project') # 关联查询优化性能
如果你的get_last_spider_status是其他逻辑,只要能把它转换成ORM表达式,都可以用类似的方式注入到查询集里。
第二步:简化Serializer,直接映射注解字段
现在last_run和last_status已经是查询集里的字段了,不需要再用SerializerMethodField来计算,直接在Serializer里映射即可:
class SpiderListSerializer(serializers.ModelSerializer): user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all()) # 直接映射注解字段,还可以指定日期格式 last_run = serializers.DateTimeField(format="%d-%m-%Y %H:%M") last_status = serializers.CharField() class Meta: model = Spider fields = "__all__"
这样不仅代码更简洁,还解决了原来SerializerMethodField可能带来的性能问题。
第三步:在FilterSet中添加过滤规则
现在可以在filters.py里给这两个字段添加过滤规则了,比如日期范围、状态匹配等:
from django_filters import rest_framework as filters from .models import Spider class SpiderFilter(filters.FilterSet): name = filters.CharFilter(field_name='name', lookup_expr='icontains') user = filters.NumberFilter(field_name='user__id') project = filters.NumberFilter(field_name='project__id') creation_date = filters.DateFilter( field_name='creation_date', input_formats=['%d-%m-%Y'] ) # 添加last_run的过滤规则:精确日期、大于等于、小于等于 last_run = filters.DateFilter(field_name='last_run', input_formats=['%d-%m-%Y']) last_run__gte = filters.DateFilter(field_name='last_run', lookup_expr='gte', input_formats=['%d-%m-%Y']) last_run__lte = filters.DateFilter(field_name='last_run', lookup_expr='lte', input_formats=['%d-%m-%Y']) # 添加last_status的过滤规则:精确匹配、模糊包含 last_status = filters.CharFilter(field_name='last_status', lookup_expr='iexact') last_status__contains = filters.CharFilter(field_name='last_status', lookup_expr='icontains') class Meta: model = Spider fields = ['name', 'user', 'project', 'creation_date', 'last_run', 'last_status']
第四步:验证功能
现在启动服务测试一下:
- 排序:可以通过
?ordering=last_run或?ordering=-last_status来对这两个字段排序 - 过滤:比如
?last_status=SUCCESS或?last_run__gte=10-08-2023来过滤数据
这样就能完美实现你想要的功能了~
备注:内容来源于stack exchange,提问作者Adrian

