Я пытаюсь найти лучший способ добавления аннотированных полей, таких как любые агрегированные (рассчитанные) поля в 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, это было бы несколько уродливым взломом для создания конечной точки без сериализатора.