How to properly use Django ORM Count with shopping cart - python

I have such models structure and I need to get information about how many pizzas was ordered.
I did something like this PizzaOrder.objects.all().values('pizza').annotate(total=Count('pizza')). It works fine for orders where count in PizzaOrder equals to 1, but if count more than 1, it displays wrong count number. So I want somehow to connect Django Count with my field count in order to know how many pizzas was ordered.
models.py
class Pizza(models.Model):
name = models.CharField(max_length=256)
class Order(models.Model):
order_number = models.IntegerField(primary_key=True)
pizzas = models.ManyToManyField(to='Pizza', through='PizzaOrder')
class PizzaOrder(models.Model):
pizza = models.ForeignKey(
to=Pizza,
on_delete=models.PROTECT,
)
order = models.ForeignKey(
to=Order,
on_delete=models.PROTECT,
)
count = models.SmallIntegerField()

Instead of Count you need to use the Sum function:
# annotating on the orders:
orders = Order.objects.annotate(total=Sum('pizzaorder__count'))
for order in orders:
print(order.order_number, order.total)
# annotating on the pizzas:
pizzas = Pizza.objects.annotate(total=Sum('pizzaorder__count'))
for pizza in pizzas:
print(pizza.name, pizza.total)
# aggregating irrespective of order / pizza:
pizza_count = PizzaOrder.objects.aggregate(total=Sum('count'))['total']
print(pizza_count)

Related

Getting the difference of quantity between 2 different django model fields?

I have here 2 django database table
class Inventory(models.Model):
product_name = models.CharField(max_length = 100)
qty = models.PositiveIntegerField()
class Order(models.Model):
product = models.ForeignKey(Inventory, on_delete = models.CASCADE )
qty = models.PositiveIntegerField()
I would like to have an inventory table page wherein I can see the total qty left in Inventory (for example: Inventory.qty - Order.qty) .
how to do this in Django ?
You can annotate the Inventory with the qty minus the sum of the related Orders:
from django.db.models import F, Sum
Inventory.objects.annotate(
qty_left=F('qty') - Sum('order__qty')
)
The Inventory objects that arise from this QuerySet will have an extra attribute .qty_left that contains the qty of the Inventory minus the sum of the qtys of the related Orders.

Django sum on an external attribut

I have an issue with database and sum in Django.
I have 3 tables: customer, order and orderLine.
For a report, I would like to calculate the sum of all line price for every order of a customer.
class Customer(models.Model):
firstName = models.CharField(max_length=200)
lastName = models.CharField(max_length=200)
mail = models.EmailField(max_length=100)
etc...
def get_count_of_orders(self):
return self.orders.count()
def get_sum_line_prince_of_all_orders(self):
???????????
return (sum_of_all_line_prince_all_orders)
class Order(models.Model):
orderNum = models.CharField(max_length=200)
customer = models.ForeignKey(Customer, related_name="orders")
globalAmount = models.DecimalField(max_digits=20, decimal_places=4)
...
class OrderLine(models.Model):
order = models.ForeignKey(Order, related_name="ordersLines")
linePrice = models.DecimalField(max_digits=20, decimal_places=4)
...
I don't know what to set in get_sum_of_orders to get the right result.
I've trayed different things like annotate or aggregate.
But without success at the moment.
I didn't understand this process at the moment.
Could you help me?
You can access all orders with:
self.orders.all()
and you can iterate them with:
sum = 0
for each_order in self.orders.all():
sum += each_order.globalAmount
return sum
Here I presume that globalAmount is the amount you need to calculate.
If you need to reach OrderLine -> linePrice through each customer and calculate the sum of the linePrice(s) of each customer, try the following:
for each_order in self.orders.all(): #each order
for each_OrderLine in each_order.ordersLines.all()
sum += each_OrderLine.linePrice
Or you could use list comprehension:
sum([myorder.linePrice for myorder in order.ordersLines.all() for order in self.orders.all()])

Query to fetch highest rated movie with mimimum 5 people rated

