Как сортировать по аннотированной Count() в связанной модели в Django

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

Я установил свои модели для включения (помимо прочего) модели Food, подключенной к модели User, через "потребитель" M2M-поля через модель потребления. Модель Food описывает пищевые блюда, а модель потребления описывает потребление потребителем пищи (дата, количество и т.д.).

class Food(models.Model):
    food_name = models.CharField(max_length=30)
    consumer = models.ManyToManyField("User", through=Consumption)

class Consumption(models.Model):
    food = models.ForeignKey("Food")
    user = models.ForeignKey("User")

Я хочу создать запрос, который возвращает все объекты Food, упорядоченные по количеству раз, когда объект Food отображается в таблице Consumption для этого пользователя (количество раз, когда пользователь потреблял пищу).

Я пытаюсь что-то сделать в строке:

Food.objects.all().annotate(consumption_times = Count(consumer)).order_by('consumption_times')`

Но это, конечно, будет считать все объекты Consumption, связанные с объектом Food, а не только те, которые связаны с пользователем. Нужно ли мне менять мои модели, или я просто пропущу что-то очевидное в запросах?

Это довольно критическая операция (среди прочего, она использовала для заполнения поля Autocomplete в Frontend), а таблица Food имеет пару тысяч записей, поэтому я предпочел бы сортировку в конце базы данных, вместо выполнения метода грубой силы и повторения результатов:

Consumption.objects.filter(food=food, user=user).count()

а затем используя сортировку python для их сортировки. Я не думаю, что этот метод будет очень хорошо масштабироваться по мере увеличения базы пользователей, и я хочу с самого начала создать базу данных в качестве будущего доказательства.

Любые идеи?

Ответ 1

Возможно, что-то вроде этого?

Food.objects.filter(consumer__user=user)\
            .annotate(consumption_times=Count('consumer'))\
            .order_by('consumption_times')

Ответ 2

У меня очень похожая проблема. В принципе, я знаю, что запрошенный SQL-запрос:

SELECT food.*, COUNT(IF(consumption.user_id=123,TRUE,NULL)) AS consumption_times
       FROM food LEFT JOIN consumption ON (food.id=consumption.food_id)
       ORDER BY consumption_times;

Я хочу, чтобы вы могли комбинировать агрегатные функции и выражение F, аннотировать выражения F без агрегатной функции, иметь более богатый набор операций/функций для выражений F и иметь виртуальные поля, которые в основном представляют собой автоматическую аннотацию F-выражений. Чтобы вы могли сделать:

Food.objects.annotate(consumption_times=Count(If(F('consumer')==user,True,None)))\
            .order_by('consumtion_times')

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

from django.db.models import aggregates,sql
class CountIf(sql.aggregates.Count):
    sql_template = '%(function)s(IF(%(field)s=%(equals)s,TRUE,NULL))'
sql.aggregates.CountIf = CountIf

consumption_times = aggregates.Count('consumer',equals=user.id)
consumption_times.name = 'CountIf'
rows = Food.objects.annotate(consumption_times=consumption_times)\
                   .order_by('consumption_times')