I have two models in different apps like so:
class Account(models.Model):
"""
Class to store fiat account information of a companies bank account
"""
number = models.CharField(max_length=100)
currency = models.ForeignKey(FiatCurrency, on_delete=models.CASCADE)
owner = models.ForeignKey(Company, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.number
class FiatTransaction(models.Model):
"""
Class to store Transactions made between escrow and operative white-listed fiat accounts
"""
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
currency = debit_account.currency
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
def __str__(self):
return F"Transferred {self.amount} from {self.debit_account} to {self.credit_account} at {self.executed_on}"
Now the field currency
of model FiatTransaction
doesn't seem to work the way I intend it to do. It raises
AttributeError: 'ForeignKey' object has no attribute 'currency'
# Source model
class FiatCurrency(models.Model):
"""
A model to store Fiat Currencies offered by Finchin to
include into cash-pools.
"""
ISO_Code = models.CharField(max_length=3)
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
Why's that and how to make this work?
CodePudding user response:
You can make a @property
that will determine the currency of that object with:
class FiatTransaction(models.Model):
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
@property
def currency(self):
return self.debit_account.currency
This can however be inefficient if you have to do this for a lot of FiatTransaction
s.
In that case it might be better to remove the currency
property, and annotate the QuerySet
with:
from django.db.models import F
FiatTransaction.objects.annotate(currency=F('debit_account__currency'))
The FiatTransaction
s that arise from this will have an extra attribute named .currency
that will contain the .currency
of the .debit_account
.
If you need this often, you can make use of a Manager
that will automatically annotate when you access FiatTransaction.objects
:
from django.db.models import F
class FiatTransactionManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).annotate(
currency=F('debit_account__currency')
)
class FiatTransaction(models.Model):
# …
objects = FiatTransactionManager()