Запрос для расчета баланса пользователей

В моем проекте есть монеты, которые пользователи могут покупать, использовать для выполнения определенных действий и конвертировать в реальные деньги. Пользователь может конвертировать в реальные деньги только заработанные монеты (например, получая подарки), но не купленные непосредственно на сайте. Поэтому внутри системы рассчитываются два баланса: купленные и заработанные монеты (общий баланс - это сумма этих двух балансов). Следующая модель содержит операции с монетами (она упрощена):

class CoinTransaction(models.Model):
    class TransactionTypes(Enum):
        purchase_of_coins = ('pu', 'Purchase of Coins')  # Will have a positive amount
        conversion_into_money = ('co', 'Conversion Into Money')  # Will have a negative amount
        earning = ('ea', 'Earning')  # Will have a positive amount
        expense = ('ex', 'Expense')  # Will have a negative amount

        @classmethod
        def get_value(cls, member):
            return cls[member].value[0]

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='coin_transactions')
    amount = models.IntegerField()
    transaction_type = models.CharField(max_length=2, choices=[x.value for x in TransactionTypes])

Баланс заработанных монет увеличивается за счет всех доходов, кроме покупок монет, а баланс купленных монет увеличивается только за счет покупок монет. Монеты от операций конвертации_в_деньги вычитаются из баланса заработанных монет, а все остальные расходы сначала вычитаются из баланса купленных монет, если он больше 0. Обратите внимание, что оба баланса не могут опускаться ниже 0. Чтобы вычислить общее количество монет пользователя, я могу просто написать:

CoinTransaction.objects.filter(user=user).aggregate(Sum('amount'))['amount__sum'] or 0

Мой вопрос заключается в том, как написать запрос для получения баланса заработанных монет. Другое решение, не связанное с запросами, заключается в отслеживании баланса в модели User и обновлении его через сигнал:

class User(AbstractUser):
   # Other fields
   bought_coins_balance = models.PositiveIntegerField()
   earned_coins_balance = models.PositiveIntegerField()

   @property
   def total_coins(self):
      return self.bought_coins_balance + self.earned_coins_balance
@receiver(post_save, sender=CoinTransaction)
def update_balances(sender, instance, created, **kwargs):
    if created:
        user = instance.user
        amount = instance.amount

        if instance.transaction_type in [CoinTransaction.TransactionTypes.get_value('purchase_of_coins'), CoinTransaction.TransactionTypes.get_value('expense')]:
           user.bought_coins_balance += amount
           if user.bought_coins_balance < 0:
              user.earned_coins_balance = max(user.earned_coins_balance + user.bought_coins_balance, 0)
              user.bought_coins_balance = 0
        else:
           user.earned_coins_balance = max(user.earned_coins_balance + amount, 0)
        
        user.save()

Код не оптимизирован (я знаю, что он подвержен условиям гонки) и приведен только для того, чтобы прояснить мою мысль (вы можете проанализировать update_balances, чтобы понять, как я хочу вычислять балансы). Я хотел бы использовать запрос, который получает баланс заработанных монет по запросу, не сохраняя значение в базе данных.

Это пример одного запроса, который за один раз получает числа купленных и заработанных монет. Если вам нужен только один из них, его можно упростить, используя фильтры в наборе запросов. Кроме того, для общего количества монет фильтры действительно не нужны, но я оставляю их здесь в демонстрационных целях.

class User(AbstractUser):

   @property
   def total_coins(self):
      amount = self.coin_transactions.aggregate(
          coins_bought=Sum(
              'amount', 
              filter=Q(transaction_type__in=['pu'])
          ), 
          coins_earned=Sum(
              'amount', 
              filter=Q(transaction_type__in=['co', 'ea', 'ex'])
          )
      )

      return amount['coins_bought'] + amount['coins_earned']

Не проверял синтаксис. Дайте мне знать, если возникнут какие-либо проблемы.

Вернуться на верх