Как использовать атрибут поля внешнего ключа для другого поля модели
У меня есть две модели в разных приложениях, например:
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}"
Теперь поле currency
модели FiatTransaction
, похоже, работает не так, как я задумал. Оно поднимает
AttributeError: объект 'ForeignKey' не имеет атрибута '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
Почему это так и как заставить это работать?
Вы можете сделать @property
, который будет определять валюту этого объекта с помощью:
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
Однако это может быть неэффективно, если вам приходится делать это для большого количества FiatTransaction
s.
В этом случае лучше удалить свойство currency
и аннотировать QuerySet
с помощью:
from django.db.models import F
FiatTransaction.objects.annotate(currency=F('debit_account__currency'))
Возникающие при этом FiatTransaction
будут иметь дополнительный атрибут .currency
, который будет содержать .currency
из .debit_account
.
Если вам это нужно часто, вы можете воспользоваться Manager
, который будет автоматически аннотировать при обращении к 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()