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