A simplified version of the problem is on a database with accounts, where each account can have income / expense. The income is created without major conflict, however when an expense is created, it must be seen that there is sufficient income in that account to cover the expense.
from django.db import models
from django.core.validators import MinValueValidator
from decimal import Decimal
class Account(models.Model):
number = models.CharField(max_length=24)
class AccountIncome(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=12, decimal_places=2)
class AccountExpenses(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=12, decimal_places=2)
I make a interface to create expenses in the admin page but the problem is that i have a custom form that define this constraint in a clean method, like this:
class AccountExpensesForm(forms.ModelForm):
...
def clean_amount(self):
amount_available = self.account.aggregate(res=Sum('incomes')-Sum("expenses")) ['res']
if self.cleaned_data['amount'] > amount_available:
raise ValidationError('....')
....
This work great when only one person is generated expenses, but it is broken when many users decided to generate expenses for the same account in the same time, my question is, what is the correct way of validate and create in one step through the django form abstraction.
I have thought about removing the clean method in the form, validate only the basic stuff in the form and add a concurrency control mechanism (like lock the rows in the AccountIncome and AccountExpenses table related with the current account) in the view, just after call form.save(commit=False)
, if exists some error in this step i add the error with `form.add_error(‘amount not available…’) in other case, save the AccountExpenses object before release the lock.
This has been my best solution for when I have control of the view, however on the admin page, the _changeform_view
method does not give as much freedom to make changes in the view without having to overwrite it, leading me to think about returning to the previous model where the validation was in a method of the form, however now it would be to implement it in the clean
method but adding that in this method the available quantity is validated and also creates the object and stores it in the form as a property, in such a way that just override save_form
and save_model
to return this property and ignore the normal flow of saving the object through the save method of the form.
Exists a better way to deal with this type of case?