使用__getattr__更改Django模型的行为

时间:2010-09-21 06:59:28

标签: python django class

我正在尝试更改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

我做错了什么?

4 个答案:

答案 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,以确保您不会进入无限循环。