I have an invoice form made up of two models - Invoice & InvoiceItem - with the help of inlineformset_factory.
I have the following jquery snippet to compute the individual totals and net total by reading the price,quantity,tax of each item.
function calculateSubTotal(obj){
subTotal = 0
parentDiv = obj.closest('.formset')
rate=parseFloat($(parentDiv).find('.rate').val())
quantity=parseInt($(parentDiv).find('.quantity').val())
tax=$(parentDiv).find('.tax option:selected').html()//.
tax=tax.match(/\d+/);
if(tax)
tax=parseFloat(tax[0],10)
else
return
if(!isNaN(rate) && !isNaN(quantity) && $.isNumeric(tax)){
subTotal = rate*quantity*(100+tax)/100
$(parentDiv).find('.total').val(subTotal)
}
}
function calculateTotal() {
subTotal=0
$('.total').each(function(){
//console.log($(this).id)
console.log($(this).val())
val=parseFloat($(this).val())
subTotal+=val
});
if(!isNaN(subTotal))
$('#id_total').val(subTotal)
}
$(document).ready(function() {
$('.formset .form-control').on("blur",function(e){
calculateSubTotal(this);
calculateTotal();
});
});
Now, I "believe", I need to make all these calculations on the server-side before save, in order to prevent any manual correction/error by the user in the form. (Correct me if I'm wrong)
How do I proceed here?
Here's my form_valid() of CreateView.
def form_valid(self, form):
context = self.get_context_data()
item_formset = context['item_formset']
with transaction.atomic():
form.instance.invoice_type=self.kwargs['invoice_type']
self.object = form.save(commit=False)
#self.object.save()
if item_formset.is_valid():
forms = item_formset.save(commit=False)
for form in forms:
**#calculate sub-total and assign net-total to parentform.instance.total**
item_formset.instance = self.object
item_formset.save()
return super().form_valid(form)
Did a lot of trial-and-errors around models's save(), post-save signals, form's clean() etc. and finally got something that works.
Here's how it looks right now.
First, use InvoiceItemForm
's clean()
method to make sure that the total of each row is correct
def clean(self):
cleaned_data=super(InvoiceItemForm, self).clean()
total=cleaned_data.get('total')
if total:
tax=cleaned_data.get('tax')
calculated_total=price*(100+tax.rate)*quantity/100
if calculated_total != total:
raise forms.ValidationError({'total':["Total is incorrect."]})
return cleaned_data
Next, under CreateViews's form_valid() method, iterate through each form in the formset and sum the individual totals. Compare this to the total value of the main form.
with transaction.atomic():
form.instance.invoice_type=self.kwargs['invoice_type']
self.object = form.save(commit=False)
if item_formset.is_valid() == False:
return self.render_to_response(self.get_context_data(form=form,item_formset=item_formset ))
subTotal=0
i=0
for f in item_formset.forms:
subTotal += float(f.data['item-'+str(i)+'-total'])
i=i+1
if subTotal!= float(form.data['total']):
form.add_error('total', "Total is incorrect.")
return self.render_to_response(self.get_context_data(form=form,item_formset=item_formset ))
(Any possible corrections/simplifications are welcome)
Thanks.