From 01c70ba14899409e86dc3f6c6bcae0afc48094e7 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Thu, 6 Nov 2025 14:24:28 +0100 Subject: [PATCH 1/4] Fixed unsafe variable interpolation in GitHub Action workflow. Thank you Davide Netti for the report and initial patch. Co-authored-by: Davide Netti --- .github/workflows/check_commit_messages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check_commit_messages.yml b/.github/workflows/check_commit_messages.yml index 701d6f058414..1a6d6d1958ca 100644 --- a/.github/workflows/check_commit_messages.yml +++ b/.github/workflows/check_commit_messages.yml @@ -24,8 +24,9 @@ jobs: echo "prefix=[$VERSION]" >> $GITHUB_OUTPUT - name: Check PR title prefix + env: + TITLE: ${{ github.event.pull_request.title }} run: | - TITLE="${{ github.event.pull_request.title }}" PREFIX="${{ steps.vars.outputs.prefix }}" if [[ "$TITLE" != "$PREFIX"* ]]; then echo "❌ PR title must start with the required prefix: $PREFIX" From e78420c2b8ef62acb6b7a37b71e3aa6833d630fb Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 4 Nov 2025 14:34:17 -0500 Subject: [PATCH 2/4] Refs #36680 -- Avoided manipulating PATH in AdminScriptTestCase. This mostly reverts 6436ec321073bf0622af815e0af08f54c97f9b30, which was fragile. Instead, if black is present, we use it to format the expected and actual results, instead of hard-coding the expected formatted value. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> --- tests/admin_scripts/tests.py | 73 ++++++++++++++---------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index c01a5571dcdc..0c2719456861 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -33,13 +33,11 @@ from django.core.management.commands.loaddata import Command as LoaddataCommand from django.core.management.commands.runserver import Command as RunserverCommand from django.core.management.commands.testserver import Command as TestserverCommand -from django.core.management.utils import find_formatters from django.db import ConnectionHandler, connection from django.db.migrations.recorder import MigrationRecorder from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings from django.test.utils import captured_stderr, captured_stdout from django.urls import path -from django.utils.functional import cached_property from django.utils.version import PY313, get_docs_version from django.views.static import serve @@ -49,6 +47,8 @@ SYSTEM_CHECK_MSG = "System check identified no issues" +HAS_BLACK = shutil.which("black") + class AdminScriptTestCase(SimpleTestCase): def setUp(self): @@ -112,21 +112,7 @@ def _ext_backend_paths(self): paths.append(os.path.dirname(backend_dir)) return paths - @cached_property - def path_without_formatters(self): - return os.pathsep.join( - [ - path_component - for path_component in os.environ.get("PATH", "").split(os.pathsep) - for formatter_path in find_formatters().values() - if formatter_path - and os.path.commonpath([path_component, formatter_path]) == os.sep - ] - ) - - def run_test( - self, args, settings_file=None, apps=None, umask=-1, discover_formatters=False - ): + def run_test(self, args, settings_file=None, apps=None, umask=-1): base_dir = os.path.dirname(self.test_dir) # The base dir for Django's tests is one level up. tests_dir = os.path.dirname(os.path.dirname(__file__)) @@ -148,8 +134,6 @@ def run_test( python_path.extend(ext_backend_base_dirs) test_environ["PYTHONPATH"] = os.pathsep.join(python_path) test_environ["PYTHONWARNINGS"] = "" - if not discover_formatters: - test_environ["PATH"] = self.path_without_formatters p = subprocess.run( [sys.executable, *args], @@ -161,19 +145,10 @@ def run_test( ) return p.stdout, p.stderr - def run_django_admin( - self, args, settings_file=None, umask=-1, discover_formatters=False - ): - return self.run_test( - ["-m", "django", *args], - settings_file, - umask=umask, - discover_formatters=discover_formatters, - ) + def run_django_admin(self, args, settings_file=None, umask=-1): + return self.run_test(["-m", "django", *args], settings_file, umask=umask) - def run_manage( - self, args, settings_file=None, manage_py=None, discover_formatters=False - ): + def run_manage(self, args, settings_file=None, manage_py=None): template_manage_py = ( os.path.join(os.path.dirname(__file__), manage_py) if manage_py @@ -192,11 +167,17 @@ def run_manage( with open(test_manage_py, "w") as fp: fp.write(manage_py_contents) - return self.run_test( - ["./manage.py", *args], - settings_file, - discover_formatters=discover_formatters, - ) + return self.run_test(["./manage.py", *args], settings_file) + + def assertInAfterFormatting(self, member, container, msg=None): + if HAS_BLACK: + import black + + # Black does not have a stable API, but this is still less fragile + # than attempting to filter out all paths where it is available. + member = black.format_str(member, mode=black.FileMode()) + + self.assertIn(member, container, msg=msg) def assertNoOutput(self, stream): "Utility assertion: assert that the given stream is empty" @@ -773,7 +754,7 @@ def test_setup_environ(self): with open(os.path.join(app_path, "apps.py")) as f: content = f.read() self.assertIn("class SettingsTestConfig(AppConfig)", content) - self.assertIn("name = 'settings_test'", content) + self.assertInAfterFormatting("name = 'settings_test'", content) def test_setup_environ_custom_template(self): """ @@ -798,7 +779,7 @@ def test_startapp_unicode_name(self): with open(os.path.join(app_path, "apps.py"), encoding="utf8") as f: content = f.read() self.assertIn("class こんにちはConfig(AppConfig)", content) - self.assertIn("name = 'こんにちは'", content) + self.assertInAfterFormatting("name = 'こんにちは'", content) def test_builtin_command(self): """ @@ -1960,7 +1941,7 @@ def setUp(self): def test_version(self): "version is handled as a special case" args = ["version"] - out, err = self.run_manage(args, discover_formatters=True) + out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, get_version()) @@ -2713,7 +2694,7 @@ def test_custom_project_template_non_python_files_not_formatted(self): args = ["startproject", "--template", template_path, "customtestproject"] testproject_dir = os.path.join(self.test_dir, "customtestproject") - _, err = self.run_django_admin(args, discover_formatters=True) + _, err = self.run_django_admin(args) self.assertNoOutput(err) with open( os.path.join(template_path, "additional_dir", "requirements.in") @@ -2808,7 +2789,7 @@ def serve_template(request, *args, **kwargs): f"{self.live_server_url}/user_agent_check/project_template.tgz" ) args = ["startproject", "--template", template_url, "urltestproject"] - _, err = self.run_django_admin(args, discover_formatters=True) + _, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertIn("Django/%s" % get_version(), user_agent) @@ -2879,8 +2860,10 @@ def test_custom_project_template_context_variables(self): test_manage_py = os.path.join(testproject_dir, "manage.py") with open(test_manage_py) as fp: content = fp.read() - self.assertIn('project_name = "another_project"', content) - self.assertIn('project_directory = "%s"' % testproject_dir, content) + self.assertInAfterFormatting('project_name = "another_project"', content) + self.assertInAfterFormatting( + 'project_directory = "%s"' % testproject_dir, content + ) def test_no_escaping_of_project_variables(self): "Make sure template context variables are not html escaped" @@ -2990,7 +2973,7 @@ def test_custom_project_template_hidden_directory_included(self): self.assertNoOutput(err) render_py_path = os.path.join(testproject_dir, ".hidden", "render.py") with open(render_py_path) as fp: - self.assertIn( + self.assertInAfterFormatting( f"# The {project_name} should be rendered.", fp.read(), ) @@ -3150,7 +3133,7 @@ def test_template(self): with open(os.path.join(app_path, "apps.py")) as f: content = f.read() self.assertIn("class NewAppConfig(AppConfig)", content) - self.assertIn("name = 'new_app'", content) + self.assertInAfterFormatting("name = 'new_app'", content) def test_creates_directory_when_custom_app_destination_missing(self): args = [ From fffa64abc3870989d62659453cb302857c539956 Mon Sep 17 00:00:00 2001 From: Kasyap Pentamaraju Date: Fri, 7 Nov 2025 10:31:53 +0530 Subject: [PATCH 3/4] Fixed #36715 -- Handled non-finite Decimals in intcomma filter. --- django/utils/numberformat.py | 3 +++ tests/humanize_tests/tests.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index f621ff64b494..c064ce691864 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -48,6 +48,9 @@ def format( if abs(number) < cutoff: number = Decimal("0") + if not number.is_finite(): + return str(number) + # Format values with more than 200 digits (an arbitrary cutoff) using # scientific notation to avoid high memory usage in {:f}'.format(). _, digits, exponent = number.as_tuple() diff --git a/tests/humanize_tests/tests.py b/tests/humanize_tests/tests.py index b9f1e58ef7da..7c2863d3c478 100644 --- a/tests/humanize_tests/tests.py +++ b/tests/humanize_tests/tests.py @@ -153,6 +153,9 @@ def test_intcomma(self): "-1234567.1234567", Decimal("1234567.1234567"), Decimal("-1234567.1234567"), + Decimal("Infinity"), + Decimal("-Infinity"), + Decimal("NaN"), None, "1234567", "-1234567", @@ -193,6 +196,9 @@ def test_intcomma(self): "-1,234,567.1234567", "1,234,567.1234567", "-1,234,567.1234567", + "Infinity", + "-Infinity", + "NaN", None, "1,234,567", "-1,234,567", From 2b0f24e6223bf7e294fba63741f58eb7b0bf49ff Mon Sep 17 00:00:00 2001 From: Clifford Gama Date: Mon, 10 Nov 2025 22:59:38 +0200 Subject: [PATCH 4/4] Fixed typo in docs/ref/databases.txt. --- docs/ref/databases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 9ea54297aaf2..e708e739372e 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -1223,7 +1223,7 @@ Subclassing the built-in database backends ========================================== Django comes with built-in database backends. You may subclass an existing -database backends to modify its behavior, features, or configuration. +database backend to modify its behavior, features, or configuration. Consider, for example, that you need to change a single database feature. First, you have to create a new directory with a ``base`` module in it. For