Django queryset with prefetch or not? - python

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)))

Related

Not able to access related model data using foreign key in Django

models.py
class products(models.Model):
name = models.CharField(max_length=100)
sku = models.CharField(max_length=50)
vendor = models.CharField(max_length=50)
brand = models.CharField(max_length=50)
price = models.FloatField()
product_status = models.BooleanField()
quantity = models.IntegerField()
def __str__(self):
return self.name
# categories
class categories(models.Model):
category_name = models.CharField(max_length=50)
parent_id = models.IntegerField()
# product categories
class product_categories(models.Model):
product = models.ForeignKey(products, on_delete=models.CASCADE)
category = models.ForeignKey(categories, on_delete=models.CASCADE)
def __str__(self):
return self.category
I can access 'category' table data(inside django shell) using
data = products.objects.all()
data.values('product_categories__category__category_name')
output: <QuerySet [{'product_categories__category__category_name': 'xxxx'}}]>
If I put this(inside django shell)
data.product_categories.category
output: 'QuerySet' object has no attribute 'product_categories'
How do I get a queryset(can be passed to html) which includes data from "categories" table along with the data of "products" table
There are a couple of issues happening here. First, data is a queryset, which is kind of like a list of objects, even though here there's just one object in the list. What you want is to get an attribute off of the item in the list, so you need something like a data.first() to get to that object before you start dotting into its attributes.
Secondly, the way Django handles reverse FK relationships requires that you refer to the FK by the standard name of, in your case, product_categories_set, OR you set your own related_name attribute on the FK. Something like:
# product categories
class product_categories(models.Model):
product = models.ForeignKey(products, on_delete=models.CASCADE, related_name='product_categories')
category = models.ForeignKey(categories, on_delete=models.CASCADE, related_name='product_categories')
def __str__(self):
return self.category
so that you can refer to your product_categories model from both the product and categories using just data.product_categories.
Thirdly, when accessing a reverse FK relationship, just like in point (1) above, you will get a related manager, from which you can get a queryset of items. Thus, to get the category name, you need to indicate which item you want the category name for. Assuming it's just the first item for everything, it would look something like:
data = products.objects.all()
product_category = data.product_categories.all()
category_name = product_category.category.category_name
Of course once you have more data, you'll not always want to just pick the first item, so you'll need to add filtering logic into the query to make sure you get the item you're looking for.
ETA, I do agree with the comment by Jorge above - a MTM would make this a bit simpler and would, in essence, create your product_categories table for you.

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)

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 group up by foreign keys

So i have two models:
class Product(models.Model):
name = models.CharField(_('Name'), max_length=200)
class Like(models.Model):
product = models.ForeignKey(Product, related_name='likes', on_delete=models.DO_NOTHING)
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='likes', on_delete=models.DO_NOTHING)
What I want is to get the total number of base products (<5 likes), standard products (<10 likes) and premium products.
I don't know how I should count FK of each product and then group them; and preferably in 1 query.
python 3.6, django 2.1, posgtresql
I don't understand how that should be acomplished in a single query, but you could try this:
from django.db.models import Count
qs = Product.objects.annotate(num_likes=Count('like'))
base_products = qs.filter(num_likes__lt=5)
standard_products = qs.filter(num_likes__gte=5, num_likes__lt=10)
premium_products = qs.filter(num_likes__gte=10)
This code gives you 3 QuerySet objects with the data you asked for.
If you want a single database query (it still includes both tables in the query) but don't mind loading the data in python lists instead of querysets (needs more memory and maybe even more time), then you could try:
from django.db.models import Count
qs = Product.objects.annotate(num_likes=Count('like'))
base_products = []
standard_products = []
premium_products = []
for p in qs:
if p.num_likes < 5:
base_products.append(p)
elif p.num_likes < 5:
standard_products.append(p)
else:
premium_products.append(p)

Distinct in many-to-many relation

class Order(models.Model):
...
class OrderItem(models.Model)
order = models.ForeignKey(Order)
product = models.ForeignKey(Product)
quantity = models.PositiveIntegerField()
What I need to do is to get the Order(s) which has only one order item. How can I write it using the QuerySet objects (without writing SQL)?
The easiest way to do this would be to use the Count aggregation:
from django.db.models import Count
Order.objects.annotate(count = Count('orderitem__id')).filter(count = 1)

Categories