DJango ORM двойное соединение с Sum
Я искал подобный случай на SO и Google, но безуспешно.
КРАТКОЕ ОБЪЯСНЕНИЕ
У меня есть транзакции, которые принадлежат счету, а счет принадлежит объединению счетов. Я хочу получить список групп счетов с их счетами, и я хочу знать общий баланс каждого счета (баланс счета рассчитывается путем сложения суммы всех его транзакций).
ДЛИННОЕ ОБЪЯСНЕНИЕ
У меня есть следующие модели (для полноты картины я включаю миксины):
class UniqueNameMixin(models.Model):
class Meta:
abstract = True
name = models.CharField(verbose_name=_('name'), max_length=100, unique=True)
def __str__(self):
return self.name
class PercentageMixin(UniqueNameMixin):
class Meta:
abstract = True
_validators = [MinValueValidator(0), MaxValueValidator(100)]
current_percentage = models.DecimalField(max_digits=5,
decimal_places=2,
validators=_validators,
null=True,
blank=True)
ideal_percentage = models.DecimalField(max_digits=5,
decimal_places=2,
validators=_validators,
null=True,
blank=True)
class AccountsAggrupation(PercentageMixin):
pass
class Account(PercentageMixin):
aggrupation = models.ForeignKey(AccountsAggrupation, models.PROTECT)
class Transaction(models.Model):
date = models.DateField()
concept = models.ForeignKey(Concept, models.PROTECT, blank=True, null=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
account = models.ForeignKey(Account, models.PROTECT)
detail = models.CharField(max_length=100, blank=True, null=True)
def __str__(self):
return '{} - {} - {} - {}'.format(self.date, self.concept, self.amount, self.account)
Я хочу иметь возможность сделать это в Django ORM:
select ca.*, ca2.*, sum(ct.amount)
from core_accountsaggrupation ca
join core_account ca2 on ca2.aggrupation_id = ca.id
join core_transaction ct on ct.account_id = ca2.id
group by ca2.name
order by ca.name;
Похоже, что вложенная навигация по множествам невозможна:
Неправильно: AccountsAggrupation.objects.prefetch_related('account_set__transaction_set')
(или любой аналогичный подход). Работать с этим можно следующим образом: перейти от транзакции к счету, а затем к account_aggroupation.
Но поскольку мне нужно было иметь дикту с account_aggroupation, указывающую каждому ключу на его набор счетов (и баланс для каждого), я в итоге сделал так:
def get_accounts_aggrupations_data(self):
accounts_aggrupations_data = {}
accounts_balances = Account.objects.annotate(balance=Sum('transaction__amount'))
for aggrupation in self.queryset:
aggrupations_accounts = accounts_balances.filter(aggrupation__id=aggrupation.id)
aggrupation.balance = aggrupations_accounts.aggregate(Sum('balance'))['balance__sum']
accounts_aggrupations_data[aggrupation] = aggrupations_accounts
current_month = datetime.today().replace(day=1).date()
date = current_month.strftime('%B %Y')
total_balance = Transaction.objects.aggregate(Sum('amount'))['amount__sum']
return {'balances': accounts_aggrupations_data, 'date': date, 'total_balance': total_balance}
Обратите внимание, что поскольку я перебираю accounts_aggrupations, этот запрос (self.queryset, который приводит к AccountsAggrupation.objects.all()) выполняется к БД.
Остальные запросы, которые я делаю, пока не выполняются, потому что я не итерирую их (пока не потребляю информацию в шаблоне).
Также обратите внимание, что словарь accounts_aggrupations_data
имеет в качестве ключа объект accounts_aggrupation.