diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 53a73e19e51f..73ba24368b0b 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -77,7 +77,15 @@ def queryset_iterator(obj, field): chunk_size = ( 2000 if getattr(attr, "prefetch_cache_name", None) else None ) - return attr.iterator(chunk_size) + query_set = attr.all() + if not query_set.totally_ordered: + current_ordering = ( + query_set.query.order_by + or query_set.model._meta.ordering + or [] + ) + query_set = query_set.order_by(*current_ordering, "pk") + return query_set.iterator(chunk_size) else: @@ -86,6 +94,13 @@ def m2m_value(value): def queryset_iterator(obj, field): query_set = getattr(obj, field.name).select_related(None).only("pk") + if not query_set.totally_ordered: + current_ordering = ( + query_set.query.order_by + or query_set.model._meta.ordering + or [] + ) + query_set = query_set.order_by(*current_ordering, "pk") chunk_size = 2000 if query_set._prefetch_related_lookups else None return query_set.iterator(chunk_size=chunk_size) diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index d8ffbdf00a98..3d1f79ea0d00 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -177,7 +177,15 @@ def queryset_iterator(obj, field): chunk_size = ( 2000 if getattr(attr, "prefetch_cache_name", None) else None ) - return attr.iterator(chunk_size) + query_set = attr.all() + if not query_set.totally_ordered: + current_ordering = ( + query_set.query.order_by + or query_set.model._meta.ordering + or [] + ) + query_set = query_set.order_by(*current_ordering, "pk") + return query_set.iterator(chunk_size) else: @@ -186,6 +194,13 @@ def handle_m2m(value): def queryset_iterator(obj, field): query_set = getattr(obj, field.name).select_related(None).only("pk") + if not query_set.totally_ordered: + current_ordering = ( + query_set.query.order_by + or query_set.model._meta.ordering + or [] + ) + query_set = query_set.order_by(*current_ordering, "pk") chunk_size = 2000 if query_set._prefetch_related_lookups else None return query_set.iterator(chunk_size=chunk_size) diff --git a/docs/internals/security.txt b/docs/internals/security.txt index a3d57e8d7c4f..5214bf0704a4 100644 --- a/docs/internals/security.txt +++ b/docs/internals/security.txt @@ -347,8 +347,10 @@ will not issue patches or new releases for those versions. Security issue severity levels ============================== -The severity level of a security vulnerability is determined by the attack -type. +The severity level of a security vulnerability is determined primarily by the +attack type. The Django Security Team retains the authority to adjust severity +levels based on the specific characteristics, context, and potential real-world +impact of individual vulnerabilities. Severity levels are: @@ -361,16 +363,21 @@ Severity levels are: * Cross site scripting (XSS) * Cross site request forgery (CSRF) - * Denial-of-service attacks * Broken authentication * **Low** + * Denial-of-service attacks * Sensitive data exposure * Broken session management * Unvalidated redirects/forwards * Issues requiring an uncommon configuration option +For example, a denial-of-service vulnerability that is exploitable by +unauthenticated attackers and affects default Django configurations, causing +severe performance degradation or service unavailability, may be elevated to +**Moderate**, given the potential impact across the Django ecosystem. + .. _security-disclosure: How Django discloses security issues diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 3b5b0380f0f9..114d819847dc 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -134,6 +134,7 @@ def run_test(self, args, settings_file=None, apps=None, umask=-1): python_path.extend(ext_backend_base_dirs) test_environ["PYTHONPATH"] = os.pathsep.join(python_path) test_environ["PYTHONWARNINGS"] = "" + test_environ["PYTHON_COLORS"] = "0" p = subprocess.run( [sys.executable, *args], @@ -2551,6 +2552,7 @@ def _test(self, args, option_b="'2'"): ) +@mock.patch.dict(os.environ, {"PYTHON_COLORS": "0"}) class ExecuteFromCommandLine(SimpleTestCase): def test_program_name_from_argv(self): """ diff --git a/tests/serializers/test_natural.py b/tests/serializers/test_natural.py index b405dc3e284b..4dff100cc7af 100644 --- a/tests/serializers/test_natural.py +++ b/tests/serializers/test_natural.py @@ -1,5 +1,7 @@ +from unittest import mock + from django.core import serializers -from django.db import connection +from django.db import connection, models from django.test import TestCase from .models import ( @@ -336,6 +338,20 @@ def nullable_natural_key_m2m_test(self, format): ) +def natural_key_m2m_totally_ordered_test(self, format): + t1 = NaturalKeyThing.objects.create(key="t1") + t2 = NaturalKeyThing.objects.create(key="t2") + t3 = NaturalKeyThing.objects.create(key="t3") + t1.other_things.add(t2, t3) + + with mock.patch.object(models.QuerySet, "order_by") as mock_order_by: + serializers.serialize(format, [t1], use_natural_foreign_keys=True) + mock_order_by.assert_called_once_with("pk") + mock_order_by.reset_mock() + serializers.serialize(format, [t1], use_natural_foreign_keys=False) + mock_order_by.assert_called_once_with("pk") + + # Dynamically register tests for each serializer register_tests( NaturalKeySerializerTests, @@ -385,3 +401,8 @@ def nullable_natural_key_m2m_test(self, format): "test_%s_nullable_natural_key_m2m", nullable_natural_key_m2m_test, ) +register_tests( + NaturalKeySerializerTests, + "test_%s_natural_key_m2m_totally_ordered", + natural_key_m2m_totally_ordered_test, +) diff --git a/tests/test_runner/test_debug_sql.py b/tests/test_runner/test_debug_sql.py index acf66633ef16..61b60643c57d 100644 --- a/tests/test_runner/test_debug_sql.py +++ b/tests/test_runner/test_debug_sql.py @@ -1,4 +1,5 @@ import logging +import os import unittest from io import StringIO from time import time @@ -183,6 +184,7 @@ def test_log_record_sql_extra_none(self): @unittest.skipUnless( connection.vendor == "sqlite", "Only run on sqlite so we can check output SQL." ) +@mock.patch.dict(os.environ, {"PYTHON_COLORS": "0"}) class TestDebugSQL(unittest.TestCase): class PassingTest(TestCase): def runTest(self): diff --git a/tests/test_utils/test_simpletestcase.py b/tests/test_utils/test_simpletestcase.py index c6aa31494445..c09c68db0c75 100644 --- a/tests/test_utils/test_simpletestcase.py +++ b/tests/test_utils/test_simpletestcase.py @@ -1,3 +1,4 @@ +import os import unittest from io import StringIO from unittest import mock @@ -19,6 +20,7 @@ def skipped_test(self): pass +@mock.patch.dict(os.environ, {"PYTHON_COLORS": "0"}) @mock.patch.object(ErrorTestCase, "_post_teardown") @mock.patch.object(ErrorTestCase, "_pre_setup") class DebugInvocationTests(SimpleTestCase):