Как установить значение для поля сериализаторов в 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": {}
  }
}

Я уже менял integerFields и 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, которая может быть немного запутанной.

Вернуться на верх