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

DRF中如何按Pizza价格总和或首个价格排序查询集?

问题:Django REST Framework多对多关联字段排序异常,如何按价格总和或首个价格排序

在使用DRF的OrderingFilter时,Pizza模型与PizzaPrice通过多对多字段关联,直接指定prices作为排序字段无法得到预期结果。需要实现按价格总和或首个价格对Pizza查询集进行排序。

提供的代码

models.py

class Categorie(models.Model):
    name = models.CharField(max_length=60, default=[0])

    def __str__(self) -> str:
        return self.name

class PizzaPrice(models.Model):
    price = models.DecimalField(max_digits=8, decimal_places=2)

    def __str__(self) -> str:
        return str(self.price)
    
    class Meta:
        ordering = ['price']

class PizzaSize(models.Model):
    size = models.IntegerField()

    class Meta:
        ordering = ['size']

    def __str__(self) -> str:
        return str(self.size)

class Pizza(models.Model):
    name = models.CharField(max_length=60)
    imageUrl = models.CharField(max_length=150)
    category = models.ForeignKey(Categorie, default=1, on_delete=models.CASCADE)
    rating = models.IntegerField(choices=PIZZA_RATING, default=1)
    sizes = models.ManyToManyField(PizzaSize, blank=True, default=[0])
    prices = models.ManyToManyField(PizzaPrice, blank=True, default=[0])


    def __str__(self) -> str:
        return self.name

serializer.py

class PizzaSerializer(serializers.ModelSerializer):
    category = serializers.CharField(source='category.name')
    sizes = serializers.SlugRelatedField(many=True, read_only=True, slug_field='size')
    prices = serializers.SlugRelatedField(many=True, read_only=True, slug_field='price')

    class Meta:
        model = Pizza
        fields = '__all__'

views.py

class PizzasList(generics.ListAPIView):
    queryset = Pizza.objects.all()
    serializer_class = PizzaSerializer
    pagination_class = PageNumberPagination
    filter_backends = [DjangoFilterBackend, OrderingFilter]

    filterset_fields = ['category']
    ordering_fields = ['rating', 'name', 'prices']

解决方案

问题根源:多对多关联字段直接排序时,Django无法明确使用关联集合中的哪一个值进行排序,因此需要通过聚合函数或子查询生成可用于排序的单值字段。

1. 按首个价格排序(基于PizzaPrice的默认排序)

由于PizzaPrice的Meta类中已设置ordering = ['price'],默认升序排列,我们可以通过Min聚合获取最低价格(即首个价格),或者用子查询严格取关联集合的第一个元素。

方式1:使用聚合函数Min

修改views.py,导入聚合函数并给Pizza查询集添加注解字段:

from django.db.models import Min
from rest_framework import generics

class PizzasList(generics.ListAPIView):
    # 给每个Pizza添加first_price注解,对应关联的最低价格
    queryset = Pizza.objects.annotate(first_price=Min('prices__price')).all()
    serializer_class = PizzaSerializer
    pagination_class = PageNumberPagination
    filter_backends = [DjangoFilterBackend, OrderingFilter]

    filterset_fields = ['category']
    # 将新添加的first_price加入可排序字段
    ordering_fields = ['rating', 'name', 'first_price']
方式2:使用子查询(更精准匹配PizzaPrice的排序)

如果需要严格按照PizzaPrice模型定义的排序取第一个价格,用子查询实现:

from django.db.models import Subquery, OuterRef
from rest_framework import generics

class PizzasList(generics.ListAPIView):
    # 子查询获取每个Pizza关联的第一个PizzaPrice的price值
    first_price_subquery = PizzaPrice.objects.filter(
        pizza=OuterRef('pk')
    ).order_by('price').values('price')[:1]
    
    queryset = Pizza.objects.annotate(first_price=Subquery(first_price_subquery)).all()
    serializer_class = PizzaSerializer
    pagination_class = PageNumberPagination
    filter_backends = [DjangoFilterBackend, OrderingFilter]

    filterset_fields = ['category']
    ordering_fields = ['rating', 'name', 'first_price']

2. 按价格总和排序

使用Sum聚合函数计算每个Pizza关联的所有价格总和,用于排序:

from django.db.models import Sum
from rest_framework import generics

class PizzasList(generics.ListAPIView):
    # 给每个Pizza添加total_price注解,对应所有关联价格的总和
    queryset = Pizza.objects.annotate(total_price=Sum('prices__price')).all()
    serializer_class = PizzaSerializer
    pagination_class = PageNumberPagination
    filter_backends = [DjangoFilterBackend, OrderingFilter]

    filterset_fields = ['category']
    # 将total_price加入可排序字段
    ordering_fields = ['rating', 'name', 'first_price', 'total_price']

可选:在序列化器中返回注解字段

如果需要前端能看到这些排序用的字段,修改serializer.py添加对应的字段:

class PizzaSerializer(serializers.ModelSerializer):
    category = serializers.CharField(source='category.name')
    sizes = serializers.SlugRelatedField(many=True, read_only=True, slug_field='size')
    prices = serializers.SlugRelatedField(many=True, read_only=True, slug_field='price')
    # 添加注解字段,只读
    first_price = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True)
    total_price = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True)

    class Meta:
        model = Pizza
        fields = '__all__'

使用方式

前端请求时,通过ordering参数指定排序字段:

  • 按首个价格升序:?ordering=first_price
  • 按首个价格降序:?ordering=-first_price
  • 按价格总和升序:?ordering=total_price
  • 按价格总和降序:?ordering=-total_price

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 12:49:52