diff options
| author | Simon Charette <charette.s@gmail.com> | 2025-01-27 23:10:13 -0500 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-02-11 09:08:35 +0100 |
| commit | 41239fe34d64e801212dccaa4585e4802d0fac68 (patch) | |
| tree | 5a7375f6945d53932f682fa894dd856d22129327 /tests | |
| parent | 0597e8ad1e55b565292ead732916aa0e39bdf37b (diff) | |
Fixed #36149 -- Allowed subquery values against tuple exact and in lookups.
Non-tuple exact and in lookups have specialized logic for subqueries that can
be adapted to properly assign select mask if unspecified and ensure the number
of involved members are matching on both side of the operator.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/composite_pk/models/tenant.py | 1 | ||||
| -rw-r--r-- | tests/composite_pk/test_filter.py | 58 | ||||
| -rw-r--r-- | tests/composite_pk/tests.py | 9 | ||||
| -rw-r--r-- | tests/foreign_object/test_tuple_lookups.py | 18 | ||||
| -rw-r--r-- | tests/lookup/tests.py | 16 | ||||
| -rw-r--r-- | tests/queries/tests.py | 14 |
6 files changed, 83 insertions, 33 deletions
diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py index 6286ed2354..c85869afa7 100644 --- a/tests/composite_pk/models/tenant.py +++ b/tests/composite_pk/models/tenant.py @@ -44,6 +44,7 @@ class Comment(models.Model): related_name="comments", ) text = models.TextField(default="", blank=True) + integer = models.IntegerField(default=0) class Post(models.Model): diff --git a/tests/composite_pk/test_filter.py b/tests/composite_pk/test_filter.py index fe942b9e5b..d4c6ef13e0 100644 --- a/tests/composite_pk/test_filter.py +++ b/tests/composite_pk/test_filter.py @@ -10,7 +10,7 @@ from django.db.models import ( ) from django.db.models.functions import Cast from django.db.models.lookups import Exact -from django.test import TestCase +from django.test import TestCase, skipUnlessDBFeature from .models import Comment, Tenant, User @@ -182,6 +182,30 @@ class CompositePKFilterTests(TestCase): Comment.objects.filter(pk__in=pks).order_by("pk"), objs ) + def test_filter_comments_by_pk_in_subquery(self): + self.assertSequenceEqual( + Comment.objects.filter( + pk__in=Comment.objects.filter(pk=self.comment_1.pk), + ), + [self.comment_1], + ) + self.assertSequenceEqual( + Comment.objects.filter( + pk__in=Comment.objects.filter(pk=self.comment_1.pk).values( + "tenant_id", "id" + ), + ), + [self.comment_1], + ) + self.comment_2.integer = self.comment_1.id + self.comment_2.save() + self.assertSequenceEqual( + Comment.objects.filter( + pk__in=Comment.objects.values("tenant_id", "integer"), + ), + [self.comment_1], + ) + def test_filter_comments_by_user_and_order_by_pk_asc(self): self.assertSequenceEqual( Comment.objects.filter(user=self.user_1).order_by("pk"), @@ -440,16 +464,40 @@ class CompositePKFilterTests(TestCase): queryset = Comment.objects.filter(**{f"id{lookup}": subquery}) self.assertEqual(queryset.count(), expected_count) - def test_non_outer_ref_subquery(self): - # If rhs is any non-OuterRef object with an as_sql() function. + def test_unsupported_rhs(self): pk = Exact(F("tenant_id"), 1) msg = ( - "'exact' subquery lookup of 'pk' only supports OuterRef objects " - "(received 'Exact')" + "'exact' subquery lookup of 'pk' only supports OuterRef " + "and QuerySet objects (received 'Exact')" ) with self.assertRaisesMessage(ValueError, msg): Comment.objects.filter(pk=pk) + @skipUnlessDBFeature("allow_sliced_subqueries_with_in") + def test_filter_comments_by_pk_exact_subquery(self): + self.assertSequenceEqual( + Comment.objects.filter( + pk=Comment.objects.filter(pk=self.comment_1.pk)[:1], + ), + [self.comment_1], + ) + self.assertSequenceEqual( + Comment.objects.filter( + pk__in=Comment.objects.filter(pk=self.comment_1.pk).values( + "tenant_id", "id" + )[:1], + ), + [self.comment_1], + ) + self.comment_2.integer = self.comment_1.id + self.comment_2.save() + self.assertSequenceEqual( + Comment.objects.filter( + pk__in=Comment.objects.values("tenant_id", "integer"), + )[:1], + [self.comment_1], + ) + def test_outer_ref_not_composite_pk(self): subquery = Comment.objects.filter(pk=OuterRef("id")).values("id") queryset = Comment.objects.filter(id=Subquery(subquery)) diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py index 6b09480fb0..18fa53d9c0 100644 --- a/tests/composite_pk/tests.py +++ b/tests/composite_pk/tests.py @@ -109,13 +109,10 @@ class CompositePKTests(TestCase): def test_composite_pk_in_fields(self): user_fields = {f.name for f in User._meta.get_fields()} - self.assertEqual(user_fields, {"pk", "tenant", "id", "email", "comments"}) + self.assertTrue({"pk", "tenant", "id"}.issubset(user_fields)) comment_fields = {f.name for f in Comment._meta.get_fields()} - self.assertEqual( - comment_fields, - {"pk", "tenant", "id", "user_id", "user", "text"}, - ) + self.assertTrue({"pk", "tenant", "id"}.issubset(comment_fields)) def test_pk_field(self): pk = User._meta.get_field("pk") @@ -174,7 +171,7 @@ class CompositePKTests(TestCase): self.assertEqual(user.email, self.user.email) def test_model_forms(self): - fields = ["tenant", "id", "user_id", "text"] + fields = ["tenant", "id", "user_id", "text", "integer"] self.assertEqual(list(CommentForm.base_fields), fields) form = modelform_factory(Comment, fields="__all__") diff --git a/tests/foreign_object/test_tuple_lookups.py b/tests/foreign_object/test_tuple_lookups.py index 42717c4f11..008f118994 100644 --- a/tests/foreign_object/test_tuple_lookups.py +++ b/tests/foreign_object/test_tuple_lookups.py @@ -63,9 +63,11 @@ class TupleLookupsTests(TestCase): ) def test_exact_subquery(self): - with self.assertRaisesMessage( - ValueError, "'exact' doesn't support multi-column subqueries." - ): + msg = ( + "The QuerySet value for the exact lookup must have 2 selected " + "fields (received 1)" + ) + with self.assertRaisesMessage(ValueError, msg): subquery = Customer.objects.filter(id=self.customer_1.id)[:1] self.assertSequenceEqual( Contact.objects.filter(customer=subquery).order_by("id"), () @@ -140,11 +142,11 @@ class TupleLookupsTests(TestCase): def test_tuple_in_subquery_must_have_2_fields(self): lhs = (F("customer_code"), F("company_code")) rhs = Customer.objects.values_list("customer_id").query - with self.assertRaisesMessage( - ValueError, - "'in' subquery lookup of ('customer_code', 'company_code') " - "must have 2 fields (received 1)", - ): + msg = ( + "The QuerySet value for the 'in' lookup must have 2 selected " + "fields (received 1)" + ) + with self.assertRaisesMessage(ValueError, msg): TupleIn(lhs, rhs) def test_tuple_in_subquery(self): diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index df96546d04..e19fbca521 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -789,6 +789,14 @@ class LookupTests(TestCase): sql = ctx.captured_queries[0]["sql"] self.assertIn("IN (%s)" % self.a1.pk, sql) + def test_in_select_mismatch(self): + msg = ( + "The QuerySet value for the 'in' lookup must have 1 " + "selected fields (received 2)" + ) + with self.assertRaisesMessage(ValueError, msg): + Article.objects.filter(id__in=Article.objects.values("id", "headline")) + def test_error_messages(self): # Programming errors are pointed out with nice error messages with self.assertRaisesMessage( @@ -1364,6 +1372,14 @@ class LookupTests(TestCase): authors = Author.objects.filter(id=authors_max_ids[:1]) self.assertEqual(authors.get(), newest_author) + def test_exact_query_rhs_with_selected_columns_mismatch(self): + msg = ( + "The QuerySet value for the exact lookup must have 1 " + "selected fields (received 2)" + ) + with self.assertRaisesMessage(ValueError, msg): + Author.objects.filter(id=Author.objects.values("id", "name")[:1]) + def test_isnull_non_boolean_value(self): msg = "The QuerySet value for an isnull lookup must be True or False." tests = [ diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 45866fd50f..c429a93af3 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -922,20 +922,6 @@ class Queries1Tests(TestCase): [self.t2, self.t3], ) - # Multi-valued values() and values_list() querysets should raise errors. - with self.assertRaisesMessage( - TypeError, "Cannot use multi-field values as a filter value." - ): - Tag.objects.filter( - name__in=Tag.objects.filter(parent=self.t1).values("name", "id") - ) - with self.assertRaisesMessage( - TypeError, "Cannot use multi-field values as a filter value." - ): - Tag.objects.filter( - name__in=Tag.objects.filter(parent=self.t1).values_list("name", "id") - ) - def test_ticket9985(self): # qs.values_list(...).values(...) combinations should work. self.assertSequenceEqual( |
