【问题标题】:How to group an array of objects by one of the model's attribute in Django?如何通过 Django 中的模型属性之一对对象数组进行分组?
【发布时间】:2021-08-09 18:11:11
【问题描述】:

我想做类似于此示例 How to group an array of objects by key 的操作,但我在 Django 中找不到 API Get 的解决方案。

这是我所拥有的一个例子:

[
    {
        "id": 1,
        "nutrition_measurement_id": "1",
        "nutrition_type_id": "2",
        "feeding_time": "dinner"
    },
    {
        "id": 2,
        "nutrition_measurement_id": "2",
        "nutrition_type_id": "1",
        "feeding_time": "dinner"
    },
    {
        "id": 3,
        "nutrition_measurement_id": "3",
        "nutrition_type_id": "2",
        "feeding_time": "breakfast"
    },
    {
        "id": 4,
        "nutrition_measurement_id": "2",
        "nutrition_type_id": "1",
        "feeding_time": "breakfast"
    }
]
这是我想要实现的示例:
{
 "dinner": [
 {
   "id": 3,
   "nutrition_type_id": 2,
   "nutrition_measurement_id": 1
 },
{
   "id": 3,
   "nutrition_type_id": 1,
   "nutrition_measurement_id": 2
 }
 ],

"breakfast": [
 {
   "id": 3,
   "nutrition_type_id": 2,
   "nutrition_measurement_id": 3
 },
{
    "id": 5,
    "nutrition_type_id": 1,
    "nutrition_measurement_id": 4
 }
]

}

这是我拥有的模型:

class HorseNutrition(models.Model):
    horse = models.ForeignKey(Horse, models.DO_NOTHING)
    nutrition_type = models.ForeignKey('NutritionType', models.DO_NOTHING)
    nutrition_measurement = models.ForeignKey('NutritionMeasurement', models.DO_NOTHING)
    feeding_time = models.CharField(max_length=10, blank=True, null=True)
    class Meta:
        managed = False
        db_table = 'horse_nutrition'

class NutritionMeasurement(models.Model):
    name = models.CharField(unique=True, max_length=30, blank=True, null=True)
    class Meta:
        managed = False
        db_table = 'nutrition_measurement'

    def __str__(self):
        return self.name
    def __unicode__(self):
        return self.name


class NutritionType(models.Model):
    name = models.CharField(unique=True, max_length=30, blank=True, null=True)
    class Meta:
        managed = False
        db_table = 'nutrition_type'

    def __str__(self):
        return self.name
    def __unicode__(self):
        return self.name

这是我的序列化程序

class HorseNutritionSerializer(serializers.ModelSerializer):

    nutrition_measurement_id = serializers.ReadOnlyField(source = 'nutrition_measurement.name')
    nutrition_type_id = serializers.ReadOnlyField(source = 'nutrition_type.name')
    
    class Meta: 
        model = HorseNutrition
        fields = ['id', 'nutrition_measurement_id', 'nutrition_type_id', 'feeding_time']

这是我的看法:

class HorseNutritionView(APIView):
    
    def get (self, request, format = None):
        #id = int(request.GET.get(self.lookup_url_kwarg))
        id = 1
        if id != None:
            queryset = HorseNutrition.objects.filter(horse_id = id)
            serializer = HorseNutritionSerializer(queryset, many = True)             
            return Response(serializer.data)
        else:
            return Response(status = status.HTTP_400_BAD_REQUEST)

【问题讨论】:

    标签: django api django-models django-rest-framework django-views


    【解决方案1】:

    您可以使用自定义ListSerializer(我们将在您的序列化程序的Meta 中设置list_serializer_class)并覆盖其to_representation 并使用itertools.groupby 对您的结果进行分组:

    from itertools import groupby
    
    class HorseNutritionListSerializer(serializers.ListSerializer):
        def to_representation(self, data):
            ret = super().to_representation(data)
            return {key: list(group) for key, group in groupby(ret, key=lambda x: x['feeding_time'])}
        
        @property
        def data(self):
            ret = serializers.BaseSerializer.data.fget(self)
            return serializers.ReturnDict(ret, serializer=self)
    
    
    class HorseNutritionSerializer(serializers.ModelSerializer):
        nutrition_measurement_id = serializers.ReadOnlyField(source = 'nutrition_measurement.name')
        nutrition_type_id = serializers.ReadOnlyField(source = 'nutrition_type.name')
        
        class Meta: 
            model = HorseNutrition
            fields = ['id', 'nutrition_measurement_id', 'nutrition_type_id', 'feeding_time']
            list_serializer_class = HorseNutritionListSerializer
    

    此外,要正确完成此分组,您的查询集必须是有序的,您可以在视图中执行此操作:

    class HorseNutritionView(APIView):
        
        def get (self, request, format = None):
            #id = int(request.GET.get(self.lookup_url_kwarg))
            id = 1
            if id != None:
                queryset = HorseNutrition.objects.filter(horse_id = id).order_by('feeding_time')
                serializer = HorseNutritionSerializer(queryset, many = True)             
                return Response(serializer.data)
            else:
                return Response(status = status.HTTP_400_BAD_REQUEST)
    

    【讨论】:

    • 这很有意义,但我不知道为什么它不起作用:(
    • @olczig 啊,我的错,查询集(当 many=True 时)由父序列化程序 ListSerializer 在内部序列化,因此我们需要覆盖它的 to_representation,我们将不得不使用它自定义ListSerializer 并将其设置为list_serializer_class。检查我的编辑。
    • 现在我只有喂食时间列表
    • @olczig 喂食时间列表?您应该得到一个 dictionary,其中包含馈送时间作为键和序列化 HorseNutrition 的列表作为值,因为这就是我们返回的内容。
    • @olczig 我从回答这个问题中学到了很多:),问题是ListSerializer.data 返回ReturnList(ret, serializer=self) 所以字典变成了一个列表。我也编辑了解决这个问题的答案(也许在视图中使用itertools.groupby 会更容易,但你可以通过这种方式学习新东西;))
    猜你喜欢
    • 2020-07-25
    • 2016-04-30
    • 1970-01-01
    • 2021-11-25
    • 2021-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多