Как установить значение для поля сериализаторов в DRF
У меня есть веб-страница, на которой я показываю некоторые отчеты о сделках между продавцами и покупателями. Поэтому для этой цели мне нужно создать API, который будет получать все сделки из базы данных, извлекать необходимые данные и сериализовывать их для использования на веб-странице. Поэтому я не думаю о создании какой-либо модели и просто возвращаю данные в формате JSON.
Сначала я создал свои сериализаторы следующим образом:
from rest_framework import serializers
from django.db.models import Sum, Count
from account.models import User
class IncomingTradesSerializer(serializers.Serializer):
all_count = serializers.IntegerField()
all_earnings = serializers.IntegerField()
successful_count = serializers.IntegerField()
successful_earnings = serializers.IntegerField()
def __init__(self, *args, **kwargs):
self.trades = kwargs.pop('trades', None)
super().__init__(*args, **kwargs)
def get_all_count(self, obj):
return self.trades.count()
def get_all_earnings(self, obj):
return sum(trade.trade_price for trade in self.trades)
def get_successful_count(self, obj):
return self.trades.exclude(failure_reason=None).count()
def get_successful_earnings(self, obj):
return sum(trade.trade_price for trade in self.trades.exclude(failure_reason=None))
class TradesDistributionSerializer(serializers.Serializer):
sellers = serializers.DictField()
def __init__(self, *args, **kwargs):
self.trades = kwargs.pop('trades', None)
super().__init__(*args, **kwargs)
def get_sellers(self, obj):
sellers = {}
for user in User.objects.all():
distributed_trades = self.trades.filter(creator=user)
sellers[user.username] = sum(
trade.trade_price for trade in distributed_trades)
return sellers
и тогда мои apiView
выглядят так :
from rest_framework.views import APIView
from rest_framework.response import Response
from trade.models import Trade
from report.serializers import IncomingTradesSerializer, TradesDistributionSerializer
class IndicatorView(APIView):
def get(self, request):
trades = Trade.objects.all()
incoming_trades_serializer = IncomingTradesSerializer(trades=trades)
trades_distribution_serializer = TradesDistributionSerializer(trades=trades)
results = {
'incomingTrades': incoming_trades_serializer.data,
'tradesDistribution': trades_distribution_serializer.data
}
return Response(results)
проблема заключается в get_fieldname
методах, которые не вызываются, поэтому ответ в конце состоит из нулевых или пустых значений :
{
"incomingTrades": {
"all_count": null,
"all_earnings": null,
"successful_count": null,
"successful_earnings": null
},
"tradesDistribution": {
"sellers": {}
}
}
Я уже менял integerField
s и DictField
на MethodField
, но проблема не была решена и единственным изменением было то, что поля исчезли в ответе и остались только два пустых словаря для сериализаторов.
Другой способ, который я пробовал, - переопределение метода to_representation
, но все было так же, как и раньше (конечно, мой менеджер сказал мне, что этот метод не очень хорош с точки зрения производительности, если количество полей увеличивается).
В чем проблема?
Нужно ли мне изменить подход или сделать что-то вроде переопределения другого метода или что-то еще?
Каков стандартный путь для этого сценария?
Вам следует использовать SerializerMethodField
, а также придерживаться Django ORM для получения данных вместо итераций над ними:
views.py
class IndicatorView(APIView):
def get(self, request):
serializer = IndicatorSerializer(Trade.objects.all())
return Response(serializer.data)
serializers.py
class IndicatorSerializer(serializers.Serializer):
incoming_trades = serializers.SerializerMethodField()
trades_distribution = serializers.SerializerMethodField()
def get_incoming_trades(self, trades):
"""
If there is a reason for failure then .exclude(failure_reason=None)
would yield UNSUCCESFULL trades.
Thus, if you want successfull ones, that would be:
.filter(failure_reason=None).count()
"""
incoming_trades = {
'all_count': trades.count(),
'all_earnings': trades.aggregate(total=Sum("trade_price"))['total'],
'successful_count': trades.filter(failure_reason=None).count(),
'successful_earnings': (
trades.filter(failure_reason=None)
.aggregate(total=Sum("trade_price"))['total']),
'unsuccessful_count': trades.exclude(failure_reason=None).count(),
'unsuccessful_earnings': (
trades.exclude(failure_reason=None)
.aggregate(total=Sum("trade_price"))['total']),
}
return incoming_trades
def get_trades_distribution(self, trades):
"""
Note that just like your query
this does not distinguish successful / unsuccessful trades
Therefore, you should filter the QS if that is your intention.
"""
trades_distribution =(
trades.order_by("id")
.annotate(seller=F("creator__username"))
.values("seller")
.annotate(trades_total=Sum("trade_price"))
.order_by()
)
return trades_distribution
ответ
{
"incoming_trades": {
"all_count": 3,
"all_earnings": 733.76,
"successful_count": 2,
"successful_earnings": 165.87,
"unsuccessful_count": 1,
"unsuccessful_earnings": 567.89
},
"trades_distribution": [
{
"seller": "admin",
"trades_total": 691.34
},
{
"seller": "someuser",
"trades_total": 42.42
}
]
}
P.S Проверьте aggregation
документацию о том, как group_by
, которая может быть немного запутанной.