diff options
| author | Matthew Schinckel <matt@schinckel.net> | 2017-02-27 19:31:52 +1030 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2019-08-29 09:45:29 +0200 |
| commit | 4137fc2efce2dde48340728b8006fc6d66b9e3a5 (patch) | |
| tree | df3632a53ff2d1f7efccd501880601f29e06d54c /tests | |
| parent | 069bee7c1232458a0f13c2e30daa8df99dbd3680 (diff) | |
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>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/expressions/models.py | 1 | ||||
| -rw-r--r-- | tests/expressions/tests.py | 60 | ||||
| -rw-r--r-- | tests/expressions_case/tests.py | 5 |
3 files changed, 64 insertions, 2 deletions
diff --git a/tests/expressions/models.py b/tests/expressions/models.py index 33f7850ac1..a81ba9e16b 100644 --- a/tests/expressions/models.py +++ b/tests/expressions/models.py @@ -34,6 +34,7 @@ class Company(models.Model): related_name='company_point_of_contact_set', null=True, ) + based_in_eu = models.BooleanField(default=False) def __str__(self): return self.name diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index eaa3471177..f50c634014 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -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 diff --git a/tests/expressions_case/tests.py b/tests/expressions_case/tests.py index f1255cb8e9..e3f4775cf2 100644 --- a/tests/expressions_case/tests.py +++ b/tests/expressions_case/tests.py @@ -1327,7 +1327,10 @@ class CaseWhenTests(SimpleTestCase): Case(When(Q(pk__in=[])), object()) def test_invalid_when_constructor_args(self): - msg = '__init__() takes either a Q object or lookups as keyword arguments' + msg = ( + 'When() supports a Q object, a boolean expression, or lookups as ' + 'a condition.' + ) with self.assertRaisesMessage(TypeError, msg): When(condition=object()) with self.assertRaisesMessage(TypeError, msg): |
