I had a similiar need and i have solved it for me so i thought i would share. Here is the solution converted to your example:
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')
If you were calling this many times per instance you could also potentially make it a little more efficient by building the list once and storing locally on first call.
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')
Then i tested as follows:
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
The reason this works is that you are only calling getattr for items which are known to exist ensuring that you will not enter an infinite loop.