🎉 Use select_for_update tag to update note balances when we save a Transaction to avoid concurrency issues

This commit is contained in:
Yohann D'ANELLO 2020-08-15 18:57:44 +02:00
parent 242b85676d
commit d4090a4043
1 changed files with 27 additions and 26 deletions

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models, transaction from django.db import models, transaction
from django.db.models import F
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -208,38 +209,38 @@ class Transaction(PolymorphicModel):
""" """
When saving, also transfer money between two notes When saving, also transfer money between two notes
""" """
with transaction.atomic(): if self.source.pk == self.destination.pk:
diff_source, diff_dest = self.validate() # When source == destination, no money is transferred and no transaction is created
return
if not self.source.is_active or not self.destination.is_active: # We refresh the notes with the "select for update" tag to avoid concurrency issues
if 'force_insert' not in kwargs or not kwargs['force_insert']: self.source = Note.objects.filter(pk=self.source_id).select_for_update().get()
if 'force_update' not in kwargs or not kwargs['force_update']: self.destination = Note.objects.filter(pk=self.destination_id).select_for_update().get()
raise ValidationError(_("The transaction can't be saved since the source note "
"or the destination note is not active."))
# If the aliases are not entered, we assume that the used alias is the name of the note # Check that the amounts stay between big integer bounds
if not self.source_alias: diff_source, diff_dest = self.validate()
self.source_alias = str(self.source)
if not self.destination_alias: if not self.source.is_active or not self.destination.is_active:
self.destination_alias = str(self.destination) if 'force_insert' not in kwargs or not kwargs['force_insert']:
if 'force_update' not in kwargs or not kwargs['force_update']:
raise ValidationError(_("The transaction can't be saved since the source note "
"or the destination note is not active."))
if self.source.pk == self.destination.pk: # If the aliases are not entered, we assume that the used alias is the name of the note
# When source == destination, no money is transferred and no transaction is created if not self.source_alias:
return self.source_alias = str(self.source)
# We save first the transaction, in case of the user has no right to transfer money if not self.destination_alias:
super().save(*args, **kwargs) self.destination_alias = str(self.destination)
# Save notes # We save first the transaction, in case of the user has no right to transfer money
self.source.refresh_from_db() super().save(*args, **kwargs)
self.source.balance += diff_source
self.source._force_save = True # Save notes
self.source.save() self.source.balance += diff_source
self.destination.refresh_from_db() self.source.save()
self.destination.balance += diff_dest self.destination.balance += diff_dest
self.destination._force_save = True self.destination.save()
self.destination.save()
def delete(self, **kwargs): def delete(self, **kwargs):
""" """