Группировка по дате в Django

Я пытаюсь добиться результата SQL запросом

SELECT 
UNIX_TIMESTAMP(DATE((FROM_UNIXTIME(`timestamp`)))) AS `x`, 
COUNT(`timestamp`) as y 
    FROM somedb.events
        WHERE user_id=3 AND 
        `timestamp` > 1612117800 AND 
        `timestamp` < 1614450600 AND
        `kind`='food'
    GROUP BY `x`
    ORDER BY `x` desc;

используя Django ORM. Ожидаемый результат:

[
    {
        "x": 1613759400,
        "y": 2
    },
    {
        "x": 1612463400,
        "y": 1
    }
]

Вот что я пробовал до сих пор:

queryset = events.objects.filter(
            user=request.user,
            timestamp__range=dates,
            kind=self.model.FOOD
        )

result = queryset.annotate(
            trunc_date_timestamp=Func(
              Value(
                TruncDate(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                    )
                )
              ),
              function='UNIX_TIMESTAMP',
              output_field=models.IntegerField()
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

В результате получается вывод:

[
    {
        "x": 0,
        "y": 3
    }
]

и

result = queryset.annotate(
            trunc_date_timestamp=Func(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                        output_field=models.DateField()
                    ),
                function='UNIX_TIMESTAMP'
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

производит вывод:

[
    {
        "x": 1613831760,
        "y": 1
    },
    {
        "x": 1613810160,
        "y": 1
    },
    {
        "x": 1612520520,
        "y": 1
    }
]

Наконец-то я заставил его работать. Я допустил крошечные ошибки в обоих подходах.

Первый подход:

result = ueryset.annotate(
            trunc_date_timestamp=Func(
                TruncDate(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                        output_field=models.DateField() # <<--- This is must
                    )
                ),
                function='UNIX_TIMESTAMP',
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

Удалено API Value() и добавлено output_field=models.DateTimeField() к внутреннему API Func(). output_field будет гарантировать, что поле, возвращаемое внутренним Func() API, должно быть типа models.DateField(), к которому будет применено TruncDate.

Второй подход:

queryset = queryset.annotate(
            trunc_date_timestamp=Func(
                Cast(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                    ),
                    output_field=models.DateField()
                ),
                function='UNIX_TIMESTAMP'
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

Изначально я думал

Func(
    F('timestamp'),
    function='FROM_UNIXTIME',
    output_field=models.DateField()
)

это вернет поле типа models.DateField(), но я не знаю, почему у меня не получилось и не сработало!!! Поэтому я использовал вместо этого метод Cast() и привел возвращаемое выражение к models.DateField(), чтобы заставить его работать

queryset = queryset.annotate(
            trunc_date_timestamp=Func(
                Cast(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                    ),
                    output_field=models.DateField()
                ),
                function='UNIX_TIMESTAMP'
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

Хотя это решение работает, я чувствую, что оригинальный код для второго подхода также должен работать, потому что если я правильно понял выражение Func(), то оба из

Func(
   F('timestamp'),
   function='FROM_UNIXTIME',
   output_field=models.DateField()
) 

и

Cast(
    Func(
       F('timestamp'),
       function='FROM_UNIXTIME'
    ),
    output_field=models.DateField()
)

выполняют одну и ту же работу.

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