You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 07:37:51