Запрос для расчета баланса пользователей
В моем проекте есть монеты, которые пользователи могут покупать, использовать для выполнения определенных действий и конвертировать в реальные деньги. Пользователь может конвертировать в реальные деньги только заработанные монеты (например, получая подарки), но не купленные непосредственно на сайте. Поэтому внутри системы рассчитываются два баланса: купленные и заработанные монеты (общий баланс - это сумма этих двух балансов). Следующая модель содержит операции с монетами (она упрощена):
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']
Не проверял синтаксис. Дайте мне знать, если возникнут какие-либо проблемы.