Элементы заказа Django по двум полям, но игнорируя их, если они равны нулю

У меня есть следующая модель (очень упрощенная для целей этого вопроса):

class Product(models.Model):
    price = models.DecimalField(max_digits=8, decimal_places=2)
    sale_price = models.DecimalField(max_digits=10, blank=True, null=True, decimal_places=2)

Для большинства продуктов цена будет заполнена, а sale_price не будет. Итак, я могу заказать продукты по цене так:

Product.objects.order_by('price')
Product.objects.order_by('-price')

Однако некоторые продукты будут иметь sale_price, и я не могу найти способ упорядочить их аккуратно, чтобы цена продажи чередовалась с обычной ценой. Если я попробую упорядочить оба поля:

Product.objects.order_by('sale_price','price')

... тогда все продукты, которые не продаются, появляются вместе, за ними следуют все, что есть, вместо того, чтобы чередовать цены.

Это имеет смысл? У кого-нибудь есть способ решить это?

Спасибо!

Ответы

Ответ 1

Вы можете использовать метод extra() QuerySet для создания дополнительного поля в вашем запросе с помощью функции COALESCE в SQL, которая возвращает первое значение, отличное от NULL, которое оно передало.

Product.objects.extra(select={"current_price":"COALESCE(sale_price, price)"}, order_by=["-current_price"])

Вы должны поместить свой order_by в вызов extra(), поскольку дополнительное поле вручную "на самом деле не существует" по отношению к остальной части ORM, но синтаксис такой же, как обычный Django order_by() с.

Смотрите дополнительную() документацию здесь: http://docs.djangoproject.com/en/1.3/ref/models/querysets/#extra

Ответ 2

Если вы наткнетесь на это требование и используете Django 1.8 и выше, вы можете использовать django.db.models.functions.Coalesce для немного лучшего кросс-дБ-двигателя, решение:

from django.db.models.functions import Coalesce

Product.objects.annotate(
    current_price=Coalesce('sale_price', 'price')
).order_by('-current_price')