#django #django-models #django-forms
#django #django-models #django-forms
Вопрос:
Итак, у меня есть модель транзакции, которая является FK-d для общего ресурса. В представлении «Учетная запись» у меня есть ModelFormset этих транзакций, и я могу сохранить несколько транзакций, прокручивая формы и сохраняя их.
С помощью метода save () моей транзакции я пытаюсь обновить баланс на связанном ресурсе. это работает, если я сохраняю одну транзакцию, но когда я ПУБЛИКУЮ свой ModelFormset с несколькими транзакциями, каждый раз, когда я нажимаю на строку self.share.balance = self.share.balance amt в переопределении сохранения транзакции () (то есть для каждой новой транзакции), share.balance является тем, что было до сохранения любой из предыдущих транзакций в наборе форм.
Кто-нибудь знает, почему добавленная сумма к общему балансу из предыдущей сохраненной транзакции не переносится при последующих сохранениях (почему к общему балансу будет добавлена только сумма последней транзакции)?
Модель транзакции, которая должна обновлять баланс на общей базе данных родительской модели
class Transaction(models.Model):
share = models.ForeignKey(Share, on_delete=models.CASCADE, null=True, blank=True)
account = models.ForeignKey(Account, on_delete=models.CASCADE, null=True, blank=True)
db_cr = models.CharField(choices=DBCR, max_length=2)
amt = models.DecimalField('Amount', max_digits=11, decimal_places=2)
post_dt = models.DateTimeField('Post Time', null=True, blank=True)
def save(self, *args, **kwargs):
if not self.pk:
...
if self.share:
if self in self.share.transaction_set.all():
logging.error('Transaction %s already posted' % self.id)
return False
amt = self.amt if self.db_cr == 'cr' else -self.amt
self.share.balance = self.share.balance amt
self.share.save()
Поделиться моделью
class Share(models.Model):
name = models.CharField(max_length=80)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
definition = models.ForeignKey(ShareDef, on_delete=models.PROTECT)
balance = models.DecimalField('Balance', max_digits=11, decimal_places=2, default=0)
def __str__(self):
return '%s %s %s %s'%(self.account,
self.name,
self.definition.sym_code,
self.balance )
def save(self, *args, **kwargs):
if not self.pk:
if not self.name:
self.name = self.definition.name
super(Share, self).save(*args, **kwargs)
На мой взгляд, у меня есть набор форм транзакции
#...in view
TranFormSet = modelformset_factory(Transaction, exclude=('origin','ach_entry'), extra=1)
if request.method=='POST':
...
tran_formset = TranFormSet(request.POST)
...
if tran_formset.is_valid():
for form in tran_formset:
tran = form.save(commit=False)
tran.account = account
tran.origin = 'tt'
tran.save()
else:
#...following kind of weird because of how I'm setting querysets of ModelChoiceFields
kwargs = {'account_instance': account}
tran_formset = TranFormSet(queryset=Transaction.objects.none())
tran_formset.form = (curry(TranForm, **kwargs))
Форма
class TranForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
account_instance = kwargs.pop('account_instance', None)
super(TranForm, self).__init__(*args, **kwargs)
if account_instance:
self.fields['share'].queryset = account_instance.share_set.all()
if self.instance.pk:
del self.fields['share']
class Meta:
model=Transaction
exclude=['origin', 'ach_entry', 'account']
post_dt = forms.DateTimeField(initial=datetime.date.today(), widget=forms.TextInput(attrs=
{
'class': 'datepicker'
}))
share = forms.ModelChoiceField(empty_label='---------', required=False, queryset=Share.objects.all())
Комментарии:
1. Возможно ли, что сохранения для транзакций выполняются параллельно? Они выглядят последовательными при просмотре вашего кода, но ваша проблема звучит как состояние гонки
2. Я бы не знал, как проверить — я ввел точку останова и запустил debug, и даже при медленном выполнении возникла эта проблема
Ответ №1:
Неясно, что может быть причиной проблемы, но может быть полезно выполнить обновление self.share.balance
в одном update()
запросе. Это можно сделать с помощью F выражений:
from django.db.models import F
class Transaction(models.Model):
# ...
def update_share_balance(self):
if self.db_cr == "cr":
amount = self.amt
else:
amount = -self.amt
# By using the queryset update() method, we can perform the
# change in a single query, without using a potentially old
# value from `self.share.balance`
return Share.objects.filter(id=self.share_id).update(
balance=F("balance") amount
)
def save(self, *args, **kwargs):
if not self.pk:
# ...
if self.share:
# ...
self.update_share_balance()
# Also, be sure to call the super().save() method at the end!
super().save(*args, **kwargs)