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)