Django中同时使用StringAgg与条件注解函数时触发'WhenNode' object has no attribute 'copy'错误
解决Django中StringAgg与条件注解共存时的'WhenNode' object has no attribute 'copy'错误
这问题我之前在项目里踩过坑!本质是Django在处理聚合函数(比如StringAgg)和基于Q对象的条件注解时,查询构造器的内部逻辑冲突导致的异常。单独用其中一类注解都没问题,但混在一起就触发了这个bug。
问题原因
当你在同一个annotate()调用里同时加入StringAgg(聚合操作,会影响分组逻辑)和ExpressionWrapper(Q(...))(行级条件注解)时,Django的查询构建器在生成SQL的过程中,错误地将内部的WhenNode对象当作需要复制的对象,但该对象并没有copy方法,从而抛出这个错误。
解决方案
方法1:拆分annotate步骤
把聚合注解和条件注解分成两个独立的annotate()调用,让Django先处理聚合逻辑,再处理行级条件注解,避免逻辑冲突:
users = ( account.get_users() # 第一步:先处理聚合操作 .annotate( sites=StringAgg('usersite__site__name', delimiter="|"), ) # 第二步:再添加行级条件和格式化注解 .annotate( is_admin=ExpressionWrapper(Q(role=1), output_field=BooleanField()), has_flag=ExpressionWrapper(Q(thing__isnull=False), output_field=BooleanField()), created_date=Func(F("created_at"), Value("YYYY-MM-DD"), function="to_char", output_field=CharField()), ) .values_list( "email", "is_admin", "has_flag", # 注意:你之前写的是has_thing,这里是笔误,要和注解名一致 "created_date", "sites", ) )
方法2:替换ExpressionWrapper为Case/When
如果拆分annotate还是不行,你可以把基于Q对象的ExpressionWrapper替换成更明确的Case+When语法,这能避免生成有问题的WhenNode对象:
from django.db.models import Case, When, Value users = ( account.get_users() .annotate( is_admin=Case( When(role=1, then=Value(True)), default=Value(False), output_field=BooleanField() ), has_flag=Case( When(thing__isnull=False, then=Value(True)), default=Value(False), output_field=BooleanField() ), created_date=Func(F("created_at"), Value("YYYY-MM-DD"), function="to_char", output_field=CharField()), sites=StringAgg('usersite__site__name', delimiter="|"), ) .values_list( "email", "is_admin", "has_flag", "created_date", "sites", ) )
方法3:升级Django版本
这个bug在较旧的Django版本(比如3.0以下)中更容易出现,如果你用的是老版本,升级到最新的稳定版(比如4.x系列)可能直接解决问题,因为官方已经修复了不少查询构造器的内部逻辑问题。
额外提示
你代码里的values_list中有个笔误:"has_thing"应该改成"has_flag",不然会抛出字段不存在的错误,记得修正哦!
内容的提问来源于stack exchange,提问作者earthtone0ne
相关产品推荐
相关产品推荐

