Как исключить экземпляры с пустыми наборами запросов в prefetch_related?

У меня есть две модели - Project и Contract.
Они имеют отношения "один ко многим".

class Contract(models.Model):
    project = models.ForeignKey(Project)

Я получаю qs из Project экземпляров с соответствующим Contract экземпляром(ами).

projects = Project.objects.filter(active=True).prefetch_related(
        Prefetch('contract_set', queryset=Contract.objects.filter(**filters), to_attr='contracts')
    )

Но теперь мне нужно получить Project экземпляры с точным Contract экземпляром, поместив Contract guid в filters.

filters: Dict = {
  'guid': some_guid,
}

Но я получаю все Project экземпляры, где только один имеет не пустой contracts attr, а остальные Project экземпляры имеют пустой contracts attr.

Я нашел такие вопросы:
Как исключить строки с пустым полем prefetch_related
Фильтр prefetch_related empty в django
но они мне не помогают

Я пробовал такие варианты:
a) используя OuterRef и Exist:

projects = Project.objects.filter(**filters_projects).annotate(
    has_contracts=Exists(
        Contract.objects.filter(project_id=OuterRef('pk'), **filters_contracts)
    )
)

projects = projects.filter(has_contracts=True)

b) с помощью annotate:

projects = (
        Project.objects.filter(**filters_projects)
        .annotate(num_contracts=Count('contract'))
        .exclude(num_contracts=0)
        .prefetch_related(
            Prefetch('contract_set', queryset=Contract.objects.filter(**filters_contracts), to_attr='contracts')
        )
    )

Они не работают на меня...

Как я могу реализовать требуемую функциональность?

Вам придется фильтровать как в Prefetch объекте , так и в объекте Project, поэтому:

projects = (
    Project.objects.filter(
        Exists(
            Contract.objects.filter(
                project_id=OuterRef('pk'), **filters_contracts
            )
        ),
        **filters_projects
    )
    .prefetch_related(
        Prefetch(
            'contract_set',
            queryset=Contract.objects.filter(**filters_contracts),
            to_attr='contracts',
        )
    )
    .distinct()
)

Таким образом, вы используете **filters_contracts дважды: один раз, чтобы получить эти Project только один раз, и один раз для предварительно найденного объекта.

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