Skip to content

Commit 03ace75

Browse files
Antoliny0919sarahboyce
authored andcommitted
[5.1.x] Fixed #36217 -- Restored pre_save/post_save signal emission via LogEntry.save() for single-object deletion in the admin.
Regression in 40b3975. Thanks smiling-watermelon for the report. Co-authored-by: Sarah Boyce <[email protected]> Backport of c09bcee from main.
1 parent 76a9f12 commit 03ace75

File tree

8 files changed

+41
-14
lines changed

8 files changed

+41
-14
lines changed

django/contrib/admin/models.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ def log_action(
5151
change_message=change_message,
5252
)
5353

54-
def log_actions(
55-
self, user_id, queryset, action_flag, change_message="", *, single_object=False
56-
):
54+
def log_actions(self, user_id, queryset, action_flag, change_message=""):
5755
# RemovedInDjango60Warning.
5856
if type(self).log_action != LogEntryManager.log_action:
5957
warnings.warn(
@@ -93,7 +91,7 @@ def log_actions(
9391
for obj in queryset
9492
]
9593

96-
if single_object and log_entry_list:
94+
if len(log_entry_list) == 1:
9795
instance = log_entry_list[0]
9896
instance.save()
9997
return instance

django/contrib/admin/options.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,6 @@ def log_addition(self, request, obj, message):
954954
queryset=[obj],
955955
action_flag=ADDITION,
956956
change_message=message,
957-
single_object=True,
958957
)
959958

960959
def log_change(self, request, obj, message):
@@ -970,7 +969,6 @@ def log_change(self, request, obj, message):
970969
queryset=[obj],
971970
action_flag=CHANGE,
972971
change_message=message,
973-
single_object=True,
974972
)
975973

976974
def log_deletion(self, request, obj, object_repr):

docs/releases/5.1.7.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ Bugfixes
2222
of ``ManyToManyField`` related managers would always return ``0`` and
2323
``False`` when the intermediary model back references used ``to_field``
2424
(:ticket:`36197`).
25+
26+
* Fixed a regression in Django 5.1 where the ``pre_save`` and ``post_save``
27+
signals for ``LogEntry`` were not sent when deleting a single object in the
28+
admin (:ticket:`36217`).

docs/releases/5.1.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,11 @@ Miscellaneous
401401
* The minimum supported version of ``asgiref`` is increased from 3.7.0 to
402402
3.8.1.
403403

404+
* To improve performance, the ``delete_selected`` admin action now uses
405+
``QuerySet.bulk_create()`` when creating multiple ``LogEntry`` objects. As a
406+
result, ``pre_save`` and ``post_save`` signals for ``LogEntry`` are not sent
407+
when multiple objects are deleted via this admin action.
408+
404409
.. _deprecated-features-5.1:
405410

406411
Features deprecated in 5.1

tests/admin_changelist/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1742,7 +1742,7 @@ def test_no_user(self):
17421742
"""{% get_admin_log %} works without specifying a user."""
17431743
user = User(username="jondoe", password="secret", email="[email protected]")
17441744
user.save()
1745-
LogEntry.objects.log_actions(user.pk, [user], 1, single_object=True)
1745+
LogEntry.objects.log_actions(user.pk, [user], 1)
17461746
context = Context({"log_entries": LogEntry.objects.all()})
17471747
t = Template(
17481748
"{% load log %}"

tests/admin_utils/test_logentry.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.contrib.admin.utils import quote
66
from django.contrib.auth.models import User
77
from django.contrib.contenttypes.models import ContentType
8+
from django.db.models.signals import post_save, pre_save
89
from django.test import TestCase, override_settings
910
from django.urls import reverse
1011
from django.utils import translation
@@ -42,11 +43,23 @@ def setUpTestData(cls):
4243
[cls.a1],
4344
CHANGE,
4445
change_message="Changed something",
45-
single_object=True,
4646
)
4747

4848
def setUp(self):
4949
self.client.force_login(self.user)
50+
self.signals = []
51+
52+
pre_save.connect(self.pre_save_listener, sender=LogEntry)
53+
self.addCleanup(pre_save.disconnect, self.pre_save_listener, sender=LogEntry)
54+
55+
post_save.connect(self.post_save_listener, sender=LogEntry)
56+
self.addCleanup(post_save.disconnect, self.post_save_listener, sender=LogEntry)
57+
58+
def pre_save_listener(self, instance, **kwargs):
59+
self.signals.append(("pre_save", instance))
60+
61+
def post_save_listener(self, instance, created, **kwargs):
62+
self.signals.append(("post_save", instance, created))
5063

