Как использовать функцию Django Queryset update() с полем внешнего ключа, приведенным с помощью F()?

Я хочу сделать простой запрос:

with transaction.atomic():
    Account.objects.select_for_update().filter(payments__status='PENDING', payments__created_date__lt=timezone.now()-timedelta(days=14)).update(balance=F('balance')+Sum(F('payments__amount') - F('payments__payment_fee__fee_amount')), payments__status='COMPLETED')

Оказалось, что это совсем не просто. Я попробовал Subquery, но в качестве альтернативы получил "Joined field references are not allowed in this query" или "FOR UPDATE is not allowed with GROUP BY clause"

Subquery не работает, потому что агрегация не работает с блокировками select_for_update(). F() не работает, потому что ссылается на поля внешнего ключа...

Каким образом лучше всего заставить этот запрос работать? Есть идеи?

  • Я использую postgresql за ORM.

Вот как я сделал:

with transaction.atomic():
    payments_due_payout = models.Payment.objects.filter(status='PENDING', created_date__lt=timezone.now()-timedelta(days=15))
    total_payout = Subquery(models.Account.objects.filter(payments__in=payments_due_payout, id=OuterRef('id')).annotate(total_payout=Sum(F('payments__amount') - F('payments__payment_fee__fee_amount'))).values('total_payout')[:1])
    models.Account.objects.select_for_update().filter(payments__in=payments_due_payout).update(balance=F('balance')+total_payout)
    payments_due_payout.update(status='COMPLETED')

Обращение к полям внешнего ключа с F в функции update() можно решить с помощью подзапроса. Но вы не можете агрегировать строки, заблокированные select_for_update().

Я решил положиться на "payments_due_payout", который невозможно модифицировать со стороны клиента из timezone.now()-timedelta(days=14), и я получаю Payment created_date__lt=timezone.now()-timedelta(days=15), что составляет разницу в один день. Я не думаю, что любой одновременный запрос к payments_due_payout будет длиться на сервере Django в течение 1 дня.

Так что проблем быть не должно, но в идеальном мире мы должны полностью предотвратить возможность изменения данных status='PENDING', программно.

Если у вас есть идеи получше, пожалуйста, дайте мне знать.

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