pythondjangodjango-rest-frameworkdjango-serializer

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 integerFields 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?


Solution

  • 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.