From 6d3f4c13f16c362a59c27ca55491c9bb06b98b59 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:07:14 +0100 Subject: [PATCH 01/16] [4.2.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index dddc14f597a5..7dc2e03a2e06 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (4, 2, 20, "final", 0) +VERSION = (4, 2, 21, "alpha", 0) __version__ = get_version(VERSION) From 506cf74b0ac3a61c1bc341f9beebf8f9c087a7e4 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:04:36 +0100 Subject: [PATCH 02/16] [4.2.x] Added CVE-2025-26699 to security archive. Backport of bad1a18ff28a671f2fdfd447bdf8f43602f882c2 from main. --- docs/releases/security.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index f997fe94a3a3..acc143770b11 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -36,6 +36,17 @@ Issues under Django's security process All security issues have been handled under versions of Django's security process. These are listed below. +March 6, 2025 - :cve:`2025-26699` +--------------------------------- + +Potential denial-of-service in ``django.utils.text.wrap()``. +`Full description +`__ + +* Django 5.1 :commit:`(patch) <8dbb44d34271637099258391dfc79df33951b841>` +* Django 5.0 :commit:`(patch) <4f2765232336b8ad0afd8017d9d912ae93470017>` +* Django 4.2 :commit:`(patch) ` + January 14, 2025 - :cve:`2024-56374` ------------------------------------ From 318c16d2b8157b0ca3fa5f69d0306409b57314b9 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:52:22 +0200 Subject: [PATCH 03/16] [4.2.x] Fixed #36298 -- Truncated the overwritten file content in file_move_safe(). Regression in 58cd4902a71a3695dd6c21dc957f59c333db364c. Thanks Baptiste Mispelon for the report. Backport of 8ad3e80e88201f4c557f6fa79fcfc0f8a0961830 from main. --- django/core/files/move.py | 1 + docs/releases/4.2.21.txt | 15 +++++++++++++++ docs/releases/index.txt | 1 + tests/files/tests.py | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 docs/releases/4.2.21.txt diff --git a/django/core/files/move.py b/django/core/files/move.py index 95d69f9d944c..44f91061f569 100644 --- a/django/core/files/move.py +++ b/django/core/files/move.py @@ -67,6 +67,7 @@ def file_move_safe( | os.O_CREAT | getattr(os, "O_BINARY", 0) | (os.O_EXCL if not allow_overwrite else 0) + | os.O_TRUNC ), ) try: diff --git a/docs/releases/4.2.21.txt b/docs/releases/4.2.21.txt new file mode 100644 index 000000000000..36e24df12f28 --- /dev/null +++ b/docs/releases/4.2.21.txt @@ -0,0 +1,15 @@ +=========================== +Django 4.2.21 release notes +=========================== + +*Expected May 7, 2025* + +Django 4.2.21 fixes a data loss bug in 4.2.20. + +Bugfixes +======== + +* Fixed a data corruption possibility in ``file_move_safe()`` when + ``allow_overwrite=True``, where leftover content from a previously larger + file could remain after overwriting with a smaller one due to lack of + truncation (:ticket:`36298`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 00e4465845f8..af5038a0950a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 4.2.21 4.2.20 4.2.19 4.2.18 diff --git a/tests/files/tests.py b/tests/files/tests.py index b3478d273204..99a289bee5ef 100644 --- a/tests/files/tests.py +++ b/tests/files/tests.py @@ -475,6 +475,27 @@ def test_file_move_permissionerror(self): os.close(handle_b) os.close(handle_c) + def test_file_move_ensure_truncation(self): + with tempfile.NamedTemporaryFile(delete=False) as src: + src.write(b"content") + src_name = src.name + self.addCleanup( + lambda: os.remove(src_name) if os.path.exists(src_name) else None + ) + + with tempfile.NamedTemporaryFile(delete=False) as dest: + dest.write(b"This is a longer content.") + dest_name = dest.name + self.addCleanup(os.remove, dest_name) + + with mock.patch("django.core.files.move.os.rename", side_effect=OSError()): + file_move_safe(src_name, dest_name, allow_overwrite=True) + + with open(dest_name, "rb") as f: + content = f.read() + + self.assertEqual(content, b"content") + class SpooledTempTests(unittest.TestCase): def test_in_memory_spooled_temp(self): From d5db532077fd4326d2908c257914ea2c4d27c6f1 Mon Sep 17 00:00:00 2001 From: nessita <124304+nessita@users.noreply.github.com> Date: Tue, 28 Jan 2025 22:17:40 -0300 Subject: [PATCH 04/16] [4.2.x] Pinned isort version to "<6.0.0" to avoid undesired reformat. Backport of 0671a461c44ba4cf97e84b6c88413bed332df314 from main. --- .github/workflows/linters.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 8f95264b9e70..197b9628894a 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -45,7 +45,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.11' - - run: python -m pip install isort + - run: python -m pip install "isort<6" - name: isort # Pinned to v2.0.0. uses: liskin/gh-problem-matcher-wrap@d8afa2cfb66dd3f982b1950429e652bc14d0d7d2 From 24eeba2c1584a79724fc83d5527f61c434c0f523 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 30 Mar 2025 17:54:15 +0200 Subject: [PATCH 05/16] [4.2.x] Fixed warnings per flake8 7.2.0. https://github.com/PyCQA/flake8/releases/tag/7.2.0 Backport of 281910ff8e9ae98fa78ee5d26ae3f0b713ccf418 from main. --- django/contrib/gis/db/models/fields.py | 2 -- django/db/backends/base/base.py | 1 - django/utils/autoreload.py | 1 - django/utils/translation/trans_real.py | 1 - 4 files changed, 5 deletions(-) diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 889c1cfe840c..812029de49dd 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -37,8 +37,6 @@ def get_srid_info(srid, connection): """ from django.contrib.gis.gdal import SpatialReference - global _srid_cache - try: # The SpatialRefSys model for the spatial backend. SpatialRefSys = connection.ops.spatial_ref_sys() diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py index 3b845ec9b37e..6830b3bec3cd 100644 --- a/django/db/backends/base/base.py +++ b/django/db/backends/base/base.py @@ -234,7 +234,6 @@ def get_new_connection(self, conn_params): def init_connection_state(self): """Initialize the database connection settings.""" - global RAN_DB_VERSION_CHECK if self.alias not in RAN_DB_VERSION_CHECK: self.check_database_version_supported() RAN_DB_VERSION_CHECK.add(self.alias) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 5b22aef2b1a1..cb63d521005f 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -82,7 +82,6 @@ def wrapper(*args, **kwargs): def raise_last_exception(): - global _exception if _exception is not None: raise _exception[1] diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 6833b4bf7fd9..4677610e13d2 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -288,7 +288,6 @@ def translation(language): """ Return a translation object in the default 'django' domain. """ - global _translations if language not in _translations: _translations[language] = DjangoTranslation(language) return _translations[language] From 07edc976c7308a223e1b1dc3c62bd80b3bfef560 Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:20:49 +0000 Subject: [PATCH 06/16] [4.2.x] Upgraded to Python 3.12, Ubuntu 24.04, and enabled fail_on_warning for docs builds. Backport of 73d532d9a92d4d472564f3251499a428d1da9835 from main. --- .github/workflows/docs.yml | 3 +-- .readthedocs.yml | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4206c58e3515..681e22a63d77 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -21,8 +21,7 @@ permissions: jobs: docs: - # OS must be the same as on djangoproject.com. - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 name: docs steps: - name: Checkout diff --git a/.readthedocs.yml b/.readthedocs.yml index bde8b64da0f0..915d51de46f9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,12 +4,13 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.8" + python: "3.12" sphinx: configuration: docs/conf.py + fail_on_warning: true python: install: From e61e3daaf037507211028494d61f24382be31e5a Mon Sep 17 00:00:00 2001 From: Matti Pohjanvirta Date: Sun, 20 Apr 2025 18:22:51 +0300 Subject: [PATCH 07/16] [4.2.x] Fixed #36341 -- Preserved whitespaces in wordwrap template filter. Regression in 55d89e25f4115c5674cdd9b9bcba2bb2bb6d820b. This work improves the django.utils.text.wrap() function to ensure that empty lines and lines with whitespace only are kept instead of being dropped. Thanks Matti Pohjanvirta for the report and fix. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 1e9db35836d42a3c72f3d1015c2f302eb6fee046 from main. --- django/utils/text.py | 13 +++++- .../filter_tests/test_wordwrap.py | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index 81ae88dc76d4..b018f2601fd2 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -102,10 +102,19 @@ def wrap(text, width): width=width, break_long_words=False, break_on_hyphens=False, + replace_whitespace=False, ) result = [] - for line in text.splitlines(True): - result.extend(wrapper.wrap(line)) + for line in text.splitlines(): + wrapped = wrapper.wrap(line) + if not wrapped: + # If `line` contains only whitespaces that are dropped, restore it. + result.append(line) + else: + result.extend(wrapped) + if text.endswith("\n"): + # If `text` ends with a newline, preserve it. + result.append("") return "\n".join(result) diff --git a/tests/template_tests/filter_tests/test_wordwrap.py b/tests/template_tests/filter_tests/test_wordwrap.py index 4afa1dd234f1..1692332e1eeb 100644 --- a/tests/template_tests/filter_tests/test_wordwrap.py +++ b/tests/template_tests/filter_tests/test_wordwrap.py @@ -89,3 +89,44 @@ def test_wrap_long_text(self): "I'm afraid", wordwrap(long_text, 10), ) + + def test_wrap_preserve_newlines(self): + cases = [ + ( + "this is a long paragraph of text that really needs to be wrapped\n\n" + "that is followed by another paragraph separated by an empty line\n", + "this is a long paragraph of\ntext that really needs to be\nwrapped\n\n" + "that is followed by another\nparagraph separated by an\nempty line\n", + 30, + ), + ("\n\n\n", "\n\n\n", 5), + ("\n\n\n\n\n\n", "\n\n\n\n\n\n", 5), + ] + for text, expected, width in cases: + with self.subTest(text=text): + self.assertEqual(wordwrap(text, width), expected) + + def test_wrap_preserve_whitespace(self): + width = 5 + width_spaces = " " * width + cases = [ + ( + f"first line\n{width_spaces}\nsecond line", + f"first\nline\n{width_spaces}\nsecond\nline", + ), + ( + "first line\n \t\t\t \nsecond line", + "first\nline\n \t\t\t \nsecond\nline", + ), + ( + f"first line\n{width_spaces}\nsecond line\n\nthird{width_spaces}\n", + f"first\nline\n{width_spaces}\nsecond\nline\n\nthird\n", + ), + ( + f"first line\n{width_spaces}{width_spaces}\nsecond line", + f"first\nline\n{width_spaces}{width_spaces}\nsecond\nline", + ), + ] + for text, expected in cases: + with self.subTest(text=text): + self.assertEqual(wordwrap(text, width), expected) From b3df75339904fb0bc5742d0b458ac59b8e68835b Mon Sep 17 00:00:00 2001 From: nessita <124304+nessita@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:26:48 -0300 Subject: [PATCH 08/16] [4.2.x] Refs #36341 -- Added release note for 4.2.21 for fix in wordwrap template filter. Revision 1e9db35836d42a3c72f3d1015c2f302eb6fee046 fixed a regression in 55d89e25f4115c5674cdd9b9bcba2bb2bb6d820b, which also needs to be backported to the stable branches in extended support. Backport of c86242d61ff81bddbead115c458c1eb532d43b43 from main. --- docs/releases/4.2.21.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/releases/4.2.21.txt b/docs/releases/4.2.21.txt index 36e24df12f28..1064dcf2020f 100644 --- a/docs/releases/4.2.21.txt +++ b/docs/releases/4.2.21.txt @@ -4,7 +4,7 @@ Django 4.2.21 release notes *Expected May 7, 2025* -Django 4.2.21 fixes a data loss bug in 4.2.20. +Django 4.2.21 fixes a data loss bug and a regression in 4.2.20. Bugfixes ======== @@ -13,3 +13,8 @@ Bugfixes ``allow_overwrite=True``, where leftover content from a previously larger file could remain after overwriting with a smaller one due to lack of truncation (:ticket:`36298`). + +* Fixed a regression in Django 4.2.20, introduced when fixing + :cve:`2025-26699`, where the :tfilter:`wordwrap` template filter did not + preserve empty lines between paragraphs after wrapping text + (:ticket:`36341`). From 93973d4f88b46fe72e806361711eeefa83f8e535 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 30 Apr 2025 11:23:51 -0300 Subject: [PATCH 09/16] [4.2.x] Added upcoming security release to release notes. Backport of 0f5dd0dff3049189a3fe71a62670b746543335d5 from main. --- docs/releases/4.2.21.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/releases/4.2.21.txt b/docs/releases/4.2.21.txt index 1064dcf2020f..306269a3e7e0 100644 --- a/docs/releases/4.2.21.txt +++ b/docs/releases/4.2.21.txt @@ -4,7 +4,8 @@ Django 4.2.21 release notes *Expected May 7, 2025* -Django 4.2.21 fixes a data loss bug and a regression in 4.2.20. +Django 4.2.21 fixes a security issue with severity "moderate", a data loss bug, +and a regression in 4.2.20. Bugfixes ======== From 35c34ed2d0039db98a871da300531514bb0cbac0 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 5 Jan 2024 06:03:19 +0100 Subject: [PATCH 10/16] [4.2.x] Removed obsolete rpm-related install code. Backport of edcf8532ffda006bc125d9c93fca59f9037f490f from main. --- scripts/rpm-install.sh | 28 ---------------------------- setup.cfg | 4 ---- 2 files changed, 32 deletions(-) delete mode 100644 scripts/rpm-install.sh diff --git a/scripts/rpm-install.sh b/scripts/rpm-install.sh deleted file mode 100644 index 89cf4dd04954..000000000000 --- a/scripts/rpm-install.sh +++ /dev/null @@ -1,28 +0,0 @@ -#! /bin/sh -# -# This file becomes the install section of the generated spec file. -# - -# This is what dist.py normally does. -%{__python} setup.py install --root=${RPM_BUILD_ROOT} --record="INSTALLED_FILES" - -# Sort the filelist so that directories appear before files. This avoids -# duplicate filename problems on some systems. -touch DIRS -for i in `cat INSTALLED_FILES`; do - if [ -f ${RPM_BUILD_ROOT}/$i ]; then - echo $i >>FILES - fi - if [ -d ${RPM_BUILD_ROOT}/$i ]; then - echo %dir $i >>DIRS - fi -done - -# Make sure we match foo.pyo and foo.pyc along with foo.py (but only once each) -sed -e "/\.py[co]$/d" -e "s/\.py$/.py*/" DIRS FILES >INSTALLED_FILES - -mkdir -p ${RPM_BUILD_ROOT}/%{_mandir}/man1/ -cp docs/man/* ${RPM_BUILD_ROOT}/%{_mandir}/man1/ -cat << EOF >> INSTALLED_FILES -%doc %{_mandir}/man1/*" -EOF diff --git a/setup.cfg b/setup.cfg index 574b02e2ba38..6c4dc6804394 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,10 +53,6 @@ console_scripts = argon2 = argon2-cffi >= 19.1.0 bcrypt = bcrypt -[bdist_rpm] -doc_files = docs extras AUTHORS INSTALL LICENSE README.rst -install_script = scripts/rpm-install.sh - [flake8] exclude = build,.git,.tox,./tests/.env extend-ignore = E203 From afe52d89c4f42870622a4bb161ab5f4d4913aac5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 24 Jun 2024 20:34:43 +0200 Subject: [PATCH 11/16] [4.2.x] Migrated setuptools configuration to pyproject.toml. This branch migrates setuptools configuration from setup.py/setup.cfg to pyproject.toml. In order to ensure that the generated binary files have consistent casing (both the tarball and the wheel), setuptools version is limited to ">=61.0.0,<69.3.0". Configuration for flake8 was moved to a dedicated .flake8 file since it cannot be configured via pyproject.toml. Also, __pycache__ exclusion was removed from MANIFEST and the extras/Makefile was replaced with a simpler build command. Co-authored-by: Nick Pope Backport of 4686541691dbe986f58ac87630c3b7a04db4ff93 from main. --- .flake8 | 9 +++ MANIFEST.in | 1 - .../writing-code/coding-style.txt | 2 +- docs/internals/howto-release-django.txt | 18 +++-- docs/topics/auth/passwords.txt | 4 +- extras/Makefile | 9 --- pyproject.toml | 72 +++++++++++++++++-- setup.cfg | 69 ------------------ setup.py | 55 -------------- 9 files changed, 92 insertions(+), 147 deletions(-) create mode 100644 .flake8 delete mode 100644 extras/Makefile delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000000..c4094af4621c --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +exclude = build,.git,.tox,./tests/.env +extend-ignore = E203 +max-line-length = 88 +per-file-ignores = + django/core/cache/backends/filebased.py:W601 + django/core/cache/backends/base.py:W601 + django/core/cache/backends/redis.py:W601 + tests/cache/tests.py:W601 diff --git a/MANIFEST.in b/MANIFEST.in index fecbae358b81..cba764b41419 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,5 +12,4 @@ graft extras graft js_tests graft scripts graft tests -global-exclude __pycache__ global-exclude *.py[co] diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt index d227e04ba0fe..0a023ed39244 100644 --- a/docs/internals/contributing/writing-code/coding-style.txt +++ b/docs/internals/contributing/writing-code/coding-style.txt @@ -46,7 +46,7 @@ Python style * Unless otherwise specified, follow :pep:`8`. Use :pypi:`flake8` to check for problems in this area. Note that our - ``setup.cfg`` file contains some excluded files (deprecated modules we don't + ``.flake8`` file contains some excluded files (deprecated modules we don't care about cleaning up and some third-party code that Django vendors) as well as some excluded errors that we don't consider as gross violations. Remember that :pep:`8` is only a guide, so respect the style of the surrounding code diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index 5c2d0b7451dd..ba70921ad243 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -58,7 +58,7 @@ You'll need a few things before getting started: .. code-block:: shell - $ python -m pip install wheel twine + $ python -m pip install build twine * Access to Django's record on PyPI. Create a file with your credentials: @@ -222,9 +222,15 @@ OK, this is the fun part, where we actually push out a release! Please see `notes on setting the VERSION tuple`_ below for details on ``VERSION``. -#. If this is a pre-release package, update the "Development Status" trove - classifier in ``setup.cfg`` to reflect this. Otherwise, make sure the - classifier is set to ``Development Status :: 5 - Production/Stable``. + #. If this is a pre-release package also update the "Development Status" + trove classifier in ``pyproject.toml`` to reflect this. An ``rc`` + pre-release should not change the trove classifier (:commit:`example + commit for alpha release `, + :commit:`example commit for beta release + <25fec8940b24107e21314ab6616e18ce8dec1c1c>`). + + #. Otherwise, make sure the classifier is set to + ``Development Status :: 5 - Production/Stable``. #. Tag the release using ``git tag``. For example: @@ -238,8 +244,8 @@ OK, this is the fun part, where we actually push out a release! #. Make sure you have an absolutely clean tree by running ``git clean -dfx``. -#. Run ``make -f extras/Makefile`` to generate the release packages. This will - create the release packages in a ``dist/`` directory. +#. Run ``python -m build`` to generate the release packages. This will create + the release packages in a ``dist/`` directory. #. Generate the hashes of the release packages: diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index b7b2dd4f7a41..636e9a2c326b 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -97,7 +97,7 @@ To use Argon2id as your default storage algorithm, do the following: #. Install the :pypi:`argon2-cffi` package. This can be done by running ``python -m pip install django[argon2]``, which is equivalent to ``python -m pip install argon2-cffi`` (along with any version requirement - from Django's ``setup.cfg``). + from Django's ``pyproject.toml``). #. Modify :setting:`PASSWORD_HASHERS` to list ``Argon2PasswordHasher`` first. That is, in your settings file, you'd put:: @@ -128,7 +128,7 @@ To use Bcrypt as your default storage algorithm, do the following: #. Install the :pypi:`bcrypt` package. This can be done by running ``python -m pip install django[bcrypt]``, which is equivalent to ``python -m pip install bcrypt`` (along with any version requirement from - Django's ``setup.cfg``). + Django's ``pyproject.toml``). #. Modify :setting:`PASSWORD_HASHERS` to list ``BCryptSHA256PasswordHasher`` first. That is, in your settings file, you'd put:: diff --git a/extras/Makefile b/extras/Makefile deleted file mode 100644 index 66efd0d45196..000000000000 --- a/extras/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -all: sdist bdist_wheel - -sdist: - python setup.py sdist - -bdist_wheel: - python setup.py bdist_wheel - -.PHONY : sdist bdist_wheel diff --git a/pyproject.toml b/pyproject.toml index b1b79a53dd41..d059282f16df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,71 @@ [build-system] -requires = ['setuptools>=40.8.0'] -build-backend = 'setuptools.build_meta' +requires = ["setuptools>=61.0.0,<69.3.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "Django" +dynamic = ["version"] +requires-python = ">= 3.8" +dependencies = [ + "asgiref >= 3.6.0, < 4", + "backports.zoneinfo; python_version<'3.9'", + "sqlparse>=0.3.1", + "tzdata; sys_platform == 'win32'", +] +authors = [ + {name = "Django Software Foundation", email = "foundation@djangoproject.com"}, +] +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +readme = "README.rst" +license = {text = "BSD-3-Clause"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Internet :: WWW/HTTP :: WSGI", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +[project.optional-dependencies] +argon2 = ["argon2-cffi>=19.1.0"] +bcrypt = ["bcrypt"] + +[project.scripts] +django-admin = "django.core.management:execute_from_command_line" + +[project.urls] +Homepage = "https://www.djangoproject.com/" +Documentation = "https://docs.djangoproject.com/" +"Release notes" = "https://docs.djangoproject.com/en/stable/releases/" +Funding = "https://www.djangoproject.com/fundraising/" +Source = "https://github.com/django/django" +Tracker = "https://code.djangoproject.com/" [tool.black] -target-version = ['py38'] -force-exclude = 'tests/test_runner_apps/tagged/tests_syntax_error.py' +target-version = ["py38"] +force-exclude = "tests/test_runner_apps/tagged/tests_syntax_error.py" + +[tool.isort] +profile = "black" +default_section = "THIRDPARTY" +known_first_party = "django" + +[tool.setuptools.dynamic] +version = {attr = "django.__version__"} + +[tool.setuptools.packages.find] +include = ["django*"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 6c4dc6804394..000000000000 --- a/setup.cfg +++ /dev/null @@ -1,69 +0,0 @@ -[metadata] -name = Django -version = attr: django.__version__ -url = https://www.djangoproject.com/ -author = Django Software Foundation -author_email = foundation@djangoproject.com -description = A high-level Python web framework that encourages rapid development and clean, pragmatic design. -long_description = file: README.rst -license = BSD-3-Clause -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Framework :: Django - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Topic :: Internet :: WWW/HTTP - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: Internet :: WWW/HTTP :: WSGI - Topic :: Software Development :: Libraries :: Application Frameworks - Topic :: Software Development :: Libraries :: Python Modules -project_urls = - Documentation = https://docs.djangoproject.com/ - Release notes = https://docs.djangoproject.com/en/stable/releases/ - Funding = https://www.djangoproject.com/fundraising/ - Source = https://github.com/django/django - Tracker = https://code.djangoproject.com/ - -[options] -python_requires = >=3.8 -packages = find: -include_package_data = true -zip_safe = false -install_requires = - asgiref >= 3.6.0, < 4 - backports.zoneinfo; python_version<"3.9" - sqlparse >= 0.3.1 - tzdata; sys_platform == 'win32' - -[options.entry_points] -console_scripts = - django-admin = django.core.management:execute_from_command_line - -[options.extras_require] -argon2 = argon2-cffi >= 19.1.0 -bcrypt = bcrypt - -[flake8] -exclude = build,.git,.tox,./tests/.env -extend-ignore = E203 -max-line-length = 88 -per-file-ignores = - django/core/cache/backends/filebased.py:W601 - django/core/cache/backends/base.py:W601 - django/core/cache/backends/redis.py:W601 - tests/cache/tests.py:W601 - -[isort] -profile = black -default_section = THIRDPARTY -known_first_party = django diff --git a/setup.py b/setup.py deleted file mode 100644 index ef91130d4738..000000000000 --- a/setup.py +++ /dev/null @@ -1,55 +0,0 @@ -import os -import site -import sys -from distutils.sysconfig import get_python_lib - -from setuptools import setup - -# Allow editable install into user site directory. -# See https://github.com/pypa/pip/issues/7953. -site.ENABLE_USER_SITE = "--user" in sys.argv[1:] - -# Warn if we are installing over top of an existing installation. This can -# cause issues where files that were deleted from a more recent Django are -# still present in site-packages. See #18115. -overlay_warning = False -if "install" in sys.argv: - lib_paths = [get_python_lib()] - if lib_paths[0].startswith("/usr/lib/"): - # We have to try also with an explicit prefix of /usr/local in order to - # catch Debian's custom user site-packages directory. - lib_paths.append(get_python_lib(prefix="/usr/local")) - for lib_path in lib_paths: - existing_path = os.path.abspath(os.path.join(lib_path, "django")) - if os.path.exists(existing_path): - # We note the need for the warning here, but present it after the - # command is run, so it's more likely to be seen. - overlay_warning = True - break - - -setup() - - -if overlay_warning: - sys.stderr.write( - """ - -======== -WARNING! -======== - -You have just installed Django over top of an existing -installation, without removing it first. Because of this, -your install may now include extraneous files from a -previous version that have since been removed from -Django. This is known to cause a variety of problems. You -should manually remove the - -%(existing_path)s - -directory and re-install Django. - -""" - % {"existing_path": existing_path} - ) From 3456eee4a3a00dc14e72d4f7d6eecc15ed9571e7 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Fri, 6 Dec 2024 18:32:39 +0000 Subject: [PATCH 12/16] [4.2.x] Fixed #35980 -- Updated setuptools to normalize package names in built artifacts. Backport of 3ae049b26b995c650c41ef918d5f60beed52b4ba from main. --- pyproject.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d059282f16df..4635d0e1f555 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,8 @@ [build-system] -requires = ["setuptools>=61.0.0,<69.3.0"] +requires = [ + "setuptools>=75.8.1; python_version >= '3.9'", + "setuptools<75.4.0; python_version < '3.9'", +] build-backend = "setuptools.build_meta" [project] @@ -7,8 +10,8 @@ name = "Django" dynamic = ["version"] requires-python = ">= 3.8" dependencies = [ - "asgiref >= 3.6.0, < 4", - "backports.zoneinfo; python_version<'3.9'", + "asgiref>=3.6.0,<4", + "backports.zoneinfo; python_version < '3.9'", "sqlparse>=0.3.1", "tzdata; sys_platform == 'win32'", ] From f4bd5647019bf4b4dc85dd9f05894e5c0e377e00 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:16:07 -0300 Subject: [PATCH 13/16] [4.2.x] Adjusted GitHub Action workflow to test Python versions based off pyproject.toml. --- .github/workflows/python_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python_matrix.yml b/.github/workflows/python_matrix.yml index 314d9301b885..ab48c2be8322 100644 --- a/.github/workflows/python_matrix.yml +++ b/.github/workflows/python_matrix.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v4 - id: set-matrix run: | - python_versions=$(sed -n "s/^.*Programming Language :: Python :: \([[:digit:]]\+\.[[:digit:]]\+\).*$/'\1', /p" setup.cfg | tr -d '\n' | sed 's/, $//g') + python_versions=$(sed -n "s/^.*Programming Language :: Python :: \([[:digit:]]\+\.[[:digit:]]\+\).*$/'\1', /p" pyproject.toml | tr -d '\n' | sed 's/, $//g') echo "Supported Python versions: $python_versions" echo "python_versions=[$python_versions]" >> "$GITHUB_OUTPUT" python: From ca31ca09f7ae5abab76012752a24a317544cdc2d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 4 Jan 2024 23:05:05 +0100 Subject: [PATCH 14/16] [4.2.x] Changed packing recommendation to use pyproject.toml in reusable apps docs. Backport of f71bcc001bb3324020cfd756e84d4e9c6bb98cce from main. --- docs/intro/reusable-apps.txt | 133 +++++++++++++++-------------------- 1 file changed, 58 insertions(+), 75 deletions(-) diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index dba37286d326..4524c3df4e18 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -182,95 +182,78 @@ this. For a small app like polls, this process isn't too difficult. license. Just be aware that your licensing choice will affect who is able to use your code. -#. Next we'll create ``pyproject.toml``, ``setup.cfg``, and ``setup.py`` files - which detail how to build and install the app. A full explanation of these - files is beyond the scope of this tutorial, but the `setuptools - documentation `_ has a good - explanation. Create the ``django-polls/pyproject.toml``, - ``django-polls/setup.cfg``, and ``django-polls/setup.py`` files with the +#. Next we'll create the ``pyproject.toml`` file which details how to build and + install the app. A full explanation of this file is beyond the scope of this + tutorial, but the `Python Packaging User Guide + `_ has a good + explanation. Create the ``django-polls/pyproject.toml`` file with the following contents: .. code-block:: toml - :caption: ``django-polls/pyproject.toml`` - - [build-system] - requires = ['setuptools>=40.8.0'] - build-backend = 'setuptools.build_meta' - - .. code-block:: ini - :caption: ``django-polls/setup.cfg`` - - [metadata] - name = django-polls - version = 0.1 - description = A Django app to conduct web-based polls. - long_description = file: README.rst - url = https://www.example.com/ - author = Your Name - author_email = yourname@example.com - license = BSD-3-Clause # Example license - classifiers = - Environment :: Web Environment - Framework :: Django - Framework :: Django :: X.Y # Replace "X.Y" as appropriate - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Topic :: Internet :: WWW/HTTP - Topic :: Internet :: WWW/HTTP :: Dynamic Content - - [options] - include_package_data = true - packages = find: - python_requires = >=3.8 - install_requires = - Django >= X.Y # Replace "X.Y" as appropriate - - .. code-block:: python - :caption: ``django-polls/setup.py`` - - from setuptools import setup - - setup() - -#. Only Python modules and packages are included in the package by default. To - include additional files, we'll need to create a ``MANIFEST.in`` file. The - ``setuptools`` docs referred to in the previous step discuss this file in - more detail. To include the templates, the ``README.rst`` and our - ``LICENSE`` file, create a file ``django-polls/MANIFEST.in`` with the - following contents: + :caption: ``django-polls/pyproject.toml`` + + [build-system] + requires = ["setuptools>=61.0"] + build-backend = "setuptools.build_meta" + + [project] + name = "django-polls" + version = "0.1" + dependencies = [ + "django>=X.Y", # Replace "X.Y" as appropriate + ] + description = "A Django app to conduct web-based polls." + readme = "README.rst" + requires-python = ">= 3.8" + authors = [ + {name = "Your Name", email = "yourname@example.com"}, + ] + classifiers = [ + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: X.Y", # Replace "X.Y" as appropriate + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + ] + + [project.urls] + Homepage = "https://www.example.com/" + +#. Many common files and Python modules and packages are included in the + package by default. To include additional files, we'll need to create a + ``MANIFEST.in`` file. To include the templates and static files, create a + file ``django-polls/MANIFEST.in`` with the following contents: .. code-block:: text - :caption: ``django-polls/MANIFEST.in`` + :caption: ``django-polls/MANIFEST.in`` - include LICENSE - include README.rst - recursive-include polls/static * - recursive-include polls/templates * + recursive-include polls/static * + recursive-include polls/templates * #. It's optional, but recommended, to include detailed documentation with your app. Create an empty directory ``django-polls/docs`` for future - documentation. Add an additional line to ``django-polls/MANIFEST.in``: - - .. code-block:: text - - recursive-include docs * + documentation. Note that the ``docs`` directory won't be included in your package unless you add some files to it. Many Django apps also provide their documentation online through sites like `readthedocs.org `_. -#. Try building your package with ``python setup.py sdist`` (run from inside - ``django-polls``). This creates a directory called ``dist`` and builds your - new package, ``django-polls-0.1.tar.gz``. +#. Check that the :pypi:`build` package is installed (``python -m pip install + build``) and try building your package by running ``python -m build`` inside + ``django-polls``. This creates a directory called ``dist`` and builds your + new package into source and binary formats, ``django-polls-0.1.tar.gz`` and + ``django_polls-0.1-py3-none-any.whl``. For more information on packaging, see Python's `Tutorial on Packaging and Distributing Projects From 9cd8028f3e38dca8e51c1388f474eecbe7d6ca3c Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Tue, 8 Apr 2025 16:30:17 +0200 Subject: [PATCH 15/16] [4.2.x] Fixed CVE-2025-32873 -- Mitigated potential DoS in strip_tags(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Elias Myllymäki for the report, and Shai Berger and Jake Howard for the reviews. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 9f3419b519799d69f2aba70b9d25abe2e70d03e0 from main. --- django/utils/html.py | 6 ++++++ docs/releases/4.2.21.txt | 11 +++++++++++ tests/utils_tests/test_html.py | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/django/utils/html.py b/django/utils/html.py index a3a7238cba44..84c37d118663 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -17,6 +17,9 @@ MAX_URL_LENGTH = 2048 MAX_STRIP_TAGS_DEPTH = 50 +# HTML tag that opens but has no closing ">" after 1k+ chars. +long_open_tag_without_closing_re = _lazy_re_compile(r"<[a-zA-Z][^>]{1000,}") + @keep_lazy(SafeString) def escape(text): @@ -175,6 +178,9 @@ def _strip_once(value): def strip_tags(value): """Return the given HTML with all tags stripped.""" value = str(value) + for long_open_tag in long_open_tag_without_closing_re.finditer(value): + if long_open_tag.group().count("<") >= MAX_STRIP_TAGS_DEPTH: + raise SuspiciousOperation # Note: in typical case this loop executes _strip_once twice (the second # execution does not remove any more tags). strip_tags_depth = 0 diff --git a/docs/releases/4.2.21.txt b/docs/releases/4.2.21.txt index 306269a3e7e0..cc39105a0167 100644 --- a/docs/releases/4.2.21.txt +++ b/docs/releases/4.2.21.txt @@ -7,6 +7,17 @@ Django 4.2.21 release notes Django 4.2.21 fixes a security issue with severity "moderate", a data loss bug, and a regression in 4.2.20. +CVE-2025-32873: Denial-of-service possibility in ``strip_tags()`` +================================================================= + +:func:`~django.utils.html.strip_tags` would be slow to evaluate certain inputs +containing large sequences of incomplete HTML tags. This function is used to +implement the :tfilter:`striptags` template filter, which was thus also +vulnerable. + +:func:`~django.utils.html.strip_tags` now raises a :exc:`.SuspiciousOperation` +exception if it encounters an unusually large number of unclosed opening tags. + Bugfixes ======== diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 579bb2a1e359..25168e23487a 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -115,17 +115,30 @@ def test_strip_tags(self): (">br>br>br>X", "XX"), ("<" * 50 + "a>" * 50, ""), + (">" + "" + "" * 51, "" with self.assertRaises(SuspiciousOperation): strip_tags(value) + def test_strip_tags_suspicious_operation_large_open_tags(self): + items = [ + ">" + " Date: Tue, 6 May 2025 22:37:35 -0300 Subject: [PATCH 16/16] [4.2.x] Bumped version for 4.2.21 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 7dc2e03a2e06..e29d3a8b9ac2 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (4, 2, 21, "alpha", 0) +VERSION = (4, 2, 21, "final", 0) __version__ = get_version(VERSION) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy