Campos agregados (e outros anotados) nos serializadores do Django Rest Framework
Estou tentando descobrir a melhor maneira de adicionar campos anotados, como quaisquer campos agregados (calculados) aos serializadores DRF (modelo). Meu caso de uso é simplesmente uma situação em que um nó de extremidade retorna campos que NÃO são armazenados em um banco de dados, mas calculados a partir de um banco de dados.
Vejamos o seguinte exemplo:
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
saída JSON desejada:
[
{
"name": "Pete's Ice Cream",
"total_trucks": 20,
"total_capacity": 4000
},
...
]
Eu tenho algumas soluções que funcionam, mas cada uma tem alguns problemas.
Opção 1: adicione getters ao modelo e use 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')
O código acima talvez possa ser refatorado um pouco, mas não mudará o fato de que esta opção executará 2 consultas SQL adicionaispor IceCreamCompany o que não é muito eficiente.
Opção 2: anotar em ViewSet.get_queryset
models.py conforme descrito originalmente.
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')
)
Isso obterá os campos agregados em uma única consulta SQL, mas não tenho certeza de como os adicionaria ao serializador, pois o DRF não sabe magicamente que anotei esses campos no QuerySet. Se eu adicionar total_trucks e total_capacity ao serializador, ocorrerá um erro sobre esses campos não estarem presentes no modelo.
A opção 2 pode funcionar sem um serializador usando umVisão mas se o modelo contiver muitos campos e for necessário que apenas alguns estejam no JSON, seria um hack um pouco feio criar o terminal sem um serializador.