Django получение FK через функцию селектора

У меня есть 3 модели:

class A:
start_date=DateField
end_date=DateField
... 
@property
def is_valid():
    if self.start_date is not None and self.end_date is not None:
            return self.start_date <= get_now().date() <= self.end_date
    elif (self.start_date is None or self.start_date <= get_now.date()) and self.end_date is None:
            return True

class B:
a = ManyToMany(A)

class C:
b = ForeignKey(B)

и селекторная функция get_valid_a's, которая возвращает все допустимые экземпляры class A, заданные одним class B

Функция селектора:

def get_valid_a's(B):
    return B.A.filter((
            Q(start_date__lte=get_now().date()) &  
    Q(end_date__gte=get_now().date())) | (
            Q(start_date__lte=get_now().date()) & 
    Q(end_date__isnull=True)) | (
            Q(start_date__isnull=True) & Q(end_date__isnull=True))
        )

Я ищу что-то вроде этого:

C_with_valid_A = C.objects.filter(
    d_pk=d_pk, 
    b__a__isnull=False,
   <here I am lookig to do something like: b__a__in=get_valid_a's(b=b)
)

Любая помощь будет оценена по достоинству!

Я бы добавил custom manager к A и добавил метод к этому менеджеру, который фильтрует по действительным результатам. Вы всегда можете просто сделать этот метод classmethod для быстрой победы

(я просто вставил запрос, так как его почти невозможно проверить/разобрать :D)

class A:
    @classmethod
    def get_valid(cls):
        return cls.objects.filter((
            Q(start_date__lte=get_now().date()) & Q(end_date__gte=get_now().date())) | (
        Q(start_date__lte=get_now().date()) & 
Q(end_date__isnull=True)) | (
        Q(start_date__isnull=True) & Q(end_date__isnull=True)))

Затем вы можете использовать этот метод в других запросах

C_with_valid_A = C.objects.filter(
    d_pk=d_pk,
    b__a__in=A.get_valid()
)

Я прочитал немного документации Django и нашел действительно полезную функцию уровня DB, которую можно использовать в таких случаях - Exists

Запрос на выборку выглядит следующим образом

C_with_valid_A = C.objects.annotate(
    has_valid_As=Exists(
    A.objects.filter((
            Q(start_date__lte=get_now().date()) &  
    Q(end_date__gte=get_now().date())) | (
            Q(start_date__lte=get_now().date()) & 
    Q(end_date__isnull=True)) | (
            Q(start_date__isnull=True) & Q(end_date__isnull=True)), 
    Bs=OuterRef('b')
    )).filter(has_valid_As=True,d=d_pk)

Для Django >= 3.0 тот же запрос можно записать как

C_with_valid_A = C.objects.filter(
    Exists(
    A.objects.filter((
            Q(start_date__lte=get_now().date()) &  
    Q(end_date__gte=get_now().date())) | (
            Q(start_date__lte=get_now().date()) & 
    Q(end_date__isnull=True)) | (
            Q(start_date__isnull=True) & Q(end_date__isnull=True)), 
    Bs=OuterRef('b'),
    d=d_pk
))

По сути, он делает то же самое, что и первый запрос. В моем понимании, OuterRef представляет отношение между моделью C и моделью B.

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