Django中按指定日期范围的累计警告数与日期排序警告问题
问题描述
需要在Django项目中实现按**指定日期范围(start_date、end_date)内的累计警告数(warning_count)和警告日期(date)**对警告记录排序,但当前代码的排序功能无法正常工作。
原代码
class LatestWarningListView(generics.ListAPIView): serializer_class = LatestWarningSerializer pagination_class = ListPagination filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['student__school_year', 'student__attending', 'type'] search_fields = ["student__name"] ordering_fields = ['date', 'warning_count'] permission_classes = [IsTeacher] def get_queryset(self): self.pagination_class.page_size = 10 start_date_str = self.request.query_params.get('start_date', None) end_date_str = self.request.query_params.get('end_date', None) start_date = parse_date(start_date_str) if start_date_str else None end_date = parse_date(end_date_str) if end_date_str else None latest_warning = Warning.objects.filter(student=OuterRef('pk')).order_by('-date') if start_date: latest_warning = latest_warning.filter(date__gte=start_date) if end_date: latest_warning = latest_warning.filter(date__lte=end_date) students = Student.objects.annotate(latest_warning_id=Subquery(latest_warning.values('id')[:1])) warning_ids = [student.latest_warning_id for student in students if student.latest_warning_id is not None] warnings = Warning.objects.filter(id__in=warning_ids) return warnings class LatestWarningSerializer(serializers.ModelSerializer): student_name = serializers.SerializerMethodField() warning_id = serializers.IntegerField(source='id', read_only=True) student_id = serializers.IntegerField(source='student.id', read_only=True) warning_count = serializers.SerializerMethodField() class Meta: model = Warning fields = ['student_id', 'student_name', 'warning_id', 'type', 'date', 'content', 'warning_count'] def get_student_name(self, obj): return obj.student.name def get_warning_count(self, obj): start_date_str = self.context['request'].query_params.get('start_date', None) end_date_str = self.context['request'].query_params.get('end_date', None) start_date = parse_date(start_date_str) if start_date_str else None end_date = parse_date(end_date_str) if end_date_str else None warnings = Warning.objects.filter(type='경고', student=obj.student) if start_date: warnings = warnings.filter(date__gte=start_date) if end_date: warnings = warnings.filter(date__lte=end_date) return warnings.count()
修改方案
排序失效的核心原因是warning_count是序列化器中的方法字段,Django的OrderingFilter无法直接对这类字段排序,必须在QuerySet层面将其转为数据库注解字段,同时优化日期范围的过滤逻辑:
1. 修改get_queryset方法,添加warning_count注解
在QuerySet中直接计算指定日期范围内的警告数,这样才能支持排序:
from django.db.models import Count, Q, OuterRef, Subquery def get_queryset(self): self.pagination_class.page_size = 10 start_date_str = self.request.query_params.get('start_date', None) end_date_str = self.request.query_params.get('end_date', None) start_date = parse_date(start_date_str) if start_date_str else None end_date = parse_date(end_date_str) if end_date_str else None # 构建日期范围过滤条件 date_filter = Q() if start_date: date_filter &= Q(date__gte=start_date) if end_date: date_filter &= Q(date__lte=end_date) # 计算每个学生在指定日期范围内的警告数 warning_count_subquery = Warning.objects.filter( student=OuterRef('student__pk'), type='경고', **(date_filter.children if date_filter.children else {}) ).annotate(count=Count('id')).values('count')[:1] # 获取符合日期范围的最新警告 latest_warning = Warning.objects.filter( student=OuterRef('pk'), **(date_filter.children if date_filter.children else {}) ).order_by('-date').values('id')[:1] students = Student.objects.annotate( latest_warning_id=Subquery(latest_warning) ).filter(latest_warning_id__isnull=False) # 通过最新警告ID获取警告记录,同时关联warning_count注解 warnings = Warning.objects.filter( id__in=students.values('latest_warning_id') ).annotate( warning_count=Subquery(warning_count_subquery) ) return warnings
2. 修改序列化器,移除get_warning_count方法
因为warning_count已经是QuerySet的注解字段,直接映射即可:
class LatestWarningSerializer(serializers.ModelSerializer): student_name = serializers.SerializerMethodField() warning_id = serializers.IntegerField(source='id', read_only=True) student_id = serializers.IntegerField(source='student.id', read_only=True) # 直接使用QuerySet中的注解字段,无需方法 warning_count = serializers.IntegerField(read_only=True) class Meta: model = Warning fields = ['student_id', 'student_name', 'warning_id', 'type', 'date', 'content', 'warning_count'] def get_student_name(self, obj): return obj.student.name
3. 验证排序功能
现在可以通过请求参数ordering来指定排序规则:
- 按警告日期降序:
?ordering=-date - 按累计警告数升序:
?ordering=warning_count - 组合排序:
?ordering=-warning_count,date
说明
- 将
warning_count转为数据库注解后,OrderingFilter可以直接识别并排序,解决了之前方法字段无法排序的问题 - 统一在QuerySet层面处理日期范围过滤,避免序列化器中重复查询,提升性能
- 确保最新警告的过滤和警告数的计算使用相同的日期范围,数据逻辑一致
内容的提问来源于stack exchange,提问作者chea
相关产品推荐
相关产品推荐

