解决Django预取字段的Unresolved attribute reference警告及自定义报错
解决方案
1. 消除PyCharm属性未解析警告(最优方案)
警告产生的原因是PyCharm的静态检查无法识别Django运行时通过prefetch_related动态注入的small_tools属性,你之前改参数类型为list[Toolbox] | QuerySet属于绕过检查的取巧方案,推荐两种更规范的实现:
方案A:模型层添加静态类型注解
直接在Toolbox模型中添加仅静态检查生效的属性注解,完全不影响运行时逻辑:
from django.db import models from typing import Iterable, TYPE_CHECKING if TYPE_CHECKING: # 静态检查阶段才导入,避免循环导入问题 from .models import Tool class Toolbox(models.Model): name = models.CharField(max_length=255) tools = models.ManyToManyField("Tool") # 声明预取字段的类型,静态检查器会识别 if TYPE_CHECKING: small_tools: Iterable["Tool"]
该方案是最推荐的,对原有业务逻辑侵入最小,类型提示准确。
方案B:使用Protocol定义约束
如果不想修改模型定义,可以通过typing.Protocol声明入参需要满足的属性约束:
from typing import Protocol, Iterable from yourapp.models import Tool, Toolbox class ToolboxWithSmallTools(Protocol): """Protocol约束:必须存在small_tools属性的Toolbox实例""" small_tools: Iterable[Tool] # 其他用到的Toolbox原生属性也可以在这里补充声明 @queries_disabled() def get_toolbox_to_small_tools_mappings(toolboxes: list[ToolboxWithSmallTools]) -> dict: return {toolbox: toolbox.small_tools for toolbox in toolboxes}
该方案的优势是可以更明确地表达函数对入参的预取要求,可读性更强。
2. 自定义友好错误提示
有两种常用实现方式,按需选择即可:
方案A:重写模型的__getattr__方法
在Toolbox模型中重写__getattr__,捕获small_tools不存在的场景抛出指定错误:
class Toolbox(models.Model): name = models.CharField(max_length=255) tools = models.ManyToManyField("Tool") def __getattr__(self, item): if item == "small_tools": raise AttributeError( "'Toolbox' object has no attribute 'small_tools'. You must prefetch this field." ) # 其他属性走默认错误逻辑 super().__getattr__(item)
方案B:在业务函数中捕获重抛错误
如果不想修改模型,可以在当前函数中捕获默认的AttributeError,替换为自定义提示后重新抛出:
@queries_disabled() def get_toolbox_to_small_tools_mappings(toolboxes: list[Toolbox]) -> dict: result = {} for toolbox in toolboxes: try: small_tools = toolbox.small_tools except AttributeError as e: if "small_tools" in str(e): raise AttributeError( "'Toolbox' object has no attribute 'small_tools'. You must prefetch this field." ) from e raise result[toolbox] = small_tools return result
补充说明:你需要确保预取时已经正确指定了to_attr参数,示例预取写法如下:
from django.db.models import Prefetch toolboxes = Toolbox.objects.prefetch_related( Prefetch( "tools", queryset=Tool.objects.filter(size=Tool.Size.SMALL), to_attr="small_tools" ) )
内容的提问来源于stack exchange,提问作者Johnny Metz
相关产品推荐
相关产品推荐

