Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions django/utils/csp.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,16 @@ class LazyNonce(SimpleLazyObject):
"""

def __init__(self):
super().__init__(self._generate)

def _generate(self):
return secrets.token_urlsafe(16)
super().__init__(generate_nonce)

def __bool__(self):
return self._wrapped is not empty


def generate_nonce():
return secrets.token_urlsafe(16)


def build_policy(config, nonce=None):
policy = []

Expand Down
6 changes: 5 additions & 1 deletion django/utils/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,11 @@ def _setup(self):
# without evaluating the wrapped object.
def __repr__(self):
if self._wrapped is empty:
repr_attr = self._setupfunc
# Avoid recursion if setupfunc is a bound method of this instance.
if getattr(self._setupfunc, "__self__", None) is self:
repr_attr = f"<bound method {self._setupfunc.__name__}>"
else:
repr_attr = self._setupfunc
else:
repr_attr = self._wrapped
return "<%s: %r>" % (type(self).__name__, repr_attr)
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/6.0.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ Bugfixes

* Fixed a visual regression in Django 6.0 for admin form fields grouped under a
``<fieldset>`` in Safari (:ticket:`36807`).

* Fixed a crash in Django 6.0 caused by infinite recursion when calling
``repr()`` on an unevaluated ``django.utils.csp.LazyNonce`` instance
(:ticket:`36810`).
10 changes: 9 additions & 1 deletion tests/utils_tests/test_csp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest.mock import patch

from django.test import SimpleTestCase
from django.utils.csp import CSP, LazyNonce, build_policy
from django.utils.csp import CSP, LazyNonce, build_policy, generate_nonce
from django.utils.functional import empty

basic_config = {
Expand Down Expand Up @@ -155,6 +155,7 @@ def memento_token_urlsafe(size):
self.assertTrue(nonce)
self.assertEqual(nonce, val)
self.assertIsInstance(nonce, str)
self.assertEqual(repr(nonce), f"<LazyNonce: '{nonce}'>")
self.assertEqual(len(val), 22) # Based on secrets.token_urlsafe of 16 bytes.
self.assertEqual(generated_tokens, [nonce])
# Also test the wrapped value.
Expand All @@ -166,3 +167,10 @@ def test_returns_same_value(self):
second = str(nonce)

self.assertEqual(first, second)

def test_repr(self):
nonce = LazyNonce()
self.assertEqual(repr(nonce), f"<LazyNonce: {repr(generate_nonce)}>")

str(nonce) # Force nonce generation.
self.assertRegex(repr(nonce), r"<LazyNonce: '[^']+'>")
16 changes: 16 additions & 0 deletions tests/utils_tests/test_lazyobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,22 @@ def test_repr(self):
self.assertIsInstance(obj._wrapped, int)
self.assertEqual(repr(obj), "<SimpleLazyObject: 42>")

def test_repr_bound_method(self):

class MyLazyGenerator(SimpleLazyObject):
def __init__(self):
super().__init__(self._generate)

def _generate(self):
return "test-generated-value"

obj = MyLazyGenerator()
self.assertEqual(repr(obj), "<MyLazyGenerator: '<bound method _generate>'>")
self.assertIs(obj._wrapped, empty) # The evaluation hasn't happened.

self.assertEqual(str(obj), "test-generated-value") # Evaluate.
self.assertEqual(repr(obj), "<MyLazyGenerator: 'test-generated-value'>")

def test_add(self):
obj1 = self.lazy_wrap(1)
self.assertEqual(obj1 + 1, 2)
Expand Down
Loading