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

如何用Django ORM与DRF实现按客户分组的JSON输出

问题描述

当前Django服务器返回的JSON格式如下:

[{
    "id": 1,
    "customer": {
      "id": 1,
      "name": "John Doe"
    },
    "description1": "...",
    "description2": "...",
    "description3": "...",
    "description4": "..."
  },
  {
    "id": 2,
    "customer": {
      "id": 1,
      "name": "John Doe"
    },
    "description1": "...",
    "description2": "...",
    "description3": "...",
    "description4": "..."
  },
  {
    "id": 3,
    "customer": {
      "id": 1,
      "name": "John Doe"
    },
    "description1": "...",
    "description2": "...",
    "description3": "...",
    "description4": "..."
  },
  {
    "id": 4,
    "customer": {
      "id": 2,
      "name": "Jane Doe"
    },
    "description1": "...",
    "description2": "...",
    "description3": "...",
    "description4": "..."
  },
  {
    "id": 5,
    "customer": {
      "id": 2,
      "name": "Jane Doe"
    },
    "description1": "...",
    "description2": "...",
    "description3": "...",
    "description4": "..."
  }
]

现有模型定义:

# customer/models.py
from django.db import models

class Customer(models.Model):
    name = models.CharField(verbose_name="Customer name")
# 其他app的models.py
from django.db import models

class PurchaseDescriptions(models.Model):
    customer = models.ManyToManyField("customer.Customer", related_name="customer", verbose_name="Customer")
    description1 = models.CharField(verbose_name="Description 1")
    description2 = models.CharField(verbose_name="Description 2")
    description3 = models.CharField(verbose_name="Description 3")
    description4 = models.CharField(verbose_name="Description 4")

序列化器代码:

from rest_framework import serializers
from .models import Customer, PurchaseDescriptions

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ["id", "name"]

class PurchaseDescriptionsSerializer(serializers.ModelSerializer):
    customer = CustomerSerializer()
    
    class Meta:
        model = PurchaseDescriptions
        fields = ["id", "customer", "description1", "description2", "description3", "description4"]

视图集代码:

from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from .models import PurchaseDescriptions
from .serializers import PurchaseDescriptionsSerializer

class ScheduleViewSet(
    mixins.ListModelMixin,
    GenericViewSet,
):
    queryset = PurchaseDescriptions.objects.all()
    serializer_class = PurchaseDescriptionsSerializer

期望将返回结果按customer字段分组,得到如下格式的JSON:

[{
    "customer": {
      "id": 1,
      "name": "John Doe"
    },
    "data": [
      {
        "id": 1,
        "description1": "...",
        "description2": "...",
        "description3": "...",
        "description4": "..."
      },
      {
        "id": 2,
        "description1": "...",
        "description2": "...",
        "description3": "...",
        "description4": "..."
      },
      {
        "id": 3,
        "description1": "...",
        "description2": "...",
        "description3": "...",
        "description4": "..."
      }
    ]
  },
  {
    "customer": {
      "id": 2,
      "name": "Jane Doe"
    },
    "data": [
      {
        "id": 4,
        "description1": "...",
        "description2": "...",
        "description3": "...",
        "description4": "..."
      },
      {
        "id": 5,
        "description1": "...",
        "description2": "...",
        "description3": "...",
        "description4": "..."
      }
    ]
  }
]

需要在不新增数据库表的前提下,通过Django视图和序列化器实现该需求。


解决方案

先修正模型的潜在问题

现有PurchaseDescriptions模型中,ManyToManyField的related_name设置为"customer"会与Customer模型名冲突,建议修改为合理名称,避免查询错误:

class PurchaseDescriptions(models.Model):
    customer = models.ManyToManyField("customer.Customer", related_name="purchase_descriptions", verbose_name="Customer")
    # 其余字段保持不变

方法一:重写视图集list方法,手动分组

无需修改序列化器结构,直接在视图层处理数据分组逻辑:

from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from rest_framework.response import Response
from .models import PurchaseDescriptions, Customer
from .serializers import PurchaseDescriptionsSerializer, CustomerSerializer

class ScheduleViewSet(
    mixins.ListModelMixin,
    GenericViewSet,
):
    # 预加载关联的customer数据,避免N+1查询
    queryset = PurchaseDescriptions.objects.all().prefetch_related('customer')
    serializer_class = PurchaseDescriptionsSerializer

    def list(self, request, *args, **kwargs):
        # 获取序列化后的原始数据
        queryset = self.filter_queryset(self.get_queryset())
        serializer = self.get_serializer(queryset, many=True)
        raw_data = serializer.data

        # 按customer的id分组
        grouped = {}
        for item in raw_data:
            customer_id = item['customer']['id']
            if customer_id not in grouped:
                grouped[customer_id] = {
                    'customer': item['customer'],
                    'data': []
                }
            # 移除customer字段,保留核心数据
            item.pop('customer')
            grouped[customer_id]['data'].append(item)
        
        # 将分组结果转为列表返回
        return Response(list(grouped.values()))

方法二:从Customer模型角度序列化,使用嵌套序列化器

调整序列化器结构,以Customer为主体,嵌套关联的PurchaseDescriptions数据:

  1. 定义仅包含PurchaseDescriptions核心字段的序列化器:
class PurchaseDataSerializer(serializers.ModelSerializer):
    class Meta:
        model = PurchaseDescriptions
        fields = ["id", "description1", "description2", "description3", "description4"]
  1. 修改CustomerSerializer,自定义输出格式:
class CustomerSerializer(serializers.ModelSerializer):
    # 关联PurchaseDescriptions数据,使用自定义的序列化器
    data = PurchaseDataSerializer(source='purchase_descriptions', many=True)
    
    class Meta:
        model = Customer
        fields = ["data"]
    
    def to_representation(self, instance):
        # 调整输出结构,将customer信息单独封装
        representation = super().to_representation(instance)
        return {
            'customer': {
                'id': instance.id,
                'name': instance.name
            },
            'data': representation['data']
        }
  1. 修改视图集,以Customer为查询主体:
class ScheduleViewSet(
    mixins.ListModelMixin,
    GenericViewSet,
):
    # 预加载关联的PurchaseDescriptions数据
    queryset = Customer.objects.all().prefetch_related('purchase_descriptions')
    serializer_class = CustomerSerializer

内容的提问来源于stack exchange,提问作者Nicnem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:44:53