You could override your Inline formset to achieve what you want. In the clean method of the formset you have access to your Shopping instance through the 'instance' member. Therefore you could use the Shopping model to store the calculated total temporarily and make your formsets communicate. In models.py:
class Shopping(models.Model):
shop_name = models.CharField(max_length=200)
def __init__(self, *args, **kwargs)
super(Shopping, self).__init__(*args, **kwargs)
self.__total__ = None
in admin.py:
from django.forms.models import BaseInlineFormSet
class ItemInlineFormSet(BaseInlineFormSet):
def clean(self):
super(ItemInlineFormSet, self).clean()
total = 0
for form in self.forms:
if not form.is_valid():
return #other errors exist, so don't bother
if form.cleaned_data and not form.cleaned_data.get('DELETE'):
total += form.cleaned_data['cost']
self.instance.__total__ = total
class BuyerInlineFormSet(BaseInlineFormSet):
def clean(self):
super(BuyerInlineFormSet, self).clean()
total = 0
for form in self.forms:
if not form.is_valid():
return #other errors exist, so don't bother
if form.cleaned_data and not form.cleaned_data.get('DELETE'):
total += form.cleaned_data['cost']
#compare only if Item inline forms were clean as well
if self.instance.__total__ is not None and self.instance.__total__ != total:
raise ValidationError('Oops!')
class ItemInline(admin.TabularInline):
model = Item
formset = ItemInlineFormSet
class BuyerInline(admin.TabularInline):
model = Buyer
formset = BuyerInlineFormSet
This is the only clean way you can do it (to the best of my knowledge) and everything is placed where it should be.
EDIT: Added the *if form.cleaned_data* check since forms contain empty inlines as well.
Please let me know how this works for you!
EDIT2: Added the check for forms about to be deleted, as correctly pointed out in the comments. These forms should not participate in the calculations.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…