How to set value for serializers field in DRF
I have a web page in which I show some reports of trades between sellers and customers. So for this purpose, I need to create an API to get all of the trades from database, extract necessary data and serialize them to be useful in web page. So I do not think of creating any model and just return the data in JSON format.
First I created my serializers like this:
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
and then my apiView
look like this :
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)
the problem is in get_fieldname
methods that are not called so the response at the end consist of null or empty values :
{
"incomingTrades": {
"all_count": null,
"all_earnings": null,
"successful_count": null,
"successful_earnings": null
},
"tradesDistribution": {
"sellers": {}
}
}
I have already change the integerField
s and DictField
to MethodField
but the problem was not solved and the only change was that the fields got disappeared in response and the only things were two empty dictionary for serializers.
Another way I tried was overriding to_representation
method but it was the same as before (of course my manager told me that this method is not good in performance if the number of fields increase).
What is the problem?
Do I need to change my approach or do something like overriding another method or something else?
What is the standard way for this scenario?
You should use SerializerMethodField
and also stick to Django ORM to fetch data instead of iterating over it:
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
response
{
"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 Check the aggregation
docs on how to group_by
which can be a little bit tricky.