Concurrency on clean/save admin page

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, 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?

Yes. Don’t use the admin for this type of business logic management.

See The Django admin site | Django documentation | Django

From that page:

If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.

Thanks, I guess this time I have to learn the hard way :sweat_smile: