1
0
mirror of https://github.com/django/django.git synced 2025-10-31 09:41:08 +00:00

Fixed #25367 -- Allowed boolean expressions in QuerySet.filter() and exclude().

This allows using expressions that have an output_field that is a
BooleanField to be used directly in a queryset filters, or in the
When() clauses of a Case() expression.

Thanks Josh Smeaton, Tim Graham, Simon Charette, Mariusz Felisiak, and
Adam Johnson for reviews.

Co-Authored-By: NyanKiyoshi <hello@vanille.bid>
This commit is contained in:
Matthew Schinckel
2017-02-27 19:31:52 +10:30
committed by Mariusz Felisiak
parent 069bee7c12
commit 4137fc2efc
10 changed files with 184 additions and 23 deletions

View File

@@ -37,7 +37,7 @@ class BasicExpressionsTests(TestCase):
ceo=Employee.objects.create(firstname="Joe", lastname="Smith", salary=10)
)
cls.foobar_ltd = Company.objects.create(
name="Foobar Ltd.", num_employees=3, num_chairs=4,
name="Foobar Ltd.", num_employees=3, num_chairs=4, based_in_eu=True,
ceo=Employee.objects.create(firstname="Frank", lastname="Meyer", salary=20)
)
cls.max = Employee.objects.create(firstname='Max', lastname='Mustermann', salary=30)
@@ -83,6 +83,14 @@ class BasicExpressionsTests(TestCase):
2,
)
def test_filtering_on_q_that_is_boolean(self):
self.assertEqual(
Company.objects.filter(
ExpressionWrapper(Q(num_employees__gt=3), output_field=models.BooleanField())
).count(),
2,
)
def test_filter_inter_attribute(self):
# We can filter on attribute relationships on same model obj, e.g.
# find companies where the number of employees is greater
@@ -642,6 +650,56 @@ class BasicExpressionsTests(TestCase):
with self.assertRaisesMessage(FieldError, "Cannot resolve keyword 'nope' into field."):
list(Company.objects.filter(ceo__pk=F('point_of_contact__nope')))
def test_exists_in_filter(self):
inner = Company.objects.filter(ceo=OuterRef('pk')).values('pk')
qs1 = Employee.objects.filter(Exists(inner))
qs2 = Employee.objects.annotate(found=Exists(inner)).filter(found=True)
self.assertCountEqual(qs1, qs2)
self.assertFalse(Employee.objects.exclude(Exists(inner)).exists())
self.assertCountEqual(qs2, Employee.objects.exclude(~Exists(inner)))
def test_subquery_in_filter(self):
inner = Company.objects.filter(ceo=OuterRef('pk')).values('based_in_eu')
self.assertSequenceEqual(
Employee.objects.filter(Subquery(inner)),
[self.foobar_ltd.ceo],
)
def test_case_in_filter_if_boolean_output_field(self):
is_ceo = Company.objects.filter(ceo=OuterRef('pk'))
is_poc = Company.objects.filter(point_of_contact=OuterRef('pk'))
qs = Employee.objects.filter(
Case(
When(Exists(is_ceo), then=True),
When(Exists(is_poc), then=True),
default=False,
output_field=models.BooleanField(),
),
)
self.assertSequenceEqual(qs, [self.example_inc.ceo, self.foobar_ltd.ceo, self.max])
def test_boolean_expression_combined(self):
is_ceo = Company.objects.filter(ceo=OuterRef('pk'))
is_poc = Company.objects.filter(point_of_contact=OuterRef('pk'))
self.gmbh.point_of_contact = self.max
self.gmbh.save()
self.assertSequenceEqual(
Employee.objects.filter(Exists(is_ceo) | Exists(is_poc)),
[self.example_inc.ceo, self.foobar_ltd.ceo, self.max],
)
self.assertSequenceEqual(
Employee.objects.filter(Exists(is_ceo) & Exists(is_poc)),
[self.max],
)
self.assertSequenceEqual(
Employee.objects.filter(Exists(is_ceo) & Q(salary__gte=30)),
[self.max],
)
self.assertSequenceEqual(
Employee.objects.filter(Exists(is_poc) | Q(salary__lt=15)),
[self.example_inc.ceo, self.max],
)
class IterableLookupInnerExpressionsTests(TestCase):
@classmethod