如何用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数据:
- 定义仅包含PurchaseDescriptions核心字段的序列化器:
class PurchaseDataSerializer(serializers.ModelSerializer): class Meta: model = PurchaseDescriptions fields = ["id", "description1", "description2", "description3", "description4"]
- 修改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'] }
- 修改视图集,以Customer为查询主体:
class ScheduleViewSet( mixins.ListModelMixin, GenericViewSet, ): # 预加载关联的PurchaseDescriptions数据 queryset = Customer.objects.all().prefetch_related('purchase_descriptions') serializer_class = CustomerSerializer
内容的提问来源于stack exchange,提问作者Nicnem
相关产品推荐
相关产品推荐

