Подтвердить что ты не робот

Агрегатные (и другие аннотированные) поля в сериализаторах Django Rest Framework

Я пытаюсь найти лучший способ добавления аннотированных полей, таких как любые агрегированные (рассчитанные) поля в Serializers DRF (Model). Мой вариант использования - это просто ситуация, когда конечная точка возвращает поля, которые НЕ хранятся в базе данных, а вычисляются из базы данных.

Посмотрим на следующий пример:

models.py

class IceCreamCompany(models.Model):
    name = models.CharField(primary_key = True, max_length = 255)

class IceCreamTruck(models.Model):
    company = models.ForeignKey('IceCreamCompany', related_name='trucks')
    capacity = models.IntegerField()

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):
    class Meta:
        model = IceCreamCompany

желаемый вывод JSON:

[

    {
        "name": "Pete Ice Cream",
        "total_trucks": 20,
        "total_capacity": 4000
    },
    ...
]

У меня есть несколько решений, которые работают, но у каждого есть некоторые проблемы.

Вариант 1: добавьте геттеры для моделирования и использования SerializerMethodFields

models.py

class IceCreamCompany(models.Model):
    name = models.CharField(primary_key=True, max_length=255)

    def get_total_trucks(self):
        return self.trucks.count()

    def get_total_capacity(self):
        return self.trucks.aggregate(Sum('capacity'))['capacity__sum']

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):

    def get_total_trucks(self, obj):
        return obj.get_total_trucks

    def get_total_capacity(self, obj):
        return obj.get_total_capacity

    total_trucks = SerializerMethodField()
    total_capacity = SerializerMethodField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')

Возможно, этот код может быть реорганизован немного, но это не изменит тот факт, что этот параметр будет выполнять 2 дополнительных SQL-запроса на IceCreamCompany, которые не очень эффективны.

Вариант 2: аннотировать в ViewSet.get_queryset

models.py, как описано ранее.

views.py

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.all()
    serializer_class = IceCreamCompanySerializer

    def get_queryset(self):
        return IceCreamCompany.objects.annotate(
            total_trucks = Count('trucks'),
            total_capacity = Sum('trucks__capacity')
        )

Это приведет к агрегированным полям в одном SQL-запросе, но я не уверен, как добавить их в Serializer, поскольку DRF не знает, что я аннотировал эти поля в QuerySet. Если я добавлю total_trucks и total_capacity в сериализатор, он выдает ошибку об этих полях, которые не присутствуют в модели.

Вариант 2 может быть выполнен без сериализатора с помощью View, но если модель содержит много полей, и только некоторые из них должны быть в JSON, это было бы несколько уродливым взломом для создания конечной точки без сериализатора.

4b9b3361

Ответ 1

Возможное решение:

views.py

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.all()
    serializer_class = IceCreamCompanySerializer

    def get_queryset(self):
        return IceCreamCompany.objects.annotate(
            total_trucks=Count('trucks'),
            total_capacity=Sum('trucks__capacity')
        )

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):
    total_trucks = serializers.IntegerField()
    total_capacity = serializers.IntegerField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')

Используя поля Serializer, я получил небольшой пример для работы. Поля должны быть объявлены как атрибуты класса сериализатора, поэтому DRF не будет вызывать ошибку о том, что они не существуют в модели IceCreamCompany.