From 97d7990abde3fe4b525ae83958fd0b52d6a1d13f Mon Sep 17 00:00:00 2001 From: Theo Alexiou Date: Sun, 13 Feb 2022 17:27:12 +0100 Subject: [PATCH] Fixed #28358 -- Prevented LazyObject from mimicking nonexistent attributes. Thanks Sergey Fedoseev for the initial patch. --- django/conf/__init__.py | 5 +++-- django/utils/functional.py | 9 +++++++++ tests/utils_tests/test_lazyobject.py | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index e1fb7f973f..55bf15a355 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -157,8 +157,9 @@ class LazySettings(LazyObject): def USE_L10N(self): stack = traceback.extract_stack() # Show a warning if the setting is used outside of Django. - # Stack index: -1 this line, -2 the caller. - filename, _, _, _ = stack[-2] + # Stack index: -1 this line, -2 the LazyObject __getattribute__(), + # -3 the caller. + filename, _, _, _ = stack[-3] if not filename.startswith(os.path.dirname(django.__file__)): warnings.warn( USE_L10N_DEPRECATED_MSG, diff --git a/django/utils/functional.py b/django/utils/functional.py index 2696dd49c5..8dd8a8e006 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -266,6 +266,7 @@ def new_method_proxy(func): self._setup() return func(self._wrapped, *args) + inner._mask_wrapped = False return inner @@ -286,6 +287,14 @@ class LazyObject: # override __copy__() and __deepcopy__() as well. self._wrapped = empty + def __getattribute__(self, name): + value = super().__getattribute__(name) + # If attribute is a proxy method, raise an AttributeError to call + # __getattr__() and use the wrapped object method. + if not getattr(value, "_mask_wrapped", True): + raise AttributeError + return value + __getattr__ = new_method_proxy(getattr) def __setattr__(self, name, value): diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index 0ff15469d4..161c9dbcec 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -32,6 +32,28 @@ class LazyObjectTestCase(TestCase): return AdHocLazyObject() + def test_getattribute(self): + """ + Proxy methods don't exist on wrapped objects unless they're set. + """ + attrs = [ + "__getitem__", + "__setitem__", + "__delitem__", + "__iter__", + "__len__", + "__contains__", + ] + foo = Foo() + obj = self.lazy_wrap(foo) + for attr in attrs: + with self.subTest(attr): + self.assertFalse(hasattr(obj, attr)) + setattr(foo, attr, attr) + obj_with_attr = self.lazy_wrap(foo) + self.assertTrue(hasattr(obj_with_attr, attr)) + self.assertEqual(getattr(obj_with_attr, attr), attr) + def test_getattr(self): obj = self.lazy_wrap(Foo()) self.assertEqual(obj.foo, "bar")