Changing the behaviour of a Django model with __getattr__

class, django, python

Solution

Consider the code inside your `__getattr__` method:

value = getattr(self.product, name, None)

Try guessing what happens when `self.product` is invoked. I'll give you a clue: it involves a call to `__getattr__`. The documentation has the details:

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.

Have you wondered how `self.product` resolves to the correct `Product` instance, even though you are not setting it anywhere?

Note that if the attribute is found through the normal mechanism, `__getattr__()` is not called.

Django does some magic that involves intercepting, you guessed it, `__getattr__`. Thereby `self` automatically ends up with an attribute `product`. Since you are overriding the `__getattr__` method, Django's magic ceases to work and your version is used. Since `self.product` is not an instance attribute, `__getattr__` is called again, and again and so on, leading to an infinite loop.

You'd be better off using a `property` to achieve this.

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)

Problem

I'm trying to change the behaviour of a Django model to allow me to access a foreign key's properties directly from the parent, e.g. ``` cache.part_number vs cache.product.part_number ``` I've tried overriding the `__getattr__` method as follows, but I get a recursion error when I try to access the foreign key's properties ``` 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 ``` What am I doing wrong?

Original source

Related problems