如何使用自定义函数对Django QuerySet查询集进行过滤
Django ORM 的 filter() 方法不支持直接传入自定义Python函数。QuerySet本质是数据库操作的抽象层,所有传入filter的条件最终都会被翻译成SQL语句在数据库层面执行,无法识别Python层面的判断逻辑,因此你当前写的给filter传lambda的写法无法生效。
下面是两种可行的实现方案:
方案一(生产环境推荐):使用ORM聚合直接在数据库层计算总经验
这个方案全程走SQL查询,性能远高于Python层遍历,不需要修改你现有的模型结构:
首先导入需要的ORM工具类:
from django.db.models import Sum, F, ExpressionWrapper, FloatField, Case, When, Value from django.utils import timezone
然后修改apply_filters方法里的xp过滤逻辑:
def apply_filters(obj, queryset): """Apply filters to given queryset""" xp = obj.request.GET.get("xp") if xp: required_xp = float(xp) queryset = queryset.annotate( # 计算每条经验的总天数,end_date为空默认算到今天 experience_days = Sum( ExpressionWrapper( Case( When(experience__end_date__isnull=True, then=Value(timezone.now().date())), default=F('experience__end_date') ) - F('experience__start_date'), output_field=FloatField() ) ), # 换算成年,用365.25兼容闰年 total_xp = ExpressionWrapper( F('experience_days') / 365.25, output_field=FloatField() ) ).filter(total_xp__gte=required_xp) return queryset
方案二(仅小数据量场景可用):Python层遍历过滤
如果你必须复用已经写好的calc_player_experience方法,可以先把所有符合基础条件的球员加载到Python层遍历判断,缺点是数据量大时性能会严重下降:
def apply_filters(obj, queryset): """Apply filters to given queryset""" xp = obj.request.GET.get("xp") if xp: required_xp = float(xp) # 先遍历所有球员判断是否符合条件 matched_pks = [ player.pk for player in queryset if obj.calc_player_experience(player, required_xp) ] # 重新过滤出符合条件的QuerySet queryset = queryset.filter(pk__in=matched_pks) return queryset
补充优化点
你原有代码里获取查询参数的写法可以简化,request.GET本身就是类字典结构,不需要转dict再取值,直接用obj.request.GET.get("xp")即可拿到对应参数值。
内容的提问来源于stack exchange,提问作者miguelmamfm
相关产品推荐
相关产品推荐

