Django аннотирует кверисет одной модели методом кверисета другой модели
Я не уверен, что то, что я хочу сделать, можно сделать.
Я хочу фильтровать или исключать элементы из одного объекта QuerySet, используя аннотированные значения QuerySet другой модели, связанной с первой через FK.
модели
Ниже обзор моделей, о которых я говорю:
две модели сидят в моем main.models.py
- класс
Member
и его Менеджер
from django.db import models
from django.contrib.auth.models import UserManager
class MemberManager(UserManager):
def get_queryset(self):
return MemberQuerySet(self.model, using=self._db)
def annotate_something(self):
return self.get_queryset().annotate_something_important()
class Member(User):
objects = MemberManager()
field_1 = models.BooleanField(default = False)
field_2 = models.CharField(max_length = 1, blank = True, null = True)
...
- класс
Experience
и его Менеджер
class ExperienceManager(models.Manager):
def get_queryset(self):
return ExperienceQuerySet(self.model, using=self._db)
def annotate_duracion(self):
return self.get_queryset().annotate_duracion()
class Experience(models.Model):
objects = ExperienceManager()
member = models.ForeignKey(Member, related_name='experiences', on_delete = models.CASCADE)
start_date = models.DateField(blank = True, null = True)
end_date = models.DateField(blank = True, null = True)
обратите внимание на FK в Experience
на Member
классы MemberQuerySet
и ExperienceQuerySet
определяются в отдельном main.queryset.py
файле
class MemberQuerySet(models.QuerySet):
def annotate_something_important(self):
return self.something_important
...
class ExperienceQuerySet(models.QuerySet):
def annotate_duration(self):
current_date = datetime.now().date()
return self.annotate(
end_date_obj = Case(
When(end_date = None, then = Value(current_date)),
default = F('end_date'),
output_field = DateField(),
)
).annotate(
duration = ExpressionWrapper(
F('end_date_obj') - F('start_date'),
output_field = DurationField()
)
).order_by("-end_date_obj")
запрос
начальной точкой является объект MemberQueryset
, полученный из следующего запроса, который может состоять из ~400k элементов.
members = Member.objects.filter(
field_1 = True
).annotate_something()
Основной целью функции является постепенная фильтрация данного набора QuerySet на основе критериев, считанных из views.py
препятствие
Теперь начинается самая мясная часть:
Я хочу исключить всех тех членов, чей последний 3 опыт длится менее 1 года
Для достижения этого я предусмотрел annotate
на членах (экземпляр MemberQueryset
), используя related_name='experiences'
. однако, я должен сделать что-то вроде вызова метода annotate_duration
в цепочке поиска, что не работает.
ориентировочное решение
Я прибегнул к следующему
bad_ids = [
m.id for m in members if not all(
m.experiences.all().annotate_duration()[:3].annotate(
long_experience=Case(
When(
duration__gte=year,
then=Value(True, output_field=BooleanField())
),
default=Value(False, output_field=BooleanField())
)
).values_list('long_experience', flat=True)
)
]
members = members.exclude(id__in = bad_ids)
Проблема такого подхода заключается в цикле for над членами, что резко увеличивает время вычислений скрипта, часто приводя к тому, что задача будет убита за чрезмерные затраты времени (я полагаю, основываясь на этой ошибке "Worker exited prematurely: signal 9 (SIGKILL)."
).
Кто-нибудь знает, как этого добиться, избегая громоздкого цикла for
?
ресурсы
ниже список ресурсов, которые я прочитал
Как аннотировать queryset другим queryset
django model method to return queryset with annotate
Django - Query : аннотируйте кверисет с помощью связанных полей модели
Я чувствую, что последний может содержать элементы для достижения моей цели, но я не знаю, как их использовать.
Любая помощь будет высоко оценена. Будьте здоровы