Как использовать `filter()` в Django для вычисления разницы между значениями полей типа `DateField` из двух моделей?

Как можно отфильтровать список людей на момент события, прошло n лет между их датой рождения и датой события? Каким образом можно применить фильтрацию по возрасту к двум связанным моделям в Django? Можно ли использовать какой-либо другой метод, кроме сериализации, для применения этой фильтрации в Django queryset?
  • 1 августа 2024 г. 8:53
Ответы на вопрос 2
Для вычисления разницы между значениями полей `DateField` из двух моделей и отфильтрования списка людей на момент события, можно воспользоваться функцией `filter()` и оператором разности `F()` в Django.

Пример кода:

```python
from django.db.models import F
from datetime import timedelta

# Предположим, у модели Person есть поле birth_date, а у модели Event - поле event_date

# Разница между датами рождения и датой события
difference = timedelta(days=365 * n)  # где n - количество лет

# Фильтрация по возрасту на момент события
people = Person.objects.filter(birth_date__lte=F('event_date') - difference)
```

Этот код найдет всех людей, чей возраст на момент события составляет не менее `n` лет.

Что касается применения фильтрации по возрасту к двум связанным моделям в Django, можно использовать аннотации (annotations) для вычисления возраста на основе даты рождения и применения фильтрации к связанным объектам.

Пример использования аннотаций для вычисления возраста и фильтрации по нему:

```python
from django.db.models import F, ExpressionWrapper, DateTimeField
from django.db.models.functions import ExtractYear
from datetime import datetime

# Аннотация для вычисления возраста
age_annotation = ExpressionWrapper(
    ExtractYear(F('event_date')) - ExtractYear(F('person__birth_date')), 
    output_field=DateTimeField()
)

# Фильтрация по возрасту
events = Event.objects.annotate(age=age_annotation).filter(age__gte=n)
```

Этот код найдет все события, где возраст человека (связанного с событием) на момент события больше или равен `n` лет.

В Django queryset также можно использовать метод `extra()` для применения фильтрации без сериализации, хотя это не всегда рекомендуется из-за возможных проблем с безопасностью и совместимостью.
Все же удалось найти способ, уверен кому-то пригодится, в отчетности это один из частых случаев. Я не большой специалист в django, да и по python в целом, поэтому задачку решал в запроснике pma, в mysql есть прекрасный функционал для арифметики дат. В общем это по сути объединение таблиц и фильтрация по двум его полям, чего добиться в django не могу. 
SELECT e.id from appname_events as e left join appname_humans as h ON e.human_id=h.id where p.birthdate between DATE_SUB(e.eventDate, interval AGEMAX year) and DATE_SUB(e.eventDate, interval AGEMIN year) ORDER BY e.eventDate DESC

Запрос получился тяжелым, но похоже это меньшее из зол. Выбор только одного столбца дает выиграть время втрое против выборки всех столбцов. Я не уверен как django потом делает свою магию, но в итоге все равно сериализуются "правильные" объекты со всеми полями.
#вьюсет
...
def get_queryset(self):
   # получение и валидация ageMin, ageMax из self.request.query_params
   ...
   # выборка и сортировка
   query="SELECT e.id from appname_events as e left join appname_humans as h ON e.human_id=h.id where p.birthdate between DATE_SUB(e.eventDate, interval " + ageMax + " year) and DATE_SUB(e.eventDate, interval " + ageMin+ " year) ORDER BY e.eventDate DESC"
   queryset=Events.objects.raw(sql)
   return queryset

Работает нормально, учитываются високосные года. Из 800 событий и 600 людей на запрос уходит от 0.005 до 0.02 сек, в зависимости от выборки.

Но честно говоря, выглядит костылем, мне все еще кажется, что это как-то средствами django orm можно сделать. Кто знает ответ, обязательно напишите здесь.
Похожие вопросы