Django ordering by two fields not working as expected - django

i am working on a shop for clothes, shoes etc. And i am trying to sort the products by price. The problems comes when the product's discount_price field is populated which means that this product is discounted. So when i want to order the products in my view i am expecting to see products with lower discount_price before products with higher price, but it is not working like this.
models.py
class Item(models.Model):
price = models.IntegerField(null=True, blank=True)
discount_price = models.IntegerField(null=True, blank=True)
The query i am performing
items = Item.objects.all().order_by('price', 'discount_price')

You can make use of Coalesce(..) [Django-doc] to first sort by 'discount_price' if it is not NULL, and use price otherwise:
from django.db.models.functions import Coalesce
items = Item.objects.order_by(Coalesce('discount_price', 'price').asc())

Related

Django – filter all products that have only empty variations

as the title says I'm trying to filter out all Products where all variations have a stock_quantity of 0. The variations are a separate class with a foreignkey to the Product. The related_name is set to 'variations'.
Here is my attempt:
Product.objects.annotate(variations_count=Count('variations')).filter(variations__stock_quantity=0)
However, it seems that filters out all variations where at least one variation has a stock quantity of 0, not all of them.
Here are my models:
class Product(models.Model):
name = models.CharField(max_length=50, null=True, blank=True)
...
class Variation(models.Model):
for_product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='variants')
stock_quantity = models.IntegerField()
How can I solve this?
Thanks!
You can check if the sum of the quantities is zero, so:
from django.db.models import Sum
Product.objects.alias(
stock_variations_count=Sum('variations__stock_quantity')
).filter(stock_variations_count=0)
or you can just exclude a Product that has any variant greater than zero:
from django.db.models import Sum
Product.objects.exclude(stock_variations__stock_quantity__gt=0)
the two are not entirely the same for Products with no variant.
or if you want to filter this out, you use:
from django.db.models import Sum
Product.objects.alias(
stock_variations_count=Sum('variations__stock_quantity')
).filter(stock_variations_count__gt=0)

Django - Getting latest of each choice

I have two models: the gas station and the price of a product. The price can up to have 4 choices, one for each product type, not every station has all four products. I want to query the latest entry of each of those products, preferably in a single query:
class GasStation(models.Model):
place_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255, null=True, blank=True)
class Price(models.Model):
class Producto(models.TextChoices):
GASOLINA_REGULAR = 'GR', _('Gasolina regular')
GASOINA_PREMIUM = 'GP', _('Gasolina premium')
DIESEL_REGULAR = 'DR', _('Diesel regular')
DIESEL_PREMIUM = 'DP', _('Diesel premium')
product = models.CharField(max_length=2, choices=Producto.choices)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
price = models.FloatField(null=True, blank=True)
estacion = models.ForeignKey(GasStation,
on_delete=models.SET_NULL,
null=True,
related_name='prices')
I've tried with:
station.price.filter(product__in=['GR', 'GP', 'DR', 'DP']).latest()
But it only returns the latest of the whole queryset, not the latest price of each product type. I want to avoid querying for each individual product because some stations don't sell all types .Any advice?
You're looking for annotations and Subquery. Below is what I think might work. Your models aren't fully defined. If you need the whole Price instance, then this won't work for you. Subquery can only annotate a single field.
from django.db.models import OuterRef, Subquery
stations = GasStation.objects.annotate(
latest_regular=Subquery(
Price.objects.filter(station_id=OuterRef("pk"), product="GR").order_by('-updated').values("price")[:1]
),
latest_premium=Subquery(
Price.objects.filter(station_id=OuterRef("pk"), product="GP").order_by('-updated').values("price")[:1]
),
...
)
station = stations.get(something_here)
station.latest_premium, station.latest_regular
You can make this more concise by using a dict comprehension iterating over your Product short codes and then doing .annotate(**annotations)

Django Queryset Prefetch Optimization for iterating over nested results

