Фильтр с порядком для двух полей

У меня есть модель Klass с полями следующего содержания:

  date_start = models.DateField(null=True, blank=True, default=date.today)
  date_finish = models.DateField(null=True, blank=True)

Как видите, дата_начало будет обычно заполнена, а дата_завершение - нет. Если ни одна из них не заполнена, мы не должны рассматривать эту запись при дальнейшей фильтрации.

Я хотел бы видеть объекты (первые N результатов) упорядоченные по самой поздней дате независимо от того, дата_начала или дата_окончания. Точнее: рассматриваемая дата должна быть дата_финиша, если она существует, дата_начала в противном случае.

Обратите внимание, что я не хочу, чтобы N/2 законченных элементов и N/2 только начатых элементов были конкатенированы, а последние N "тронутых" элементов.

Моя первая идея - предоставить дополнительное поле considered_date, которое будет заполняться так, как я предложил, но я не знаю, как это реализовать. Должно ли это быть сделано:

  • на уровне модели, так что новое поле DateField должно быть добавлено и его содержимое всегда заполнено чем-л
  • .
  • отбирается для 2 отдельных условий (N элементов в каждом), затем предоставляется временное дополнительное поле (но без сохранения в db), затем 2 набора объединяются и снова упорядочиваются для этого нового условия

Забавный факт: у меня также есть BooleanField, которое указывает, закрыт период или нет. Оно нужно мне для простоты и фильтрации, но мы можем использовать его и здесь. Очевидно, оно обрабатывается функцией save() (по умолчанию True, устанавливается в False, если заполняется date_finish).

Честно говоря, эта "функция" не является критической в моем приложении. Она просто отображает некоторые "последние изменения" на странице приветствия, поэтому может срабатывать довольно часто.

Кажется, что ваше описание considered_date является идеальным случаем использования COALESCE sql функции, которая возвращает первое не NULL значение из своих аргументов.

Итак, в обычном SQL это вернет значение date_finish, если оно не NULL или date_start в противном случае (предполагая, что date_start никогда не будет NULL, поскольку оно имеет значение по умолчанию)

COALESCE(your_table_name.date_finish, your_table_name.date_start);

Django ORM имеет API для этой функции. Используя его с методом annotate queryset, мы можем построить нужный запрос без создания дополнительных полей в модели.

Для удобства назовем вашу модель TestModel

from django.db.models.functions import Coalesce

TestModel.objects.annotate(
    latest_touched_date=Coalesce("date_finish", "date_start")
).order_by("-latest_touched_date")

Обратите внимание, это будет работать только в том случае, если date_finish больше date_start. (что, как мне кажется, верно)

Вы можете добавить exclude для фильтрации любых записей, где и date_finish и date_start являются NULLs

from django.db.models.functions import Coalesce

TestModel.objects.exclude(date_start__isnull=True, date_finish__isnull=True).annotate(
    latest_touched_date=Coalesce("date_finish", "date_start")
).order_by("-latest_touched_date")

Просто нарежьте набор запросов, чтобы получить первые N результаты.

Вернуться на верх