diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 56ff7bb5add9..82498aa662f2 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -262,6 +262,8 @@ def merge(*lists): return list(dict.fromkeys(chain.from_iterable(filter(None, lists)))) def __add__(self, other): + if not isinstance(other, Media): + return NotImplemented combined = Media() combined._css_lists = self._css_lists[:] combined._js_lists = self._js_lists[:] diff --git a/django/utils/html.py b/django/utils/html.py index 71122ac79e8c..63db147ff147 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -278,7 +278,7 @@ def unquote_quote(segment): class CountsDict(dict): def __init__(self, *args, word, **kwargs): - super().__init__(*args, *kwargs) + super().__init__(*args, **kwargs) self.word = word def __missing__(self, key): diff --git a/docs/make.bat b/docs/make.bat index 5c0627897214..bed2f7e0d374 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -47,7 +47,7 @@ if "%1" == "clean" ( ) if "%1" == "html" ( - %SPHINXBUILD% -b djangohtml %ALLSPHINXOPTS% %BUILDDIR%/html + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. diff --git a/tests/forms_tests/tests/test_media.py b/tests/forms_tests/tests/test_media.py index b24ed948a955..854e52d95c9f 100644 --- a/tests/forms_tests/tests/test_media.py +++ b/tests/forms_tests/tests/test_media.py @@ -859,6 +859,13 @@ def test_add_empty(self): self.assertEqual(merged._css_lists, [{"screen": ["a.css"]}]) self.assertEqual(merged._js_lists, [["a"]]) + def test_add_invalid_type(self): + class InvalidType: + pass + + with self.assertRaises(TypeError): + Media() + InvalidType() + def test_render_js_with_attrs(self): media = Media(js=[Script("/path/to/js", integrity="sha256-abc")]) self.assertHTMLEqual( diff --git a/tests/signals/tests.py b/tests/signals/tests.py index e304424eb8af..ea2fe39fb8e8 100644 --- a/tests/signals/tests.py +++ b/tests/signals/tests.py @@ -1,4 +1,5 @@ import contextvars +import gc from inspect import markcoroutinefunction from unittest import mock @@ -14,26 +15,34 @@ class BaseSignalSetup: + def _signal_counts(self): + # Clear dead receivers before counting. + counts = [] + for signal in ( + signals.pre_save, + signals.post_save, + signals.pre_delete, + signals.post_delete, + ): + with signal.lock: + signal._clear_dead_receivers() + counts.append(len(signal.receivers)) + return tuple(counts) + def setUp(self): - # Save up the number of connected signals so that we can check at the - # end that all the signals we register get properly unregistered - # (#9989) - self.pre_signals = ( - len(signals.pre_save.receivers), - len(signals.post_save.receivers), - len(signals.pre_delete.receivers), - len(signals.post_delete.receivers), - ) + # The count comparison in tearDown() is a regression check for + # Signal.disconnect() failing to remove entries (#9989). Collect first + # so the baseline contains only reachable receivers; a receiver + # connected elsewhere in the suite and then garbage-collected could + # linger as a dead weakref, inflating the count (#29187). No collection + # in tearDown(): a weak receiver kept alive by a cycle should be + # counted, to catch a forgotten disconnect. + gc.collect() + self.pre_signals = self._signal_counts() def tearDown(self): # All our signals got disconnected properly. - post_signals = ( - len(signals.pre_save.receivers), - len(signals.post_save.receivers), - len(signals.pre_delete.receivers), - len(signals.post_delete.receivers), - ) - self.assertEqual(self.pre_signals, post_signals) + self.assertEqual(self.pre_signals, self._signal_counts()) class SignalTests(BaseSignalSetup, TestCase):