I'm looking for a way to optimize a queryset result processing in Django by improving database access performance, taking into consideration that I need to fetch a nested relation.
Taking these models as example:
class Movie(models.Model):
name = models.CharField(max_length=50)
class Ticket(models.Model):
code = models.CharField(max_length=255, blank=True, unique=True)
movie = models.ForeignKey(Movie, related_name='tickets')
class Buyer(models.Model):
name = models.CharField(max_length=50)
class Purchase(models.Model):
tickets = models.ManyToManyField(Ticket, related_name='purchases')
buyer = models.ForeignKey(Buyer, related_name='purchases')
and this Movie QuerySet:
movies = Movie.objects.all().prefetch_related('tickets__purchases__buyer')
In case I need to retrieve all buyers from each Movie in the above QuerySet, this is one approach:
for movie in movies:
buyers = Buyer.objects.filter(purchases__tickets__in=movie.tickets.all()).distinct()
But that will hit the database once for each Movie iterated. So to this in a single transaction, I'm doing something like:
def get_movie_buyers(movie):
buyers = set()
for ticket in movie.tickets.all():
for purchase in ticket.purchases.all():
if purchase.buyer:
buyers.add(purchase.buyer)
return buyers
for movie in movies:
buyers = get_movie_buyers(movie)
This approach hits the database once due to the prefetch_related in the QuerySet, but it doesn't look optimal as I'm iterating over many nested loops, which will then increase application memory overload instead.
There might be a better approach that I couldn't figure out yet, looking for some guidance.
UPDATE
alasdair suggested to use Prefetch object, tried that:
movies = Movie.objects.prefetch_related(
Prefetch(lookup='tickets__purchases__buyer',
to_attr='buyers')
).all()
for movie in movies:
print movie.buyers
But this gives me the following error:
'Movie' object has no attribute 'buyers'
The reason why it seems too difficult is the ManyToMany relation between Purchase and Tickets.
This relation allows the same ticket to be present in multiple purchases. But this will not be the case in actual data as one ticket can be purchased only once.
The query can be simplified if you remove this ManyToMany field and add a ForeignKey field in Ticket to purchase
class Ticket(models.Model):
code = models.CharField(max_length=255, blank=True, unique=True)
movie = models.ForeignKey(Movie, related_name='tickets')
purchase = models.ForeignKey(Purchase, null=True, blank=True)
Then the query can be simplified as below.
movies = Movie.objects.all().prefetch_related('tickets__purchase__buyer')
for movie in movies:
print(set(ticket.purchase.buyer for ticket in movie.tickets if ticket.purchase))
Sure this will create additional complexity while creating Purchases as you need to update the ticket objects with purchase_id.
You need to make a call on where to keep complexity based on the frequency of both actions

Django select all values() with nullable related fields

I have models like this:
class Vendor(models.Model):
title = models.CharField()
class Product(models.Model):
...
vendor = models.ForeignKey(Vendor, null=True, blank=True)
stock = models.ManyToManyField(Supplier, through='Stock')
class Stock(models.Model):
in_stock = models.BooleanField(default=True)
supplier = models.ForeignKey('catalog.Supplier', related_name='supplier_stock')
product = models.ForeignKey('catalog.Product', related_name='product_stock')
priority = models.IntegerField(default=0)
I designed models like this, because one Product can be supplied by different suppliers, and I need to know, what supplier exactly has this Product in stock.
So, in my view I want to get all results in values, to reduce number of queries and some specific logic. Also it duplicates me Product row with different Stock, by in python I group them up.
In my view I use:
Product.objects.all().values(
'id', 'title', 'vendor_code', 'vendor__title', 'price',
'product_stock__in_stock', 'stock__title', 'stock__id', 'stock__priority')
Because of INNER JOIN and null=True for Vendor related model, it returns me not all records for Product model. It just returns values where Vendor reference is set.
If I use 'vendor' instead of 'vendor__title' it returns me more results, than previous one, because in vendor field I can get {...'vendor': *id goes here*...} or {...'vendor': None...}, but I need the vendor__title value there. So any suggestions, how to achieve this?
Thanks in advance
Changed from vendor__title to product_stock__product__vendor__title helped me to fix my problem.

Sumproduct using Django's aggregation

Question
Is it possible using Django's aggregation capabilities to calculate a sumproduct?
Background
I am modeling an invoice, which can contain multiple items. The many-to-many relationship between the Invoice and Item models is handled through the InvoiceItem intermediary table.
The total amount of the invoice—amount_invoiced—is calculated by summing the product of unit_price and quantity for each item on a given invoice. Below is the code that I'm currently using to accomplish this, but I was wondering if there is a better way to handle this using Django's aggregation capabilities.
Current Code
class Item(models.Model):
item_num = models.SlugField(unique=True)
description = models.CharField(blank=True, max_length=100)
class InvoiceItem(models.Model):
item = models.ForeignKey(Item)
invoice = models.ForeignKey('Invoice')
unit_price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.DecimalField(max_digits=10, decimal_places=4)
class Invoice(models.Model):
invoice_num = models.SlugField(max_length=25)
invoice_items = models.ManyToManyField(Item,through='InvoiceItem')
def _get_amount_invoiced(self):
invoice_items = self.invoiceitem_set.all()
amount_invoiced = 0
for invoice_item in invoice_items:
amount_invoiced += (invoice_item.unit_price *
invoice_item.quantity)
return amount_invoiced
amount_invoiced = property(_get_amount_invoiced)
Yes, it is possible since Django 1.1 where aggregate functions were introduced. Here's a solution for your models:
def _get_amount_invoiced(self):
self.invoiceitem_set.extra(select=("item_total": "quantity * unit_price")
).aggregate(total=Sum("item_total")["total"]
It is, however, highly recommended to store item_total in a database, because it may be subject to discounts, taxes and other changes that make calculating it evety time impractical or even impossible.