Skip to content

Commit eca3d77

Browse files
committed
Merge remote-tracking branch 'upstream/main'
* upstream/main: Fixed typo in docs/ref/databases.txt. Fixed #36715 -- Handled non-finite Decimals in intcomma filter. Refs #36680 -- Avoided manipulating PATH in AdminScriptTestCase. Fixed unsafe variable interpolation in GitHub Action workflow. Clarified "get_db_prep_value" default result in docs/ref/models/fields.txt. Clarified EmailValidator docs to specify it validates an email address.
2 parents 1f47cda + 2b0f24e commit eca3d77

File tree

7 files changed

+44
-51
lines changed

7 files changed

+44
-51
lines changed

.github/workflows/check_commit_messages.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ jobs:
2424
echo "prefix=[$VERSION]" >> $GITHUB_OUTPUT
2525
2626
- name: Check PR title prefix
27+
env:
28+
TITLE: ${{ github.event.pull_request.title }}
2729
run: |
28-
TITLE="${{ github.event.pull_request.title }}"
2930
PREFIX="${{ steps.vars.outputs.prefix }}"
3031
if [[ "$TITLE" != "$PREFIX"* ]]; then
3132
echo "❌ PR title must start with the required prefix: $PREFIX"

django/utils/numberformat.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ def format(
4848
if abs(number) < cutoff:
4949
number = Decimal("0")
5050

51+
if not number.is_finite():
52+
return str(number)
53+
5154
# Format values with more than 200 digits (an arbitrary cutoff) using
5255
# scientific notation to avoid high memory usage in {:f}'.format().
5356
_, digits, exponent = number.as_tuple()

docs/ref/databases.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,7 @@ Subclassing the built-in database backends
12231223
==========================================
12241224

12251225
Django comes with built-in database backends. You may subclass an existing
1226-
database backends to modify its behavior, features, or configuration.
1226+
database backend to modify its behavior, features, or configuration.
12271227

12281228
Consider, for example, that you need to change a single database feature.
12291229
First, you have to create a new directory with a ``base`` module in it. For

docs/ref/models/fields.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2523,8 +2523,8 @@ Field API reference
25232523
.. method:: get_db_prep_value(value, connection, prepared=False)
25242524

25252525
Converts ``value`` to a backend-specific value. By default it returns
2526-
``value`` if ``prepared=True`` and :meth:`~Field.get_prep_value` if is
2527-
``False``.
2526+
``value`` if ``prepared=True``, and :meth:`get_prep_value(value)
2527+
<Field.get_prep_value>` otherwise.
25282528

25292529
See :ref:`converting-query-values-to-database-values` for usage.
25302530

docs/ref/validators.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ to, or in lieu of custom ``field.clean()`` methods.
133133
:param code: If not ``None``, overrides :attr:`code`.
134134
:param allowlist: If not ``None``, overrides :attr:`allowlist`.
135135

136-
An :class:`EmailValidator` ensures that a value looks like an email, and
137-
raises a :exc:`~django.core.exceptions.ValidationError` with
136+
An :class:`EmailValidator` ensures that a value looks like an email
137+
address, and raises a :exc:`~django.core.exceptions.ValidationError` with
138138
:attr:`message` and :attr:`code` if it doesn't. Values longer than 320
139139
characters are always considered invalid.
140140

tests/admin_scripts/tests.py

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,11 @@
3333
from django.core.management.commands.loaddata import Command as LoaddataCommand
3434
from django.core.management.commands.runserver import Command as RunserverCommand
3535
from django.core.management.commands.testserver import Command as TestserverCommand
36-
from django.core.management.utils import find_formatters
3736
from django.db import ConnectionHandler, connection
3837
from django.db.migrations.recorder import MigrationRecorder
3938
from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings
4039
from django.test.utils import captured_stderr, captured_stdout
4140
from django.urls import path
42-
from django.utils.functional import cached_property
4341
from django.utils.version import PY313, get_docs_version
4442
from django.views.static import serve
4543

@@ -49,6 +47,8 @@
4947

5048
SYSTEM_CHECK_MSG = "System check identified no issues"
5149

50+
HAS_BLACK = shutil.which("black")
51+
5252

5353
class AdminScriptTestCase(SimpleTestCase):
5454
def setUp(self):
@@ -112,21 +112,7 @@ def _ext_backend_paths(self):
112112
paths.append(os.path.dirname(backend_dir))
113113
return paths
114114

115-
@cached_property
116-
def path_without_formatters(self):
117-
return os.pathsep.join(
118-
[
119-
path_component
120-
for path_component in os.environ.get("PATH", "").split(os.pathsep)
121-
for formatter_path in find_formatters().values()
122-
if formatter_path
123-
and os.path.commonpath([path_component, formatter_path]) == os.sep
124-
]
125-
)
126-
127-
def run_test(
128-
self, args, settings_file=None, apps=None, umask=-1, discover_formatters=False
129-
):
115+
def run_test(self, args, settings_file=None, apps=None, umask=-1):
130116
base_dir = os.path.dirname(self.test_dir)
131117
# The base dir for Django's tests is one level up.
132118
tests_dir = os.path.dirname(os.path.dirname(__file__))
@@ -148,8 +134,6 @@ def run_test(
148134
python_path.extend(ext_backend_base_dirs)
149135
test_environ["PYTHONPATH"] = os.pathsep.join(python_path)
150136
test_environ["PYTHONWARNINGS"] = ""
151-
if not discover_formatters:
152-
test_environ["PATH"] = self.path_without_formatters
153137

154138
p = subprocess.run(
155139
[sys.executable, *args],
@@ -161,19 +145,10 @@ def run_test(
161145
)
162146
return p.stdout, p.stderr
163147

164-
def run_django_admin(
165-
self, args, settings_file=None, umask=-1, discover_formatters=False
166-
):
167-
return self.run_test(
168-
["-m", "django", *args],
169-
settings_file,
170-
umask=umask,
171-
discover_formatters=discover_formatters,
172-
)
148+
def run_django_admin(self, args, settings_file=None, umask=-1):
149+
return self.run_test(["-m", "django", *args], settings_file, umask=umask)
173150

174-
def run_manage(
175-
self, args, settings_file=None, manage_py=None, discover_formatters=False
176-
):
151+
def run_manage(self, args, settings_file=None, manage_py=None):
177152
template_manage_py = (
178153
os.path.join(os.path.dirname(__file__), manage_py)
179154
if manage_py
@@ -192,11 +167,17 @@ def run_manage(
192167
with open(test_manage_py, "w") as fp:
193168
fp.write(manage_py_contents)
194169

195-
return self.run_test(
196-
["./manage.py", *args],
197-
settings_file,
198-
discover_formatters=discover_formatters,
199-
)
170+
return self.run_test(["./manage.py", *args], settings_file)
171+
172+
def assertInAfterFormatting(self, member, container, msg=None):
173+
if HAS_BLACK:
174+
import black
175+
176+
# Black does not have a stable API, but this is still less fragile
177+
# than attempting to filter out all paths where it is available.
178+
member = black.format_str(member, mode=black.FileMode())
179+
180+
self.assertIn(member, container, msg=msg)
200181

201182
def assertNoOutput(self, stream):
202183
"Utility assertion: assert that the given stream is empty"
@@ -773,7 +754,7 @@ def test_setup_environ(self):
773754
with open(os.path.join(app_path, "apps.py")) as f:
774755
content = f.read()
775756
self.assertIn("class SettingsTestConfig(AppConfig)", content)
776-
self.assertIn("name = 'settings_test'", content)
757+
self.assertInAfterFormatting("name = 'settings_test'", content)
777758

778759
def test_setup_environ_custom_template(self):
779760
"""
@@ -798,7 +779,7 @@ def test_startapp_unicode_name(self):
798779
with open(os.path.join(app_path, "apps.py"), encoding="utf8") as f:
799780
content = f.read()
800781
self.assertIn("class こんにちはConfig(AppConfig)", content)
801-
self.assertIn("name = 'こんにちは'", content)
782+
self.assertInAfterFormatting("name = 'こんにちは'", content)
802783

803784
def test_builtin_command(self):
804785
"""
@@ -1960,7 +1941,7 @@ def setUp(self):
19601941
def test_version(self):
19611942
"version is handled as a special case"
19621943
args = ["version"]
1963-
out, err = self.run_manage(args, discover_formatters=True)
1944+
out, err = self.run_manage(args)
19641945
self.assertNoOutput(err)
19651946
self.assertOutput(out, get_version())
19661947

@@ -2713,7 +2694,7 @@ def test_custom_project_template_non_python_files_not_formatted(self):
27132694
args = ["startproject", "--template", template_path, "customtestproject"]
27142695
testproject_dir = os.path.join(self.test_dir, "customtestproject")
27152696

2716-
_, err = self.run_django_admin(args, discover_formatters=True)
2697+
_, err = self.run_django_admin(args)
27172698
self.assertNoOutput(err)
27182699
with open(
27192700
os.path.join(template_path, "additional_dir", "requirements.in")
@@ -2808,7 +2789,7 @@ def serve_template(request, *args, **kwargs):
28082789
f"{self.live_server_url}/user_agent_check/project_template.tgz"
28092790
)
28102791
args = ["startproject", "--template", template_url, "urltestproject"]
2811-
_, err = self.run_django_admin(args, discover_formatters=True)
2792+
_, err = self.run_django_admin(args)
28122793

28132794
self.assertNoOutput(err)
28142795
self.assertIn("Django/%s" % get_version(), user_agent)
@@ -2879,8 +2860,10 @@ def test_custom_project_template_context_variables(self):
28792860
test_manage_py = os.path.join(testproject_dir, "manage.py")
28802861
with open(test_manage_py) as fp:
28812862
content = fp.read()
2882-
self.assertIn('project_name = "another_project"', content)
2883-
self.assertIn('project_directory = "%s"' % testproject_dir, content)
2863+
self.assertInAfterFormatting('project_name = "another_project"', content)
2864+
self.assertInAfterFormatting(
2865+
'project_directory = "%s"' % testproject_dir, content
2866+
)
28842867

28852868
def test_no_escaping_of_project_variables(self):
28862869
"Make sure template context variables are not html escaped"
@@ -2990,7 +2973,7 @@ def test_custom_project_template_hidden_directory_included(self):
29902973
self.assertNoOutput(err)
29912974
render_py_path = os.path.join(testproject_dir, ".hidden", "render.py")
29922975
with open(render_py_path) as fp:
2993-
self.assertIn(
2976+
self.assertInAfterFormatting(
29942977
f"# The {project_name} should be rendered.",
29952978
fp.read(),
29962979
)
@@ -3150,7 +3133,7 @@ def test_template(self):
31503133
with open(os.path.join(app_path, "apps.py")) as f:
31513134
content = f.read()
31523135
self.assertIn("class NewAppConfig(AppConfig)", content)
3153-
self.assertIn("name = 'new_app'", content)
3136+
self.assertInAfterFormatting("name = 'new_app'", content)
31543137

31553138
def test_creates_directory_when_custom_app_destination_missing(self):
31563139
args = [

tests/humanize_tests/tests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ def test_intcomma(self):
153153
"-1234567.1234567",
154154
Decimal("1234567.1234567"),
155155
Decimal("-1234567.1234567"),
156+
Decimal("Infinity"),
157+
Decimal("-Infinity"),
158+
Decimal("NaN"),
156159
None,
157160
"1234567",
158161
"-1234567",
@@ -193,6 +196,9 @@ def test_intcomma(self):
193196
"-1,234,567.1234567",
194197
"1,234,567.1234567",
195198
"-1,234,567.1234567",
199+
"Infinity",
200+
"-Infinity",
201+
"NaN",
196202
None,
197203
"1,234,567",
198204
"-1,234,567",

0 commit comments

Comments
 (0)