Django DRF 筛选关联外键项目并按日期范围汇总工时的问题咨询
关联外键工时聚合的实现方案
前置修正:模型定义的常见报错问题
你的Hours模型中外键定义缺少models前缀,且未按照Django要求声明删除策略,运行时会直接抛出异常,修正后的定义为:
class Hours(models.Model): hours = models.FloatField() assigned_virtual_project = models.ForeignKey(VirtualProject, on_delete=models.CASCADE) date = models.DateField()
注:Django 2.0及以上版本,所有外键必须显式声明on_delete参数
提示:如果后续给这个外键自定义了
related_name参数,后续代码里用到的反向关联名需要同步替换为自定义值,未设置时Django默认反向关联名为hours_set
核心实现逻辑调整
你当前的视图仅返回了归属指定owner的虚拟项目列表,要实现关联工时的日期范围筛选+求和,需要用到Django ORM的聚合注解能力,无需额外多表关联查询。
视图层逻辑修改
替换原有get_queryset方法,重点处理参数解析、关联数据聚合两个环节:
from django.db.models import Sum, Q from django.utils.dateparse import parse_date class GetDataView(viewsets.ModelViewSet): serializer_class = DataSerializer def get_queryset(self): # 解析请求携带的查询参数 owner_id = self.request.query_params.get('owner') date_range = self.request.query_params.get('dateRange') # 1. 保留你已实现的逻辑:筛选归属指定所有者的所有虚拟项目 base_queryset = VirtualProject.objects.filter(owner_id=owner_id) # 2. 解析日期范围参数,兼容前端传"开始日期,结束日期"的通用格式(如"2024-01-01,2024-01-31") date_agg_filter = Q() if date_range: try: start_str, end_str = date_range.split(',') start_date = parse_date(start_str.strip()) end_date = parse_date(end_str.strip()) if start_date and end_date: # 构造聚合专用过滤条件:仅统计日期落在区间内的工时记录 date_agg_filter = Q( hours_set__date__gte=start_date, hours_set__date__lte=end_date ) except ValueError: # 日期格式不合法时不做聚合过滤,返回所有工时总和 pass # 3. 给每个虚拟项目注解聚合字段:符合日期范围的工时总合 queryset = base_queryset.annotate( total_hours=Sum( 'hours_set__hours', filter=date_agg_filter, default=0 # 无匹配工时时返回0,避免前端收到None值 ) ) return queryset
关键设计说明
- 没有将日期过滤条件直接放到主queryset的filter中:如果采用这种写法,会把没有对应日期工时的虚拟项目直接从列表里过滤掉,和你“返回所有归属owner的虚拟项目、同时计算匹配的工时和”的需求不符
- 聚合参数内置的
filter选项:仅作用于求和计算的范围,完全不会影响主列表的项目完整性 - 直接用
owner_id做等值过滤:比先查询关联的Person对象再做关联过滤的执行效率更高 - 增加了简单的参数异常捕获:前端传的日期范围格式不合法时,不会直接抛出500服务端错误
序列化器调整
将聚合生成的total_hours字段纳入返回结果,修改原有VirtualProjectSerializer:
class VirtualProjectSerializer(serializers.ModelSerializer): # 注解生成的聚合字段为只读属性,直接声明对应字段类型即可 total_hours = serializers.FloatField(read_only=True) class Meta: model = VirtualProject fields = '__all__'
因为fields设置为'__all__',序列化器会自动包含模型本身的所有字段加上你新增的只读聚合字段,无需逐个声明字段名。
内容的提问来源于stack exchange,提问作者pipikej
相关产品推荐
相关产品推荐

