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
相关产品推荐
相关产品推荐