5164
def test_logentry_save(self):
5265
"""
@@ -288,6 +301,7 @@ def test_log_actions(self):
288301
for obj in queryset
289302
]
290303
self.assertSequenceEqual(logs, expected_log_values)
304+
self.assertEqual(self.signals, [])
291305

292306
# RemovedInDjango60Warning.
293307
def test_log_action_fallback(self):
@@ -371,6 +385,8 @@ def test_proxy_model_content_type_is_used_for_log_entries(self):
371385
"created_1": "00:00",
372386
}
373387
changelist_url = reverse("admin:admin_utils_articleproxy_changelist")
388+
expected_signals = []
389+
self.assertEqual(self.signals, expected_signals)
374390

375391
# add
376392
proxy_add_url = reverse("admin:admin_utils_articleproxy_add")
@@ -379,6 +395,10 @@ def test_proxy_model_content_type_is_used_for_log_entries(self):
379395
proxy_addition_log = LogEntry.objects.latest("id")
380396
self.assertEqual(proxy_addition_log.action_flag, ADDITION)
381397
self.assertEqual(proxy_addition_log.content_type, proxy_content_type)
398+
expected_signals.extend(
399+
[("pre_save", proxy_addition_log), ("post_save", proxy_addition_log, True)]
400+
)
401+
self.assertEqual(self.signals, expected_signals)
382402

383403
# change
384404
article_id = proxy_addition_log.object_id
@@ -391,6 +411,10 @@ def test_proxy_model_content_type_is_used_for_log_entries(self):
391411
proxy_change_log = LogEntry.objects.latest("id")
392412
self.assertEqual(proxy_change_log.action_flag, CHANGE)
393413
self.assertEqual(proxy_change_log.content_type, proxy_content_type)
414+
expected_signals.extend(
415+
[("pre_save", proxy_change_log), ("post_save", proxy_change_log, True)]
416+
)
417+
self.assertEqual(self.signals, expected_signals)
394418

395419
# delete
396420
proxy_delete_url = reverse(
@@ -401,6 +425,10 @@ def test_proxy_model_content_type_is_used_for_log_entries(self):
401425
proxy_delete_log = LogEntry.objects.latest("id")
402426
self.assertEqual(proxy_delete_log.action_flag, DELETION)
403427
self.assertEqual(proxy_delete_log.content_type, proxy_content_type)
428+
expected_signals.extend(
429+
[("pre_save", proxy_delete_log), ("post_save", proxy_delete_log, True)]
430+
)
431+
self.assertEqual(self.signals, expected_signals)
404432

405433
def test_action_flag_choices(self):
406434
tests = ((1, "Addition"), (2, "Change"), (3, "Deletion"))
@@ -415,15 +443,13 @@ def test_hook_get_log_entries(self):
415443
[self.a1],
416444
CHANGE,
417445
change_message="Article changed message",
418-
single_object=True,
419446
)
420447
c1 = Car.objects.create()
421448
LogEntry.objects.log_actions(
422449
self.user.pk,
423450
[c1],
424451
ADDITION,
425452
change_message="Car created message",
426-
single_object=True,
427453
)
428454
exp_str_article = escape(str(self.a1))
429455
exp_str_car = escape(str(c1))

tests/admin_views/test_history_view.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ def setUp(self):
6666
[self.superuser],
6767
CHANGE,
6868
change_message=f"Changed something {i}",
69-
single_object=True,
7069
)
7170
self.admin_login(
7271
username="super",

tests/admin_views/tests.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3855,21 +3855,18 @@ def setUpTestData(cls):
38553855
[cls.m1],
38563856
2,
38573857
change_message="Changed something",
3858-
single_object=True,
38593858
)
38603859
LogEntry.objects.log_actions(
38613860
user_pk,
38623861
[cls.m1],
38633862
1,
38643863
change_message="Added something",
3865-
single_object=True,
38663864
)
38673865
LogEntry.objects.log_actions(
38683866
user_pk,
38693867
[cls.m1],
38703868
3,
38713869
change_message="Deleted something",
3872-
single_object=True,
38733870
)
38743871

38753872
def setUp(self):

0 commit comments

Comments
 (0)