diff --git a/django/utils/functional.py b/django/utils/functional.py index 6d38f932f9..9d1d163447 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -324,6 +324,11 @@ class LazyObject: __str__ = new_method_proxy(str) __bool__ = new_method_proxy(bool) + # Numeric conversion support + __int__ = new_method_proxy(int) + __float__ = new_method_proxy(float) + __complex__ = new_method_proxy(complex) + # Introspection support __dir__ = new_method_proxy(dir) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index e57cad79ef..2c7236c4be 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -6,11 +6,12 @@ from operator import attrgetter from django.core.exceptions import EmptyResultSet, FieldError from django.db import DEFAULT_DB_ALIAS, connection -from django.db.models import Count, Exists, F, OuterRef, Q +from django.db.models import Count, Exists, F, OuterRef, Q, Subquery from django.db.models.sql.constants import LOUTER from django.db.models.sql.where import NothingNode, WhereNode from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext +from django.utils.functional import SimpleLazyObject from .models import ( FK1, Annotation, Article, Author, BaseA, Book, CategoryItem, @@ -2130,6 +2131,39 @@ class SubqueryTests(TestCase): ).order_by('id').values_list('id', flat=True), [2, 4] ) + def test_nested_subquery_with_simplelazyobject(self): + """ + Test that SimpleLazyObject works with nested subquery annotations. + Regression test for the issue where using SimpleLazyObject in a filter + with nested subquery annotation would fail with: + TypeError: int() argument must be a string, a bytes-like object or a + number, not 'SimpleLazyObject' + """ + # Create test data + category = NamedCategory.objects.create(id=5, name="test") + + # Create a nested subquery annotation + # This simulates the scenario in the bug report where we have: + # - An outer query on DumbCategory + # - Annotated with a subquery on Tag + # - That subquery is itself annotated with a subquery on another model + # - And we filter using a SimpleLazyObject + tag_subquery = Tag.objects.filter( + category=OuterRef('pk') + ).values('category') + + # Wrap the category id in a SimpleLazyObject + # This is the key part - wrapping a numeric value (like a User.id) + lazy_category_id = SimpleLazyObject(lambda: category.id) + + # This should not raise a TypeError + queryset = NamedCategory.objects.annotate( + tag_category=Subquery(tag_subquery) + ).filter(tag_category=lazy_category_id) + + # Force evaluation - this is where the bug would occur + str(queryset.query) + @skipUnlessDBFeature('allow_sliced_subqueries_with_in') class QuerySetBitwiseOperationTests(TestCase): diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index e5bccc6362..b622d3f44c 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -91,6 +91,24 @@ class LazyObjectTestCase(TestCase): for t in [True, 1, (1,), {1: 2}, [1], object(), {1}]: self.assertTrue(t) + def test_int(self): + obj = self.lazy_wrap(42) + self.assertEqual(int(obj), 42) + obj = self.lazy_wrap(3.14) + self.assertEqual(int(obj), 3) + + def test_float(self): + obj = self.lazy_wrap(42) + self.assertEqual(float(obj), 42.0) + obj = self.lazy_wrap(3.14) + self.assertEqual(float(obj), 3.14) + + def test_complex(self): + obj = self.lazy_wrap(42) + self.assertEqual(complex(obj), 42+0j) + obj = self.lazy_wrap(3.14) + self.assertEqual(complex(obj), 3.14+0j) + def test_dir(self): obj = self.lazy_wrap('foo') self.assertEqual(dir(obj), dir('foo'))