summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-10-16 11:10:21 -0400
committerTim Graham <timograham@gmail.com>2017-10-16 15:40:08 -0400
commit9b3b7804d2408171cc365ad1f34402ef10d3a6b5 (patch)
tree737b633d8578ec7c12d9b2491b70154a854f6b97
parent04a92ebe494435f39e72aa0bd954472dcb6e28ae (diff)
[2.0.x] Fixed #28497 -- Restored the ability to use sliced QuerySets with __exact.
Regression in ec50937bcbe160e658ef881021402e156beb0eaf. Thanks Simon Charette for review. Backport of 1b73ccc4bf78af905f72f4658cf463f38ebf7b97 from master
-rw-r--r--django/db/models/lookups.py14
-rw-r--r--django/db/models/sql/query.py3
-rw-r--r--tests/lookup/tests.py22
3 files changed, 39 insertions, 0 deletions
diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py
index f79f435515..b32b05a2b6 100644
--- a/django/db/models/lookups.py
+++ b/django/db/models/lookups.py
@@ -245,6 +245,20 @@ class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
lookup_name = 'exact'
+ def process_rhs(self, compiler, connection):
+ from django.db.models.sql.query import Query
+ if isinstance(self.rhs, Query):
+ if self.rhs.has_limit_one():
+ # The subquery must select only the pk.
+ self.rhs.clear_select_clause()
+ self.rhs.add_fields(['pk'])
+ else:
+ raise ValueError(
+ 'The QuerySet value for an exact lookup must be limited to '
+ 'one result using slicing.'
+ )
+ return super().process_rhs(compiler, connection)
+
@Field.register_lookup
class IExact(BuiltinLookup):
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index a962aabdf1..372431c620 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1627,6 +1627,9 @@ class Query:
"""Clear any existing limits."""
self.low_mark, self.high_mark = 0, None
+ def has_limit_one(self):
+ return self.high_mark is not None and (self.high_mark - self.low_mark) == 1
+
def can_filter(self):
"""
Return True if adding filters to this instance is still possible.
diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py
index 7b08c778df..0161782dbe 100644
--- a/tests/lookup/tests.py
+++ b/tests/lookup/tests.py
@@ -848,6 +848,28 @@ class LookupTests(TestCase):
self.assertTrue(Season.objects.filter(nulled_text_field__nulled__exact=None))
self.assertTrue(Season.objects.filter(nulled_text_field__nulled=None))
+ def test_exact_sliced_queryset_limit_one(self):
+ self.assertCountEqual(
+ Article.objects.filter(author=Author.objects.all()[:1]),
+ [self.a1, self.a2, self.a3, self.a4]
+ )
+
+ def test_exact_sliced_queryset_limit_one_offset(self):
+ self.assertCountEqual(
+ Article.objects.filter(author=Author.objects.all()[1:2]),
+ [self.a5, self.a6, self.a7]
+ )
+
+ def test_exact_sliced_queryset_not_limited_to_one(self):
+ msg = (
+ 'The QuerySet value for an exact lookup must be limited to one '
+ 'result using slicing.'
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ list(Article.objects.filter(author=Author.objects.all()[:2]))
+ with self.assertRaisesMessage(ValueError, msg):
+ list(Article.objects.filter(author=Author.objects.all()[1:]))
+
def test_custom_field_none_rhs(self):
"""
__exact=value is transformed to __isnull=True if Field.get_prep_value()