Как блокировать строки при операциях чтения и записи?

У меня есть модель, которая подсчитывает, сколько операций было сделано пользователем. (Она упрощена для примера)

User = get_user_model()

class UserOperationCounter(models.Model):
    count = models.IntegerField(default=0)
    user = models.OneToOne(User)

У меня есть контекстный менеджер, который получает объект счетчика для пользователя и увеличивает параметр counter после выхода

@atomic()
class IncrementCounterContextManager:
    def __init__(user_id):
        self.operation_counter = UserOperationCounter.objects.filter(user_id=user_id).select_for_update().get()
    def __enter__():
        print(f"Fetch counter={operation_counter.counter} for user={operation_counter.user.id}")
        return self.operation_counter
    def __exit__():
        self.operation_counter.count = F("count") + 1
        self.operation_counter.save(update_fields=["count"])

        self.operation_counter.refresh_from_db()
        print(f"Set counter={operation_counter.counter} for user={operation_counter.user.id}")

Допустим, я хочу использовать контекстный менеджер каждый раз, когда пользователь выполняет какое-то определенное действие на платформе, а затем я хочу отправить ему уведомление с сообщением: "You have done <counter> hidden actions on our platform".

select_for_update() прекрасно работает в сценариях, когда я хочу одновременно обновить объект Counter, но строка не заблокирована для чтения.

Вот пример:

def send_notification(counter, user_id):
    print(f"Send notification with counter={counter} to user={user_id}")
    

user_id = 1
with atomic():
    with IncrementCounterContextManager(user_id=1) as usr_counter1:
         send_notification(usr_counter1.counter, usr_counter1.user.id)
         with IncrementCounterContextManager(user_id=1) as usr_counter2:
             send_notification(usr_counter2.counter, usr_counter2.user.id)

Когда я проверяю журналы, я вижу что-то вроде:

Fetch counter=0 for user=1
Fetch counter=0 for user=1
Send notification with counter=0 to user=1
Send notification with counter=0 to user=1
Set counter=1 for user=1
Set counter=2 for user=1

Я хотел бы узнать, как я могу заблокировать строку для чтения, чтобы получить только последнее значение после оценки объекта? Может быть, я могу как-то узнать, что объект нужно обновить?

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