diff options
| author | Ian Foote <python@ian.feete.org> | 2021-04-02 18:25:20 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2021-07-09 11:43:06 +0200 |
| commit | f42ccdd835e5b3f0914b5e6f87621c648136ea36 (patch) | |
| tree | 49f0a1ecbd05b4d8d0049cec21ad564a663b427f /tests | |
| parent | f5dccbafb957841d0867f0b153d7f7123f0ec83d (diff) | |
Fixed #27021 -- Allowed lookup expressions in annotations, aggregations, and QuerySet.filter().
Thanks Hannes Ljungberg and Simon Charette for reviews.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/lookup/tests.py | 161 |
1 files changed, 159 insertions, 2 deletions
diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index e38eae087c..168a621a7e 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -6,9 +6,13 @@ from operator import attrgetter from django.core.exceptions import FieldError from django.db import connection, models from django.db.models import ( - BooleanField, Exists, ExpressionWrapper, F, Max, OuterRef, Q, + BooleanField, Case, Exists, ExpressionWrapper, F, Max, OuterRef, Q, + Subquery, Value, When, +) +from django.db.models.functions import Cast, Substr +from django.db.models.lookups import ( + Exact, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, ) -from django.db.models.functions import Substr from django.test import TestCase, skipUnlessDBFeature from django.test.utils import isolate_apps @@ -1020,3 +1024,156 @@ class LookupTests(TestCase): )), [stock_1, stock_2], ) + + +class LookupQueryingTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.s1 = Season.objects.create(year=1942, gt=1942) + cls.s2 = Season.objects.create(year=1842, gt=1942) + cls.s3 = Season.objects.create(year=2042, gt=1942) + + def test_annotate(self): + qs = Season.objects.annotate(equal=Exact(F('year'), 1942)) + self.assertCountEqual( + qs.values_list('year', 'equal'), + ((1942, True), (1842, False), (2042, False)), + ) + + def test_alias(self): + qs = Season.objects.alias(greater=GreaterThan(F('year'), 1910)) + self.assertCountEqual(qs.filter(greater=True), [self.s1, self.s3]) + + def test_annotate_value_greater_than_value(self): + qs = Season.objects.annotate(greater=GreaterThan(Value(40), Value(30))) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, True), (1842, True), (2042, True)), + ) + + def test_annotate_field_greater_than_field(self): + qs = Season.objects.annotate(greater=GreaterThan(F('year'), F('gt'))) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, False), (1842, False), (2042, True)), + ) + + def test_annotate_field_greater_than_value(self): + qs = Season.objects.annotate(greater=GreaterThan(F('year'), Value(1930))) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, True), (1842, False), (2042, True)), + ) + + def test_annotate_field_greater_than_literal(self): + qs = Season.objects.annotate(greater=GreaterThan(F('year'), 1930)) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, True), (1842, False), (2042, True)), + ) + + def test_annotate_literal_greater_than_field(self): + qs = Season.objects.annotate(greater=GreaterThan(1930, F('year'))) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, False), (1842, True), (2042, False)), + ) + + def test_annotate_less_than_float(self): + qs = Season.objects.annotate(lesser=LessThan(F('year'), 1942.1)) + self.assertCountEqual( + qs.values_list('year', 'lesser'), + ((1942, True), (1842, True), (2042, False)), + ) + + def test_annotate_greater_than_or_equal(self): + qs = Season.objects.annotate(greater=GreaterThanOrEqual(F('year'), 1942)) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, True), (1842, False), (2042, True)), + ) + + def test_annotate_greater_than_or_equal_float(self): + qs = Season.objects.annotate(greater=GreaterThanOrEqual(F('year'), 1942.1)) + self.assertCountEqual( + qs.values_list('year', 'greater'), + ((1942, False), (1842, False), (2042, True)), + ) + + def test_combined_lookups(self): + expression = Exact(F('year'), 1942) | GreaterThan(F('year'), 1942) + qs = Season.objects.annotate(gte=expression) + self.assertCountEqual( + qs.values_list('year', 'gte'), + ((1942, True), (1842, False), (2042, True)), + ) + + def test_lookup_in_filter(self): + qs = Season.objects.filter(GreaterThan(F('year'), 1910)) + self.assertCountEqual(qs, [self.s1, self.s3]) + + def test_filter_lookup_lhs(self): + qs = Season.objects.annotate(before_20=LessThan(F('year'), 2000)).filter( + before_20=LessThan(F('year'), 1900), + ) + self.assertCountEqual(qs, [self.s2, self.s3]) + + def test_filter_wrapped_lookup_lhs(self): + qs = Season.objects.annotate(before_20=ExpressionWrapper( + Q(year__lt=2000), + output_field=BooleanField(), + )).filter(before_20=LessThan(F('year'), 1900)).values_list('year', flat=True) + self.assertCountEqual(qs, [1842, 2042]) + + def test_filter_exists_lhs(self): + qs = Season.objects.annotate(before_20=Exists( + Season.objects.filter(pk=OuterRef('pk'), year__lt=2000), + )).filter(before_20=LessThan(F('year'), 1900)) + self.assertCountEqual(qs, [self.s2, self.s3]) + + def test_filter_subquery_lhs(self): + qs = Season.objects.annotate(before_20=Subquery( + Season.objects.filter(pk=OuterRef('pk')).values( + lesser=LessThan(F('year'), 2000), + ), + )).filter(before_20=LessThan(F('year'), 1900)) + self.assertCountEqual(qs, [self.s2, self.s3]) + + def test_combined_lookups_in_filter(self): + expression = Exact(F('year'), 1942) | GreaterThan(F('year'), 1942) + qs = Season.objects.filter(expression) + self.assertCountEqual(qs, [self.s1, self.s3]) + + def test_combined_annotated_lookups_in_filter(self): + expression = Exact(F('year'), 1942) | GreaterThan(F('year'), 1942) + qs = Season.objects.annotate(gte=expression).filter(gte=True) + self.assertCountEqual(qs, [self.s1, self.s3]) + + def test_combined_annotated_lookups_in_filter_false(self): + expression = Exact(F('year'), 1942) | GreaterThan(F('year'), 1942) + qs = Season.objects.annotate(gte=expression).filter(gte=False) + self.assertSequenceEqual(qs, [self.s2]) + + def test_lookup_in_order_by(self): + qs = Season.objects.order_by(LessThan(F('year'), 1910), F('year')) + self.assertSequenceEqual(qs, [self.s1, self.s3, self.s2]) + + @skipUnlessDBFeature('supports_boolean_expr_in_select_clause') + def test_aggregate_combined_lookup(self): + expression = Cast(GreaterThan(F('year'), 1900), models.IntegerField()) + qs = Season.objects.aggregate(modern=models.Sum(expression)) + self.assertEqual(qs['modern'], 2) + + def test_conditional_expression(self): + qs = Season.objects.annotate(century=Case( + When( + GreaterThan(F('year'), 1900) & LessThanOrEqual(F('year'), 2000), + then=Value('20th'), + ), + default=Value('other'), + )).values('year', 'century') + self.assertCountEqual(qs, [ + {'year': 1942, 'century': '20th'}, + {'year': 1842, 'century': 'other'}, + {'year': 2042, 'century': 'other'}, + ]) |
