Django: Фильтр по полю связанной модели, не равному определенному значению

Учитывая эти модели:

from django.db import models

class Foo(models.Model):
    pass  # table with only one column, i.e. a primary key 'id' of type integer

class Bar(models.Model):
    value = models.TextField()
    foo = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bars')

Как создать фильтр, возвращающий все Foo, связанные с хотя бы одним Bar с ненулевым value?

Я знаю, что можно сначала выбрать ненулевые Bar, а затем вернуть связанные с ними Foo. Но я специально ищу решение, которое бы получало соответствующие Foo непосредственно в QuerySet. Причина в том, что это решение можно будет легко применить к отношениям, которые вложены на более глубоком уровне.


Что не работает

Несложно создать QuerySet, который возвращает все Foo, связанные хотя бы с одним Bar с нулевым value:

Foo.objects.filter(bars__value="zero")

Однако, из-за отсутствия оператора "not equal" в методах фильтра Django, невозможно сделать что-то вроде этого:

Foo.objects.filter(bars__value__not_equal="zero")  # Unsupported lookup 'not_equal' for TextField

Использование exclude:

Foo.objects.exclude(bars__value="zero")

не дает желаемого результата, поскольку исключает все Foo, которые относятся к хотя бы одному Bar с нулевым значением.

Таким образом, если Foo связан с двумя Bar, один из которых имеет нулевое value, а другой ненулевое value, он будет отфильтрован этим exclude. Однако предполагается включить его, поскольку он связан по крайней мере с одним Bar с ненулевым значением.

Аналогично, используя отрицаемый объект Q:

Foo.objects.filter(~models.Q(bars__value="zero"))

также не дает желаемого результата, поскольку включает только все Foo, которые не связаны с любыми Bar с нулевым значением.

Это создаст тот же QuerySet, что и метод exclude, упомянутый выше.


Примечания

Тип поля B.value в примерах намеренно выбран как текст, чтобы подчеркнуть, что я ищу общее решение, где оператор "не равно" не может быть эмулирован с помощью, например, комбинации операторов "меньше чем" и "больше чем".

Я также не могу придумать простой способ достижения этого, но использование Subquery для исключения нулевого значения Bar может сработать:

from django.db.models import Subquery

qs = Foo.objects.filter(
    bars__pk__in=Subquery(Bar.objects.exclude(value="zero").values('pk'))
)

Это должно дать вам все экземпляры Foo, имеющие отношение к Bar с ненулевым значением.

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