summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMatthew Schinckel <matt@schinckel.net>2017-02-27 19:31:52 +1030
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-08-29 09:45:29 +0200
commit4137fc2efce2dde48340728b8006fc6d66b9e3a5 (patch)
treedf3632a53ff2d1f7efccd501880601f29e06d54c /tests
parent069bee7c1232458a0f13c2e30daa8df99dbd3680 (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.py1
-rw-r--r--tests/expressions/tests.py60
-rw-r--r--tests/expressions_case/tests.py5
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):