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

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.

Related

Count & Sum of Order Values for each customer (through iteration) in Django

I have Customer & Order models as below:
class Customer(models.Model):
name = models.CharField(max_length = 100)
city = models.CharField(max_length = 100)
class Order(models.Model):
value = models.FloatField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
Now I would like to generate a table of (distinct) customers along with a count of number of orders placed by each of them and the sum of values of those orders. I tried this in the views.py:
def customers(request):
customer_orders = Order.objects.distinct().annotate(Sum('value'))
Then in my html template, I tried the following:
<ul>
{% for customer in customer_orders %}
<li>{{customer.customer}} - {{customer.value__sum}}<li>
{% endfor %}
</ul>
After all this, instead of getting unique customers (and respective order records), I'm getting a list of all orders and customers are getting repeated (as shown below). Not sure what I'm missing here.
Bosco-Ward - 16,700.0
Ernser PLC - 51,200.0
Murphy Ltd - 21,400.0
Kohler-Veum - 29,200.0
Schmidt-Legros - 96,800.0
Brown-Weissnat - 8,200.0
Bosco-Ward - 36,400.0
Ernser PLC - 66,600.0
Murphy Ltd - 84,200.0
Also wanted to know if there's a possibility to generate a table of city names with order count and total value of orders received from that city (note that my order model doesn't have city field).
Since you want a queryset of Customer instances make your query on the Customer model itself instead of on Order, next I believe you will not need to use distinct here since the customer instances should be considered unique. Hence, you can make a query like:
from django.db.models import Count, Sum
customers = Customer.objects.annotate(order_count=Count('order'), order_value_sum=Sum('order__value'))
for customer in customers:
print(customer.name, customer.order_count, customer.order_value_sum)
Kindly note im typing the solution from my phone without testing it but this is what i think:
Give a related name to customer in Order model:
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name=“orders”)
Get orders count:
Customer.objects.get(id=1).orders.count()
Or access this attribute in template something like:
{{ customer.orders.count }}
Get orders count values by city
From django.db.models import Count, Sum
Customer.objects.values(“city”).annotate(order_count=Count(“orders”)).annotate(totals=Sum(“value”))
Add realted_name to Order customer field:
class Customer(models.Model):
name = models.CharField(max_length = 100)
city = models.CharField(max_length = 100)
class Order(models.Model):
value = models.FloatField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='orders')
query:
Customer.objects.values(“city”).annotate(order_count=Count(“orders”)).annotate(totals=Sum(“orders__value”))

How to properly use Django ORM Count with shopping cart

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)

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.

SQLite Django Model for Inventory of Seeds

I'm trying to build an Inventory Model for a Django App that handles the sale of seeds. Seeds are stored and sold in packs of 3, 5, or 10 seeds of a single variety (for example: 3 pack of mustard seeds).
I want to add x amount of products to inventory with a price for each entry, and sell that product at that price for as long as that entry has items left(quantity field > 0) even if later entries have been made for the same product and presentation but at a different price, so i have the following model:
class Product(models.Model):
name = models.CharField(max_length=100)
class Presentation(models.Model):
seed_qty = models.IntegerField()
class Stock(models.Model):
date = models.DateField(auto_now=True)
quantity = models.IntegerField()
product = models.ForeignKey(Product, on_delete=models.CASCADE)
presentation = models.ForeignKey(Presentation, on_delete=models.CASCADE)
cost = models.FloatField(null=True, blank=True)
sell_price = models.FloatField(null=True, blank=True)
I'm wondering if I should actually relate Product and Stock with a ManyToMany field through a GeneralEntry intermediate model in which I'd store date_added, presentation and cost/price.
My issue is that when I add multiple Stock entries for the same product and presentation, I can't seem to query the earliest prices for each available (quantity>0) stock entry for each product.
What I've tried so far has been:
stock = Stock.objects.filter(quantity__gt=0).order_by('-date')
stock = stock.annotate(min_date=Min('date')).filter(date=min_date)
But that returns that max_date isn't defined.
Any ideas on how to query or rearrange this model ?
Thanks!
*** UPDATE : I wasn't using F() function from django.db.models.
Doing it like this works:
stock = Stock.objects.filter(quantity__gt=0).order_by('-date')
stock = stock.annotate(min_date=Min('date')).filter(date=F('min_date'))
Turns out I wasn't using F() function from django.db.models.
Doing it like this works:
stock = Stock.objects.filter(quantity__gt=0).order_by('-date')
stock = stock.annotate(min_date=Min('date')).filter(date=F('min_date'))

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