浏览代码

Fixed #28497 -- Restored the ability to use sliced QuerySets with __exact.

Regression in ec50937bcbe160e658ef881021402e156beb0eaf.

Thanks Simon Charette for review.
Tim Graham 7 年之前
父节点
当前提交
1b73ccc4bf
共有 3 个文件被更改,包括 39 次插入0 次删除
  1. 14 0
      django/db/models/lookups.py
  2. 3 0
      django/db/models/sql/query.py
  3. 22 0
      tests/lookup/tests.py

+ 14 - 0
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):

+ 3 - 0
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.

+ 22 - 0
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()