简体   繁体   中英

Django: how to order a queryset by a related object if it exists and by a field if it doesn't?

I have two models:

class Product(models.Model):
    name = models.CharField(max_length=200, blank=False)
    price = models.DecimalField(max_digits=100, decimal_places=2, blank=False)

    class Meta:
        verbose_name = 'product'
        verbose_name_plural = 'products'

class PromotionPrice(models.Model):
    product = models.ForeignKey(Product, related_name='promotion_prices', on_delete=models.CASCADE, null=False)
    price = models.DecimalField(max_digits=100, decimal_places=2, blank=False)
    start_date = models.DateTimeField(blank=False)
    end_date = models.DateTimeField(blank=False)

    class Meta:
        verbose_name = 'promotion price'
        verbose_name_plural = 'promotion prices'

I want an efficient way to order all the Product objects by the price, taking into account the promotion price if it exists.

Eg I have the following list of Product objects:

[
    {
        id: 1,
        name: "Apple",
        price: "5.00",
        promotion_prices: []
    },
    {
        id: 2,
        name: "Banana",
        price: "10.00",
        promotion_prices: []
    },
    {
        id: 3,
        name: "Orange",
        price: "15.00",
        promotion_prices: [
            {
                product: 1,
                price: "9.00",
                start_date: "2021-03-01T00:00:00Z",
                end_date: "2021-04-01T00:00:00Z"
            }
        ]
    }
]

I want the result of ordering to be "Apple", "Orange", "Banana", because "Orange" has a promotion price on it.

I'm dealing with thousands of objects, so sorting using sorted() takes ages.

You may calculate actual price for each product. For this you may use subquery to calculate if product has promo price, and use coalesce function to return product price of it does not have promo price. Thus you will have actual price for each product which you may use in ordering. Something like this:

from django.db.models.expressions import Subquery, OuterRef, F
from django.db.models.functions.datetime import Now
from django.db.models.functions.comparison import Coalesce
from django.db.models.query_utils import Q
    
Product.objects.annotate(
    actual_price = Coalesce(
        Subquery(
            PromotionPrice.objects.filter(
                Q(start_date__lte = Now(),end_date__gte=Now()),
                product_id = OuterRef('id')
            ).values_list('price',flat=True)
        ),
        F('price')
    )
).order_by('actual_price')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM