You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DRF使用.values()做group by查询时嵌入关联Security记录的问题

问题根因

你调用.values('ticker', 'security_id')执行分组后,查询集返回的每个元素都是字典结构,其中security_id为整数类型,并非Security模型实例,因此SecuritySerializer尝试读取实例pk属性时,就会抛出'int' object has no attribute 'pk'异常。

解决方案

这里提供两种可行方案,可根据业务场景选择:

方案1:从Security模型侧发起分组查询(推荐)

由于你的聚合逻辑本身就是按Security+Ticker分组,直接从Security侧反向关联Trade做聚合,无需调用.values(),可保留完整的模型实例结构,适配序列化器逻辑。

1. 修改视图分组分支代码

if group_by:
    # 反向关联Trade做聚合,返回Security实例+聚合字段
    qs = Security.objects.filter(trade__in=Trade.objects.filter(**qs_filter)).annotate(
        value_usd=Sum('trade__value_usd'),
        shares=Sum('trade__shares'),
        days_to_trade=Sum('trade__days_to_trade'),
        num_trades=Count('trade'),
        ticker=F('trade__ticker')
    ).distinct().order_by('ticker')
    self.serializer_class = TradeByTickerSerializer

2. 修改TradeByTickerSerializer

class TradeByTickerSerializer(serializers.ModelSerializer):
    num_trades = serializers.IntegerField(read_only=True)
    value_usd = serializers.FloatField(read_only=True)
    shares = serializers.FloatField(read_only=True)
    days_to_trade = serializers.FloatField(read_only=True)
    ticker = serializers.CharField(read_only=True)
    # 直接按期望格式封装security字段
    security = serializers.SerializerMethodField()
    
    def get_security(self, obj):
        return {
            "id": obj.id,
            "name": obj.name,
            "ipo_date": obj.ipo_date
        }
        
    class Meta:
        model = Security
        fields = [
            'security',
            'ticker',
            'shares',
            'value_usd',
            'days_to_trade',
            'num_trades',
        ]

方案2:保留原有Trade侧查询逻辑,手动映射Security对象

如果不想调整原有查询逻辑,可以用SerializerMethodField手动根据security_id查询关联的Security对象序列化,同时做批量查询优化避免N+1问题。

1. 修改视图分组分支代码

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')
    # 批量预查所有关联的Security对象,避免循环查询
    security_ids = [item['security_id'] for item in qs]
    security_map = {s.id: s for s in Security.objects.filter(id__in=security_ids)}
    self.serializer_class = TradeByTickerSerializer
    # 将预查的Security映射表传入序列化器上下文
    self.get_serializer_context().update({'security_map': security_map})

2. 修改TradeByTickerSerializer

class TradeByTickerSerializer(serializers.ModelSerializer):
    num_trades = serializers.IntegerField(read_only=True)
    security = serializers.SerializerMethodField()
    
    def get_security(self, obj):
        security_map = self.context.get('security_map', {})
        security = security_map.get(obj['security_id'])
        return SecuritySerializer(security).data if security else None
    
    class Meta:
        model = Trade
        fields = [
            'security',
            'ticker',
            'shares',
            'value_usd',
            'days_to_trade',
            'num_trades',
        ]

内容的提问来源于stack exchange,提问作者jmcopeland

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 01:06:03