Django Rest Framework, запросы group_by с использованием .values() и встраивание связанной записи

Я не могу понять, как встроить связанную запись в ответ Django Rest Framework при использовании .values() в наборе запросов Django для возврата сгруппированных и агрегированных значений. Образцы моделей, сериализаторов и набора представлений включены ниже вместе с текущим ответом и образцом желаемого ответа.

В каждой сделке участвует одна ценная бумага. Для одной ценной бумаги может существовать несколько сделок. У меня есть необходимость отображать сделки по отдельности, а также сгруппированные по безопасности. При группировке по безопасности я хочу вернуть в ответ встроенную безопасность вместе с суммарными значениями для связанных сделок, но пока я могу вернуть только идентификатор безопасности. Любая попытка получить саму ценную бумагу в ответе приводит к исключению 'int' object has no attribute 'pk'

Торговая модель

class Trade(models.Model):
    security = models.ForeignKey('Security')
    ticker = models.CharField(
        max_length=128,
        null=True,
        blank=True,
        db_index=True,
    )
    shares = models.FloatField(db_index=True)
    value_usd = models.FloatField(db_index=True)
    days_to_trade = models.FloatField(db_index=True)
    trade_date = models.DateField(
        db_index=True,
        null=True,
        blank=True,
    )

Модель безопасности

class Security(models.Model):
    name = models.CharField(
        db_index=True,
        blank=True,
        null=True,
    )
    ipo_date = models.DateField(
        null=True,
        blank=True
    )

Торговый взгляд

class TradeViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        // param and filter setup/defaults snipped for clarity
        ...
        qs = Trade.objects.filter(**qs_filter)
        // if group_by param is passed in, then group the trades by ticker/security and return aggregate sums for value, shares, and days to trade
        if group_by:
            qs = qs.values('ticker', 'security_id').annotate(value_usd=Sum('value_usd'), shares=Sum('shares'), days_to_trade=Sum('days_to_trade'), num_trades=Count('security')).order_by('ticker')
            self.serializer_class = TradeByTickerSerializer
        return qs

    serializer_class = TradeSerializer

Торговый сериализатор

class TradeSerializer(serializers.ModelSerializer):
    security = SecuritySerializer(many=False, read_only=True)
    class Meta:
        model = Trade
        fields = [
            'id',
            'security',
            'trade_date',
            'ticker',
            'shares',
            'value_usd',
            'days_to_trade',
        ]
        depth = 1

Сериализатор TradeByTicker

class TradeByTickerSerializer(serializers.ModelSerializer):
    num_trades = serializers.IntegerField(read_only=True)
    // security = SecuritySerializer(many=False, read_only=True) // this gives an exception 
    class Meta:
        model = ProjectedTrade
        fields = [
            // 'security', // I tried this but get an exception
            'security_id',
            'ticker',
            'shares',
            'value_usd',
            'days_to_trade',
            'num_trades',
        ]

Текущий выход

{
    "results": [
        {
            "securityId": 123,
            "ticker": "ABC-US",
            "shares": 790611.154356048,
            "valueUsd": 15598758.0754448,
            "daysToTrade": 7.2460672754406,
            "numTrades": 1
        },
        {
            "securityId": 456,
            "ticker": "DEF-US",
            "shares": 1116548.93406872,
            "valueUsd": 29041437.7751273,
            "daysToTrade": 6.6811609336384,
            "numTrades": 3
        },
        {
            "securityId": 789,
            "ticker": "HIJ-US",
            "shares": 979099.946772097,
            "valueUsd": 16360760.1105617,
            "daysToTrade": 6.50616625094424,
            "numTrades": 2
        },
        ...
]

Желаемый выход

{
    "results": [
        {
            "security": {
                "id": 123,
                "name": "Another Boring Company",
                "ipoDate": "1988-12-23"
            },
            "ticker": "ABC",
            "shares": 790611.154356048,
            "valueUsd": 15598758.0754448,
            "daysToTrade": 7.2460672754406,
            "numTrades": 1
        },
        {
            "security": {
                "id": 456,
                "name": "Def Jam Records",
                "ipoDate": "2001-04-24"
            },
            "ticker": "DEF",
            "shares": 1116548.93406872,
            "valueUsd": 29041437.7751273,
            "daysToTrade": 6.6811609336384,
            "numTrades": 3
        },
        {
            "security": {
                "id": 789,
                "name": "Hijinx Corp",
                "ipoDate": "1999-12-31"
            },
            "ticker": "HIJ",
            "shares": 979099.946772097,
            "valueUsd": 16360760.1105617,
            "daysToTrade": 6.50616625094424,
            "numTrades": 2
        },
        ...
]

Вы можете использовать SerializerMethodField для получения соответствующих данных безопасности следующим образом:

class TradeByTickerSerializer(serializers.ModelSerializer):
    num_trades = serializers.IntegerField(read_only=True)
    security = serializer.SerializerMethodField()

    class Meta:
        model = Trade
        fields = [
            'security',
            'security_id',
            'ticker',
            'shares',
            'value_usd',
            'days_to_trade',
            'num_trades',
        ]

    def get_security(self, obj)
        return SecuritySerializer(Security.objects.get(pk=obj['security_id'])).data

Поскольку кверисет больше не содержит Trade объектов, я бы предложил просто использовать Serializer (неModelSerializer):

class TradeByTickerSerializer(serializers.Serializer):
    security = serializers.SerializerMethodField()
    ticker = serializers.FloatField(read_only=True)
    shares = serializers.FloatField(read_only=True)
    value_usd = serializers.FloatField(read_only=True)
    days_to_trade = serializers.FloatField(read_only=True)
    num_trades = serializers.IntegerField(read_only=True)

    def get_security(self, obj)
        return SecuritySerializer(Security.objects.get(pk=obj['security_id'])).data

Обратите внимание, что при этом в базу данных будет попадать каждый результат набора запросов group by, только для получения связанного объекта Security.

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