¿Cómo prevenir la condición de carrera en Django en INSERT con limitación SUM?

Considere dos modelos:

  • Proyecto :
    • id: una clave principal de AutoField
    • Presupuesto: Un campo de campo PositiveIntegerField
    • algunos campos no relacionados
  • Gastos
    • id: una clave principal de AutoField
    • cantidad: A PositiveIntegerField
    • proyecto: Un ForeignKey to Project
    • algunos campos no relacionados

Para cada proyecto, deseo garantizar que la sum de sus gastos sea menor o igual al presupuesto en todo momento.

En términos de SQL: SELECT SUM(amount) FROM expense WHERE project_id = ?; siempre debe ser menor o igual que el SELECT budget FROM project WHERE id = ?;

¿Hay alguna manera de hacer esto en Django, teniendo en cuenta que varias personas pueden estar accediendo al servidor web y creando Expense al mismo tiempo?

Estoy usando postgresql como mi base de datos backend.

He intentado usar select_for_update pero eso no impide que INSERT s tenga lugar y no parece funcionar en agregaciones de todos modos.

Primero estaba considerando save el Expense en la base de datos, luego consultar el costo total y eliminar el Expense si está por encima del presupuesto, pero luego necesitaría ese código fuera de una transacción para que otros subprocesos puedan verlo y luego si el el servidor se detiene a mitad del proceso, los datos podrían quedar en un estado no válido.

Gracias a @Alasdair por indicarme la dirección correcta.

Después de completar los campos de inst (un nuevo Expense ), haga:

 with transaction.atomic(): project = models.Project.objects.select_for_update().get( pk=project_id) cost = project.total_cost() budget = project.budget if cost + inst.cost > budget: raise forms.ValidationError(_('Over-budget')) self._inst.save() 

Tenga en cuenta que tengo total_cost definido como un método en el Project :

 class Project: def total_cost(self): return self.expense_set.all().aggregate( t=Sum(F('cost')))['t']