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
с ненулевым значением.