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
.