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

Django REST框架按日期分组订单并定制序列化响应问题

如何用Django REST Framework序列化器按日期分组订单并实现分页?

嘿,我来帮你搞定这个按日期分组订单的需求!先理清楚你的问题:你需要把Order模型的数据按ordered_at日期分组,返回嵌套格式的响应,还要用序列化器实现,同时结合DRF的分页功能对吧?

你的现状

首先看你的Order模型:

class Order(models.Model):
    drink = models.TextField(blank=True, null=True)
    total = models.IntegerField(null=True, blank=True)
    ordered_at = models.DateField(default=timezone.now)

你期望的响应是按日期分组,每个日期条目包含该日的所有订单,格式如下:

[
    {
        "id": 1,
        "ordered_at": "2020-09-19",
        "orders": [
            {
                "id": 64,
                "drink": "",
                "total": 200000,
                "ordered_at": "2020-09-19"
            },
            {
                "id": 65,
                "drink": "",
                "total": 200000,
                "ordered_at": "2020-09-19"
            }
        ]
    },
    {
        "id": 2,
        "ordered_at": "2020-09-18",
        "orders": [
            {
                "id": 63,
                "drink": "",
                "total": 200000,
                "ordered_at": "2020-09-18"
            }
        ]
    }
]

你提到想使用distinct()和group_by,但不确定怎么在序列化器里落地,而且希望用序列化器而非纯APIView实现,还要加分页。下面一步步来解决:


步骤1:先搞定数据分组(避免N+1查询)

首先我们需要从数据库中把订单按日期分组,这里要注意性能,尽量减少查询次数。可以先一次性拉取所有订单,再在内存中分组:

from django.utils import timezone

def get_grouped_orders():
    # 一次性获取所有订单,按日期倒序排列
    all_orders = Order.objects.order_by('-ordered_at')
    
    # 用字典按日期分组
    date_order_map = {}
    for order in all_orders:
        # 提取日期部分(因为你的ordered_at是DateField,直接取就行)
        order_date = order.ordered_at
        if order_date not in date_order_map:
            date_order_map[order_date] = []
        date_order_map[order_date].append(order)
    
    # 把字典转换为有序的列表,按日期倒序,同时添加分组id
    grouped_data = []
    for idx, (date, orders) in enumerate(sorted(date_order_map.items(), key=lambda x: x[0], reverse=True), start=1):
        grouped_data.append({
            'id': idx,
            'date': date,
            'orders': orders
        })
    
    return grouped_data

这样只需要一次数据库查询,比循环查每个日期的订单高效多了。


步骤2:自定义序列化器适配分组结构

因为我们需要嵌套的响应格式,所以要写两个序列化器:一个处理单个订单,一个处理分组后的日期条目:

from rest_framework import serializers
from .models import Order

# 单个订单的序列化器
class OrderItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = ['id', 'drink', 'total', 'ordered_at']

# 分组后的日期条目序列化器
class DateGroupedOrderSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    # 把date字段映射为ordered_at,匹配你要的响应字段名
    ordered_at = serializers.DateField(source='date')
    # 嵌套订单序列化器
    orders = OrderItemSerializer(many=True)

这里用source='date'把我们分组时的date字段映射成响应里的ordered_at,完美匹配你要的格式。


步骤3:结合DRF的分页功能

DRF的默认分页是针对QuerySet的,但我们的分组数据是列表,所以需要在视图中手动处理分页。我们用APIView来实现,同时集成DRF的分页组件:

首先定义一个自定义分页类(可以根据需求调整参数):

from rest_framework.pagination import PageNumberPagination

class GroupedOrderPagination(PageNumberPagination):
    page_size = 5  # 默认每页显示5个分组
    page_size_query_param = 'page_size'  # 允许前端指定每页数量
    max_page_size = 20  # 最大每页数量限制

然后写视图类:

from rest_framework.views import APIView
from rest_framework.response import Response

class GroupedOrderListView(APIView):
    pagination_class = GroupedOrderPagination

    def get(self, request):
        # 获取分组好的数据
        grouped_data = get_grouped_orders()
        
        # 初始化分页器
        paginator = self.pagination_class()
        # 对分组数据进行分页
        paginated_groups = paginator.paginate_queryset(grouped_data, request)
        
        # 序列化分页后的分组数据
        serializer = DateGroupedOrderSerializer(paginated_groups, many=True)
        
        # 返回带分页信息的响应(包含next/previous链接等)
        return paginator.get_paginated_response(serializer.data)

最后别忘了在urls.py中配置这个视图的路由:

from django.urls import path
from .views import GroupedOrderListView

urlpatterns = [
    path('grouped-orders/', GroupedOrderListView.as_view(), name='grouped-orders'),
]

补充优化方案(数据库层面分组)

如果你的订单量特别大,还可以考虑用Django的TruncDate注解来做数据库层面的分组,进一步优化性能:

from django.db.models.functions import TruncDate
from django.db.models import Count

date_groups = Order.objects.annotate(
    date=TruncDate('ordered_at')
).values('date').annotate(order_count=Count('id')).order_by('-date')

然后再根据每个date查询对应的订单,不过这种方式如果日期很多还是会有N+1问题,所以内存分组在大部分场景下更实用。


内容的提问来源于stack exchange,提问作者Hà Ngọc Khuê

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 11:57:51