How to calculate the total of invoice using sum annotate or agregate instead of forloop and save the value on field invoice.total
class Invoice(models.Model):
date = models.DateField(default=timezone.now)
client = models.ForeignKey('Client',on_delete=models.PROTECT)
total = models.DecimalField(default=0, max_digits=20, decimal_places=2)
def totalsubtotals(self):
items = self.invoiceitem_set.all()
total = 0
for item in items:
total += item.subtotal
return total
def save(self, *args, **kwargs):
self.total = self.totalsubtotals()
super(Invoice, self).save(*args, **kwargs)
class InvoiceItem(models.Model):
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
price = models.DecimalField(max_digits=20, decimal_places=2)
quantity = models.DecimalField(max_digits=20, decimal_places=2)
subtotal = models.DecimalField(default=0, max_digits=20, decimal_places=2)
def save(self, *args, **kwargs):
self.subtotal = self.price * self.quantity
super(InvoiceItem, self).save(*args, **kwargs)
Please don't. This is a form of data duplication: you store some part of the data twice: both the subtotals and the total. In fact subtotal is not necessary either.
It turns out that keeping data in sync is a hard problem. Imagine that you later change the quantity of an InvoiceItem, then you will not save the Invoice again, and thus the total is no longer correct. If you remove an InvoiceItem or you create a new InvoiceItem, then that is not the case either. You can make use of signals, but a lot of Django ORM calls will not run the signals. Indeed for example:
from django.db.models import F
InvoiceItem.objects.filter(pk=14).update(quantity=F('quantity')+1)
Here we thus increment a quantity, but the subtotal and total will not be updated, not even if we use signals.
You can try to implement this at the database level through triggers, but that will require manually adding triggers, and it is furthermore still likely you forget a scenario then eventually the data will get out of sync.
It is therefore more convenient not to use duplicated data. You can for example calculate the subtotal through a property:
class InvoiceItem(models.Model):
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
price = models.DecimalField(max_digits=20, decimal_places=2)
quantity = models.DecimalField(max_digits=20, decimal_places=2)
#property
def subtotal(self):
return self.price * self.quantity
for the Invoice you should not calculate this at the Django/Pyton layer, since that will take too much time and result in extra querying. You can use .annotate(…) in the views where you need the total of the Invoices with:
from django.db.models import F, Sum
Invoice.objects.annotate(
total=Sum(F('invoiceitem__quantity') * F('price'))
)
Your Invoice model thus does not contain a total or any subroutines to recalculate the total:
class Invoice(models.Model):
date = models.DateField(default=timezone.now)
client = models.ForeignKey('Client',on_delete=models.PROTECT)
Related
I am trying to print a particular wise total in the invoice using Django. The problem is if I use for loop, it prints the last items particular wise total in everywhere. I am confused what will be the best way to calculate individual total in their respective rows?
Here is my Views.py:
def order_complete(request):
order_number = request.GET.get('order_number')
transID = request.GET.get('payment_id')
try:
order = Order.objects.get(order_number=order_number, is_ordered=True)
ordered_products = OrderProduct.objects.filter(order_id = order.id)
total=0
subtotal = 0
for i in ordered_products:
total = i.product_price * i.quantity
subtotal += total
payment = Payment.objects.get(payment_id = transID)
context ={
'order': order,
'ordered_products': ordered_products,
'order_number': order.order_number,
'transID': payment.payment_id,
'payment': payment,
'subtotal': subtotal,
'total':total,
}
return render(request, 'orders/order_complete.html', context)
except(Payment.DoesNotExist, Order.DoesNotExist):
return redirect('home')
models.py
class OrderProduct(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
payment = models.ForeignKey(Payment, on_delete=models.SET_NULL, blank=True, null=True)
user = models.ForeignKey(Account, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
variations = models.ManyToManyField(Variation, blank=True)
quantity = models.IntegerField()
product_price = models.FloatField()
ordered = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.product.product_name
template
<dd class="text-right">${{ total }}</dd>
suggestion/solutions would be highly appreciated.
Here is the result
My suggestion is to always store total_amount in the table itself along with unit_price and quantity,
Cause this way, if you ever want to get the item's unit cost you can simply access it on order table itself and total to tell that this is the total of unit price x quantity
1 more advantage you get by this i.e. when calculating the final total of all order items you can simply write the following to get it
from django.db.models import Sum
order_products = OrderProduct.objects.filter(order_id=order.id)
total_amount = order_products.aggregate(Sum('total_amount'))["total_amount__sum"]
# total_amount is the new field to add in OrderProduct table which stores
# quantity * unit_price at the time of creation
This way, you will directly get individual total & sum of those totals
When I add an invoice, the Total is always 0 but when I update without any changes, it's updated with the totalsubtotals(). I understand that there are many calculations and in my case, the total calculation is done before the subtotals. Any recommendations.
class Invoice(models.Model):
date = models.DateField(default=timezone.now)
client = models.ForeignKey('Client',on_delete=models.PROTECT)
total = models.DecimalField(default=0, max_digits=20, decimal_places=2)
def totalsubtotals(self):
items = self.invoiceitem_set.all()
total = 0
for item in items:
total += item.subtotal
return total
def save(self, *args, **kwargs):
self.total = self.totalsubtotals()
super(Invoice, self).save(*args, **kwargs)
class InvoiceItem(models.Model):
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
price = models.DecimalField(max_digits=20, decimal_places=2)
quantity = models.DecimalField(max_digits=20, decimal_places=2)
subtotal = models.DecimalField(default=0, max_digits=20, decimal_places=2)
def save(self, *args, **kwargs):
self.subtotal = self.price * self.quantity
super(InvoiceItem, self).save(*args, **kwargs)
It looks to me like your "default = 0" in your InvoiceItem model under subtotal is what's causing the problem, if there is any error with price or quantity the default value is stored, returning 0 to your Invoice model.
I find that default values also make debugging a lot harder so I try to only use them where values are optional, in the case of an invoice, you can't order no quantity of products and you can't have no price either (0 is a number) errors in the input would set the value in the DB to Null (or None in the case of Python) and then your default sets the subtotal to 0.
Removing the default would cause errors when you try to input your values and you can better track down where the issue is based on your error messages.
Alternatively, in your save function for InvoiceItem, you can try...
if self.price && self.quantity: (check that they're not Null/None)
self.subtotal = self.price * self.quantity
else:
raise ValueError('Incorrect values in price or subtotal')
The question in general is about finding the modification of a foreign key of a model and call some function of the related model.
Assume I have two model class:
class Discount(models.Model):
def use(self, sell_item):
if self.max_price:
self.max_price -= sell_item.net()
if self.max_count:
self.max_count -= sell_item.amount
self.save()
def deuse(self, sell_item):
if self.max_price:
self.max_price += sell_item.net()
if self.max_count:
self.max_count += sell_item.amount
self.save()
max_price = models.PositiveIntegerField(blank=True,
null=True)
max_count = models.PositiveIntegerField(blank=True,
null=True)
amount = models.PositiveIntegerField(blank=False,
null=False)
class SellItem(models.Model):
def net(self):
price = self.amount * self.price
if self.discount:
price -= self.discount.amount * price / 100
return price * (1 + self.tax / 100)
amount = models.PositiveIntegerField(balnk=False,
null=False)
price = models.PositiveIntegerField(blank=False,
null=False)
tax = models.PositiveIntegerFeidl(blank=False,
null=False)
discount = models.ForeignKey(Discount,
blank=True,
null=True)
Now I want to execute use function whenever a discount add to an item and deuse it whenever it is being removed from an item. I found a post about it and to do that I write below code for sell item:
def __init__(self, *args, **kwargs):
self.dirty = False
self.pre_states = []
self.new_states = []
super(SellItem, self).__init__(*args, **kwargs)
def __setattr__(self, name, value):
if name == 'discount':
if hasattr(self, name):
pre_discount = self.discount
if pre_discount != value:
self.dirty = True
if pre_discount:
self.pre_states = ['pre_discount']
self.pre_discount = pre_discount
if value:
self.new_states = ['discount']
object.__setattr__(self, name, value)
def save(self, *args, **kwargs):
super(SellItem, self).save(*args, **kwargs)
if self.dirty:
if 'pre_discount' in self.pre_states:
self.pre_discount.deuse(self)
if 'discount' in self.new_states:
self.discount.use(self)
But it is not enough, because basically django would not fetch a foreign key when a new class is constructed, it instead just fill the _id item for it and whenever you need that it would fetch it from database, if I check for modification of discount_id instead of discount based on the order of setting of member values I may miss the previous discount because I have just current and previous discount_id not discount.
I know that it could possible implement with checking all of cases but I think after all I depend on django implementation of the behavior of database fetching which could be changed further.
I think there must be a proper and easier solution for just knowing the modification of a foreign key, I know there is some packages for storing history of modification but they are too much for my simple request.
absolute n00b here, just fiddling around. Trying to make a very simple app to track my personal expenses. I have a class for entering the expenses, a class for the categories and a class for my account balance. Plan is to create en entry in the account balance everytime I create an expense.
How do I update my account balance? I'll have to get fields from the latest entry in expenses to do the math with in my balance class, right?
This is what I have. Any help appreciated.
from django.db import models
from django.utils import timezone
class Category(models.Model):
category = models.CharField(max_length=200,blank=False)
def __str__(self):
return self.category
class Balance(models.Model):
date = models.DateTimeField(default=timezone.now)
previous_balance = ????
transaction = ????
current_balance = previous_balance - transaction
class Expense(models.Model):
date = models.DateTimeField(default=timezone.now)
spender = models.ForeignKey('auth.User')
description = models.CharField(max_length=200)
category = models.ForeignKey(Category,default=1)
ABN = 'ABN'
ING = 'ING'
ACCOUNT_CHOICES = (
(ABN, 'ABN'),
(ING, 'ING'),
)
account = models.CharField(
max_length=30,
choices=ACCOUNT_CHOICES,
default=ABN,
)
amount = models.DecimalField(max_digits=10, decimal_places=2)
def commit(self):
self.commit_date = timezone.now()
self.save()
def __str__(self):
return u"%s. Kosten: %s" % (self.description, self.amount)
If I'm understanding your question correctly, you want to be able to get your current balance after creating Expenses. If so, you can use Django's aggregation:
from django.db.models import Sum
class Balance(models.Model):
date = models.DateTimeField(default=timezone.now)
# Keep the amount you start with
starting_balance = models.IntegerField()
# Get the Sum of all expenses and do some simple subtraction
def get_current_balance(self):
total_expenses = Expense.objects.all().aggregate(Sum('amount'))
return self.starting_balance - total_expenses['amount__sum']
Then in your views, you can do something like:
current_balance = some_balance_instance.get_current_balance()
considered balance change will be trigger by expense record change, you can overwrite save on Expense model. then balance table can be maintain in auto.
import datetime
class Expense(models.Model):
date = models.DateTimeField(default=timezone.now)
spender = models.ForeignKey('auth.User')
description = models.CharField(max_length=200)
category = models.ForeignKey(Category,default=1)
ABN = 'ABN'
ING = 'ING'
ACCOUNT_CHOICES = (
(ABN, 'ABN'),
(ING, 'ING'),
)
account = models.CharField(
max_length=30,
choices=ACCOUNT_CHOICES,
default=ABN,
)
amount = models.DecimalField(max_digits=10, decimal_places=2)
def save(self, *args, **kwargs):
super(Expense, self).save(*args, **kwargs)
last_bal = Balance.objects.order_by('id').last()
Balance.objects.create(date=datetime.datetime.now(), previouse_balance=last_bal.current_balance,
transaction=self, current_balance=last_bal.current_balance + self.amount)
Record the value of a calculated field is really more efficient to calculate without saving?
class SaleDetail(models.Model):
product = models.ForeignKey(Product)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=8, decimal_places=2)
subtotal = models.DecimalField(max_digits=8, decimal_places=2)
def save(self, *args, **kwargs):
self.subtotal = self.price * self.quantity
super(SaleDetail, self).save(*args, **kwargs)
Calculated field
class SaleDetail(models.Model):
product = models.ForeignKey(Product)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=8, decimal_places=2)
def get_total_value(self):
if self.quantity:
return self.price * self.quantity
total_value = property(get_total_value)
There doesn't really seem to be any good reason to pre-calculate the field here. Multiplying one existing value by another is a simple operation, and there's no benefit to be gained by calculating it on save.
There would only be some benefit if the value to be calculated involved a lot of complex and expensive operations, such as querying multiple tables or calling an external API.