I want to fetch name of movie with maximum rated movie with minimum 5 people rated in django.
My code :
model.py
class Movie(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
vote_count = models.IntegerField()
class Watchlist(models.Model):
userid = models.IntegerField()
movie_id = models.ForeignKey(Movie, on_delete=models.CASCADE)
rating = models.IntegerField()
what will be query to get movie with highest rating with minimum 5 people ?
I propose that you make some changes to your model. Normally ForeignKeys do not end with an id suffix, since Django will add a "twin field" with an _id suffix that stores the value of the target field. Furthermore you probably better make a ForeignKey to the user model. If you do not specify a primary key yourself, Django will automatically add an field named id that is an AutoField, hendce there is no need to add that manually. Finally you do not need to store the vote_count in a field of the Movie, you can retrieve that by counting the number of related Rating objects:
from django.conf import settings
class Movie(models.Model):
title = models.CharField(max_length=100)
class Rating(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete.models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
rating = models.IntegerField()
Then we can retrieve the highest rated movie with:
from django.db.models import Avg, Count
higest_rated = Movie.objects.annotate(
rating=Avg('rating__rating'),
votes=Count('rating')
).filter(votes__gte=5).order_by('-rating').first()
Here the votes__gte=5 will filter such that it will only obtain Movies with five or more votes, and we order by rating in descending order.
I'd modify the model, moving out Rating entity related fields from Watchlist and Movie.
Add the "Rate" class, and then filter by two conditions:
Count(Rate for the exact Movie) > minimum threshold(e.g. 5)
AVG(rating score for the exact Movie) > minimum threshold(e.g. 5)
or, if you need top-rated movies, use Order by as it described in that answer
In your case, you could use Count and Average with Watchlist.Rating field

Save a ForeignKey "child" without saving the "parent" first?

Suppose I have a view for saving an order to a database based on cart contents:
def cart_checkout(request):
order = Order()
order.first_name = 'x'
order.last_name = 'y'
order.address = 'z'
order.save()
cart = Cart(request)
for product_id, product_quantity in cart:
product = Product.objects.get(pk=product_id)
order_item = OrderItem()
order_item.order = order
order_item.name = product.name
order_item.price = product.price
order_item.amount = product_quantity
order_item.save()
order.update_total_price() # updates the Order total price field with the sum of order items prices
order.save()
return HttpResponse('Checked-out!')
As you can see, I am calling order.save() twice in this view: first to create an Order instance the OrderItems can be attached to in the for loop, and then to update the total price of the order based on order items in it. If I removed the first .save(), I would get an error on the second one telling me the order needs to be saved first.
Calling the .save() method twice does not seem DRY enough to me. Is there a way to do it only once?
Note that I am not subclassing ModelForm, so I cannot use .save(commit=False). Also, I do not want to just hide the save() method in the update_total_price() method.
Models.py:
from django.db import models
from .mixins import DisplayNameMixin
class Product(DisplayNameMixin, models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
amount = models.IntegerField()
class Order(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def update_total_price(self):
order_items = self.orderitem_set.all()
self.total_price = sum([
x.price * x.amount
for x in order_items
])
class OrderItem(models.Model):
order = models.ForeignKey('Order', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
amount = models.IntegerField()
I think, you can't help but save the order twice, as you need to have an order_id to create the OrderItems, and then update the order with the items' amount.
I have a few suggestions to make though.
You can make total_price a calculated property, so that you would not have to save the order:
class Order(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
#property
def total_price(self):
return sum([
x.price * x.amount
for x in self.orderitem_set.all()
])
From DB theory perspective your DB structure is wrong. It needs to be normalized first.
Why it is wrong?
Order.total_price is redundant table column. That information can be found with aggregation. At DB level there are no protections preventing DB users (Django app in your case) from entering compromised data. So your DB can be telling two different total prices (Order.total_price != SUM(OrderItem.price * OrderItem.amount)) at the same time.
So to appease DB normalization gods you need to drop total_price field and use Django aggregations/annotations (https://docs.djangoproject.com/en/3.0/topics/db/aggregation/) when you need to access it.
Saying that, there could be a totally valid reason to put total_price inside Order table. That reason usually is performance. Sometimes SQL query complexity (It is very annoying to filter by an aggregated column).
But there is a price. And that price is de-normalization of your DB. In your case you are paying breaking DRY principle.
Just make sure that you are calling both save()'s in the same transaction.
To expand on petraszd's answer (i.e. remove the total_price field) and engin_ipek's answer (i.e. add total_price as a calculated property), you could try making total_price a cached property, to avoid calculating the same value more than once - as long as the same Order instance is passed around.
You would also probably make the calculation a little less expensive if you used aggregation to calculate the total price, as petraszd mentioned, e.g. adding the products of price and amount.

Django queryset with prefetch or not?

I have such models
class Category(models.Model):
name = models.CharField()
…
class Product(models.Model):
category = models.ForeignKey(Category, verbose_name=‘cat’)
name = models.CharField()
price = models.IntegerField()
I need to create queryset which will select all products with price >= 100 grouped by categories. Afterwards I need to get count of products in each category.
I did
categories = Category.objects.filter(product__price__gte = 100)
This queryset gave me all categories which contain product with price >= 100. But how can I get count of products in it and products themseves? Maybe I need to use a prefetch_related, but I don't know how.
You can achieve this usingCount with conditional aggregation from django.
categories = Category.objects.filter(product__price__gte = 100).\
annotate(count_of_products=Count('products', filter=Q(product__price__gte = 100)))

Categories