From 7112007fcaf7a7db8a4a6bb1d45b51d8fa41d0b7 Mon Sep 17 00:00:00 2001 From: Tom Most Date: Wed, 1 Jul 2026 14:11:37 -0700 Subject: [PATCH 1/2] Fixed #37199 -- Fixed typo in bulk_update code example in docs/ref/models/querysets.txt. --- docs/ref/models/querysets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 93bddf90b050..f40c1405690e 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -2542,7 +2542,7 @@ them, but it has a few caveats: batch_size = 100 ids_iter = iter(range(1000)) while ids := list(islice(ids_iter, batch_size)): - batch = Entry.objects.filter(ids__in=ids) + batch = Entry.objects.filter(id__in=ids) for entry in batch: entry.headline = f"Updated headline {entry.pk}" Entry.objects.bulk_update(batch, ["headline"], batch_size=batch_size) From bdbda29c3e126754c3ae04ceb5c5d35d49aae01c Mon Sep 17 00:00:00 2001 From: Sina Chaichi Maleki Date: Fri, 8 May 2026 23:20:07 +0200 Subject: [PATCH 2/2] Fixed #29303 -- Avoided mutating original view in non_atomic_requests(). Previously non_atomic_requests() mutated the _non_atomic_requests attribute of the original view function instead of returning a new wrapper. --- django/db/transaction.py | 13 ++++++++++--- tests/handlers/tests.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/django/db/transaction.py b/django/db/transaction.py index fe6d3fed3214..94f381779c52 100644 --- a/django/db/transaction.py +++ b/django/db/transaction.py @@ -1,5 +1,6 @@ import warnings from contextlib import ContextDecorator, contextmanager +from functools import wraps from django.db import ( DEFAULT_DB_ALIAS, @@ -337,10 +338,16 @@ def atomic(using=None, savepoint=True, durable=False): def _non_atomic_requests(view, using): try: - view._non_atomic_requests.add(using) + databases = view._non_atomic_requests | {using} except AttributeError: - view._non_atomic_requests = {using} - return view + databases = {using} + + @wraps(view) + def wrapper(*args, **kwargs): + return view(*args, **kwargs) + + wrapper._non_atomic_requests = databases + return wrapper def non_atomic_requests(using=None): diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py index 625090a66d0c..240c398988cc 100644 --- a/tests/handlers/tests.py +++ b/tests/handlers/tests.py @@ -1,7 +1,8 @@ from django.core.exceptions import ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler, WSGIRequest, get_script_name from django.core.signals import request_finished, request_started -from django.db import close_old_connections, connection +from django.db import close_old_connections, connection, transaction +from django.http import HttpResponse from django.test import ( AsyncRequestFactory, RequestFactory, @@ -143,6 +144,17 @@ def test_no_auto_transaction(self): # The non_atomic_requests decorator is used for an incorrect table. self.assertContains(response, "True") + def test_non_atomic_requests_does_not_mutate_original(self): + def my_view(request): + return HttpResponse("Ok") + + wrapped_once = transaction.non_atomic_requests(using="default")(my_view) + wrapped_twice = transaction.non_atomic_requests(using="other")(wrapped_once) + + self.assertIsNot(wrapped_twice, wrapped_once) + self.assertEqual(wrapped_once._non_atomic_requests, {"default"}) + self.assertEqual(wrapped_twice._non_atomic_requests, {"default", "other"}) + @override_settings(ROOT_URLCONF="handlers.urls") class SignalsTests(SimpleTestCase):