我正在尝试更改Django模型的行为,以允许我直接从父级访问外键的属性,例如
cache.part_number
vs
cache.product.part_number
我尝试重写__getattr__
方法,如下所示,但是当我尝试访问外键的属性时出现递归错误
class Product(models.Model):
part_number = models.CharField(max_length=10)
...
class Cache(models.Model):
product = models.ForeignKey(Product)
...
def __getattr__(self, name):
value = getattr(self.product, name, None)
if value:
return value
else:
raise AttributeError
我做错了什么?
答案 0 :(得分:9)
考虑__getattr__
方法中的代码:
value = getattr(self.product, name, None)
尝试猜测调用self.product
时会发生什么。我会给你一个线索:它涉及到__getattr__
的电话。 documentation有详细信息:
当属性查找未在通常位置找到属性时调用(即,它不是实例属性,也不是在类树中找到自己)。 name是属性名称。此方法应返回(计算的)属性值或引发AttributeError异常。
您是否想知道self.product
如何解析为正确的Product
实例,即使您没有将其设置在任何位置?
请注意,如果通过常规机制找到属性,则不会调用
__getattr__()
。
Django做了一些涉及拦截的魔法,你猜对了,__getattr__
。因此,self
会自动以属性product
结束。由于你重写了__getattr__
方法,Django的魔法停止工作并使用你的版本。由于self.product
不是实例属性,因此再次调用__getattr__
,再次调用,依此类推,从而导致无限循环。
最好使用property
来实现这一目标。
class Cache(models.Model):
product = models.ForeignKey(Product)
...
def _get_part_number(self):
part_number = self.product.part_number
if not part_number:
raise AttributeError
return part_number
part_number = property(_get_part_number)
答案 1 :(得分:1)
我遇到了与此处发布的问题类似的问题,当我查看Python访问对象属性的方式时,我得到了答案。
据我所知,当调用getattr()时,Python首先调用getattribute(),如果getattribute找不到该属性,那么python将使用你的getattr函数。
我试图远离在我的函数中使用getattr,因为它会导致无限递归,请参阅:https://stackoverflow.com/a/3278104/2319915
这样:
class Product(models.Model):
part_number = models.CharField(max_length=10)
class Cache(models.Model):
product = models.ForeignKey(Product)
def __getattr__(self, name):
try:
return getattribute(self, name)
except AttributeError:
try:
return Product.objects.get(part_no=self.product.part_no)
except ObjectDoesNotExist:
raise AttributeError
答案 2 :(得分:0)
如下:
class Product(models.Model):
part_number = models.CharField(max_length=10)
...
class Cache(models.Model):
product = models.ForeignKey(Product)
...
def __getattr__(self, name):
prefix = 'product_'
# Only deal with get_ calls
if not name.startswith(prefix):
raise AttributeError
else:
name = name.replace(prefix,'')
value = getattr(self.product, name, None)
if value:
return value
else:
raise AttributeError
然后你可以打电话:
cache.product_part_number
答案 3 :(得分:0)
我有类似的需求,我已经为我解决了,所以我想我会分享。这是转换为您的示例的解决方案:
class Product(models.Model):
part_number = models.CharField(max_length=10)
test_value1 = models.CharField(max_length=20)
test_value2 = models.CharField(max_length=20)
class Cache(models.Model):
name = models.CharField(max_length=30)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
def __getattr__(self, name):
if name in [f.name for f in Product._meta.get_fields()]:
return getattr(self.product, name, None)
else:
raise AttributeError('Item not found')
如果您每个实例调用多次,您还可以通过一次构建列表并在首次调用时将其存储在本地来使其效率更高。
class Product(models.Model):
part_number = models.CharField(max_length=10)
test_value1 = models.CharField(max_length=20)
test_value2 = models.CharField(max_length=20)
class Cache(models.Model):
name = models.CharField(max_length=30)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
def __getattr__(self, name):
if name == 'prod_attrlist':
self.__setattr__('prod_attrlist', set([f.name for f in Product._meta.get_fields()]))
return self.prod_attrlist
elif name in self.prod_attrlist:
return getattr(self.product, name, None)
else:
raise AttributeError('Item not found')
然后我进行了如下测试:
newitem = Product(part_number='123', test_value1='456', test_value2='789')
newitem.save()
newitem2 = Cache(name='testcache', product=newitem)
newitem2.save()
item = Cache.objects.get(name='testcache')
print(item.part_number) #123
print(item.test_value1) #456
print(item.doesntexist) #AttributeError: Item not found
之所以可行,是因为您只对已知存在的项目调用getattr,以确保您不会进入无限循环。