Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to use foreign key field's attribute for another model field

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’

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

# 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?

>Solution :

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 FiatTransactions.

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 FiatTransactions 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()
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading