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
Related
How can i get "total" price of items of OrderItem in cart model from these models down below? I tried doing something in views but I get attribute error that QuerySet' object has no attribute 'total'.
views.py
def cart(request):
cart = Cart.objects.filter(order_user=request.user)
order_items = OrderItem.objects.filter(cart__in=cart)
total = 0
for i in order_items:
total = i.quantity * i.item.price + cart.total
cart.update(total=total)
models.py
class OrderItem(models.Model):
cart = models.ForeignKey('Cart', on_delete=CASCADE, null=True)
item = models.ForeignKey(Item, on_delete=CASCADE, null=True)
quantity = models.IntegerField(default=1)
class Item(Visits, models.Model):
title = models.CharField(max_length=150)
price = models.IntegerField(default=1000)
image = models.ImageField(upload_to='pictures', default='static/images/man.png')
description = models.TextField(default="Item")
visits = models.IntegerField(default=0)
class Cart(models.Model):
order_user = models.OneToOneField(User, on_delete=CASCADE)
ordered = models.BooleanField(default=False)
total = models.IntegerField(default=0, help_text="100 = 1EUR")
order_items = models.ManyToManyField(Item, related_name='carts', through=OrderItem )
Just aggregate the total of ModelField total of the queryset like so
Total = Cart.objects.all().aggregate('total')
# Filtered in your case
Total = Cart.objects.filter(order_user=request.user).aggregate('total')
Apply filtering as necessary.
Also I suggest to have a good read here
You can retrieve the cart information for current users via the OrderItem model itself.
Check how annotate works
from django.db.models import Count
order_items = (OrderItem.objects.filter(cart__order_user=request.user)
.annotate(total=Count("quantity")*(item__price) + cart__total)
)
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)
Models.py
class Entity(models.Model):
entity = models.CharField(max_length=40)
class Period(models.Model):
period = models.CharField(max_length=10)
class Product(models.Model):
entity = models.ForeignKey(Entity, on_delete=models.CASCADE, default=None, blank=True, null=True)
period = models.ForeignKey(Period, on_delete=models.CASCADE, default=None, blank=True, null=True)
sku = models.CharField(max_length=12)
class Sale(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, default=None, blank=True, null=True)
price = models.DecimalField(max_digits=11, decimal_places=2)
Views.py
if request.method == 'POST':
if form.is_valid():
entityquery = form.cleaned_data['entity']
periodquery = form.cleaned_data['period']
entities = Entity.objects.get(entity=entityquery)
periods = Period.objects.get(period=periodquery)
products = Product.objects.filter(entity=entityquery, period=periodquery).values('id', 'period', 'entity', 'sku')
for sales in products.iterator():
sales = Sale.objects.filter(product__sku=product.sku, product__entity=entityquery, product__period=periodquery).aggregate(Sum('price'))
return sales
args = {'form': form, 'products': products, 'periods': periods, 'entities': entities, 'sales': sales}
return render(request, "products_list.html", args)
Expected Result
So far I am able to list all the SKU items that were sold based on the criteria (Period and Entity). Lets assume SKU 12 has two sales $10 and $30 and SKU 23 has three sales $5, $5 and $6
and I need to show the total sales for each of those products.
Input
Entity: Company XZY
Period: November
Output
SKU Total Sales
12 40.00
23 16.00
It is possible to do group by and SUM using the the django ORM.
How to query as GROUP BY in django?
from django.db.models import Sum
sales = Sale.objects.filter(product__sku=product.sku, product__entity=entityquery, product__period=periodquery).values(`product`).annotate(total = Sum('price'))
Also, in most cases, is very inefficient to loop over a query.
If at all possible, try to use the built-in ORM methods to avoid this.
This is known as a N+1 query problem.
I'd like to ask, how I could shrink this to one command? I understand that annotate is proper way to do this,but don't understand how.
Here is my code, which is too slow:
sum = 0
for contact in self.contacts.all():
sum += (contact.orders.aggregate(models.Sum('total'))['total__sum'])
return sum
I'd like to get Sum for each contact, all records in total column of relevant orders.
Code above produces sum, but is sluggishly slow. It is my understand it can be done with annotate,but not sure how to use it.
Here is Contact:
class Contact(models.Model):
company = models.ForeignKey(
Company, related_name="contacts", on_delete=models.PROTECT)
first_name = models.CharField(max_length=80)
last_name = models.CharField(max_length=80, blank=True)
email = models.EmailField()
And here is Orders:
class Order(models.Model):
order_number = models.CharField(max_length=80)
company = models.ForeignKey(Company, related_name="orders")
contact = models.ForeignKey(Contact, related_name="orders")
total = models.DecimalField(max_digits=12, decimal_places=6)
order_date = models.DateTimeField(null=True, blank=True)
Help please
You can annotate your queryset on the Contract model with:
from django.db.models import Sum
Contract.objects.annotate(
total_orders=Sum('orders__total')
)
The Contract objects that arise from this queryset will have an extra attribute .total_orders that contains the sum of the total field of the related Order objects.
This will thus create a query that looks like:
SELECT contract.*, SUM(order.total)
FROM contract
LEFT OUTER JOIN order ON order.contract_id = contract.id
GROUP BY contract.id
I try to calculate the average amount for each month for the 13 last month.
So I have a card with the date which has many amounts which are linked to a category.
For example my card 1 has an amount for category A, an amount for category B an amount for category C .... Amount, Card and Category have their own class in the model.
My objective is to calculate for one category, the average amount for each 13 last month.
Here is my model:
class Card(models.Model):
date = models.DateField(auto_now_add=False, null=False)
day = models.IntegerField(null=False)
comment = models.TextField(null=False)
worked = models.BooleanField(default=True)
def __str__(self):
return "<id={}, date={}, day={}, comment={}, worked={}>".format(
self.id,
self.date,
self.day,
self.comment,
self.worked
)
class Category(models.Model):
name = models.CharField(max_length=100)
icon = models.CharField(max_length=50)
order = models.IntegerField(null=False, unique=True)
image = models.CharField(max_length=255)
def __str__(self):
return "<id={}, name={}, icon={}>".format(self.id, self.name, self.icon)
class Amount(models.Model):
amount = models.DecimalField(max_digits=10, decimal_places=2)
card = models.ForeignKey(Card, on_delete=models.CASCADE, related_name='amounts')
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='amounts')
def __str__(self):
return "<id={}, amount={}>".format(self.id, self.amount)
And I have really no idea of how to do this.
Thanks for your help
The solution below uses Django's aggregation and Transform to give you for one category called category the average of the amounts in each month in range(13). If you need it to just be the previous 13 months I would suggest using datetime.timedelta and include a filter on card__date__year. You could also combine the filters below into one line but it gets a little long...
from django.db.models import Avg
amounts_in_category = Amount.objects.filter(category = category)
for month in range(13):
amounts_in_month = amounts_in_category.filter(card__date__month = month)
average_amount_for_month = amounts_in_month.aggregate(Avg('amount'))