summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantoliny0919 <antoliny0919@gmail.com>2025-02-28 20:17:17 +0900
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-03-04 10:34:15 +0100
commitc09bceef68e5abb79accedd12dade16aa6577a09 (patch)
tree6f0d89f6b5d517550ba10fd4002e4dc783118337
parent1759c1dbd1e3cd4c9fcd345269e0d25796468f7f (diff)
Fixed #36217 -- Restored pre_save/post_save signal emission via LogEntry.save() for single-object deletion in the admin.
Regression in 40b3975e7d3e1464a733c69171ad7d38f8814280. Thanks smiling-watermelon for the report. Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
-rw-r--r--django/contrib/admin/models.py6
-rw-r--r--django/contrib/admin/options.py2
-rw-r--r--docs/releases/5.1.7.txt4
-rw-r--r--docs/releases/5.1.txt5
-rw-r--r--tests/admin_changelist/tests.py2
-rw-r--r--tests/admin_utils/test_logentry.py32
-rw-r--r--tests/admin_views/test_history_view.py1
-rw-r--r--tests/admin_views/tests.py3
8 files changed, 41 insertions, 14 deletions
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
index 5723ebff7f..453e65cf01 100644
--- a/django/contrib/admin/models.py
+++ b/django/contrib/admin/models.py
@@ -24,9 +24,7 @@ ACTION_FLAG_CHOICES = [
class LogEntryManager(models.Manager):
use_in_migrations = True
- def log_actions(
- self, user_id, queryset, action_flag, change_message="", *, single_object=False
- ):
+ def log_actions(self, user_id, queryset, action_flag, change_message=""):
if isinstance(change_message, list):
change_message = json.dumps(change_message)
@@ -44,7 +42,7 @@ class LogEntryManager(models.Manager):
for obj in queryset
]
- if single_object and log_entry_list:
+ if len(log_entry_list) == 1:
instance = log_entry_list[0]
instance.save()
return instance
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 3c2cf9d130..090b12151a 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -946,7 +946,6 @@ class ModelAdmin(BaseModelAdmin):
queryset=[obj],
action_flag=ADDITION,
change_message=message,
- single_object=True,
)
def log_change(self, request, obj, message):
@@ -962,7 +961,6 @@ class ModelAdmin(BaseModelAdmin):
queryset=[obj],
action_flag=CHANGE,
change_message=message,
- single_object=True,
)
def log_deletions(self, request, queryset):
diff --git a/docs/releases/5.1.7.txt b/docs/releases/5.1.7.txt
index d1ed21ec5d..77e89d9c27 100644
--- a/docs/releases/5.1.7.txt
+++ b/docs/releases/5.1.7.txt
@@ -22,3 +22,7 @@ Bugfixes
of ``ManyToManyField`` related managers would always return ``0`` and
``False`` when the intermediary model back references used ``to_field``
(:ticket:`36197`).
+
+* Fixed a regression in Django 5.1 where the ``pre_save`` and ``post_save``
+ signals for ``LogEntry`` were not sent when deleting a single object in the
+ admin (:ticket:`36217`).
diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt
index 799f3ee819..a57bf60cbd 100644
--- a/docs/releases/5.1.txt
+++ b/docs/releases/5.1.txt
@@ -401,6 +401,11 @@ Miscellaneous
* The minimum supported version of ``asgiref`` is increased from 3.7.0 to
3.8.1.
+* To improve performance, the ``delete_selected`` admin action now uses
+ ``QuerySet.bulk_create()`` when creating multiple ``LogEntry`` objects. As a
+ result, ``pre_save`` and ``post_save`` signals for ``LogEntry`` are not sent
+ when multiple objects are deleted via this admin action.
+
.. _deprecated-features-5.1:
Features deprecated in 5.1
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index 17866c8ad6..6003ce47d8 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -1845,7 +1845,7 @@ class GetAdminLogTests(TestCase):
"""{% get_admin_log %} works without specifying a user."""
user = User(username="jondoe", password="secret", email="super@example.com")
user.save()
- LogEntry.objects.log_actions(user.pk, [user], 1, single_object=True)
+ LogEntry.objects.log_actions(user.pk, [user], 1)
context = Context({"log_entries": LogEntry.objects.all()})
t = Template(
"{% load log %}"
diff --git a/tests/admin_utils/test_logentry.py b/tests/admin_utils/test_logentry.py
index 491a220199..7ba43c4ba0 100644
--- a/tests/admin_utils/test_logentry.py
+++ b/tests/admin_utils/test_logentry.py
@@ -5,6 +5,7 @@ from django.contrib.admin.models import ADDITION, CHANGE, DELETION, LogEntry
from django.contrib.admin.utils import quote
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
+from django.db.models.signals import post_save, pre_save
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils import translation
@@ -41,11 +42,23 @@ class LogEntryTests(TestCase):
[cls.a1],
CHANGE,
change_message="Changed something",
- single_object=True,
)
def setUp(self):
self.client.force_login(self.user)
+ self.signals = []
+
+ pre_save.connect(self.pre_save_listener, sender=LogEntry)
+ self.addCleanup(pre_save.disconnect, self.pre_save_listener, sender=LogEntry)
+
+ post_save.connect(self.post_save_listener, sender=LogEntry)
+ self.addCleanup(post_save.disconnect, self.post_save_listener, sender=LogEntry)
+
+ def pre_save_listener(self, instance, **kwargs):
+ self.signals.append(("pre_save", instance))
+
+ def post_save_listener(self, instance, created, **kwargs):
+ self.signals.append(("post_save", instance, created))
def test_logentry_save(self):
"""
@@ -271,6 +284,7 @@ class LogEntryTests(TestCase):
for obj in queryset
]
self.assertSequenceEqual(logs, expected_log_values)
+ self.assertEqual(self.signals, [])
def test_recentactions_without_content_type(self):
"""
@@ -314,6 +328,8 @@ class LogEntryTests(TestCase):
"created_1": "00:00",
}
changelist_url = reverse("admin:admin_utils_articleproxy_changelist")
+ expected_signals = []
+ self.assertEqual(self.signals, expected_signals)
# add
proxy_add_url = reverse("admin:admin_utils_articleproxy_add")
@@ -322,6 +338,10 @@ class LogEntryTests(TestCase):
proxy_addition_log = LogEntry.objects.latest("id")
self.assertEqual(proxy_addition_log.action_flag, ADDITION)
self.assertEqual(proxy_addition_log.content_type, proxy_content_type)
+ expected_signals.extend(
+ [("pre_save", proxy_addition_log), ("post_save", proxy_addition_log, True)]
+ )
+ self.assertEqual(self.signals, expected_signals)
# change
article_id = proxy_addition_log.object_id
@@ -334,6 +354,10 @@ class LogEntryTests(TestCase):
proxy_change_log = LogEntry.objects.latest("id")
self.assertEqual(proxy_change_log.action_flag, CHANGE)
self.assertEqual(proxy_change_log.content_type, proxy_content_type)
+ expected_signals.extend(
+ [("pre_save", proxy_change_log), ("post_save", proxy_change_log, True)]
+ )
+ self.assertEqual(self.signals, expected_signals)
# delete
proxy_delete_url = reverse(
@@ -344,6 +368,10 @@ class LogEntryTests(TestCase):
proxy_delete_log = LogEntry.objects.latest("id")
self.assertEqual(proxy_delete_log.action_flag, DELETION)
self.assertEqual(proxy_delete_log.content_type, proxy_content_type)
+ expected_signals.extend(
+ [("pre_save", proxy_delete_log), ("post_save", proxy_delete_log, True)]
+ )
+ self.assertEqual(self.signals, expected_signals)
def test_action_flag_choices(self):
tests = ((1, "Addition"), (2, "Change"), (3, "Deletion"))
@@ -358,7 +386,6 @@ class LogEntryTests(TestCase):
[self.a1],
CHANGE,
change_message="Article changed message",
- single_object=True,
)
c1 = Car.objects.create()
LogEntry.objects.log_actions(
@@ -366,7 +393,6 @@ class LogEntryTests(TestCase):
[c1],
ADDITION,
change_message="Car created message",
- single_object=True,
)
exp_str_article = escape(str(self.a1))
exp_str_car = escape(str(c1))
diff --git a/tests/admin_views/test_history_view.py b/tests/admin_views/test_history_view.py
index dfac3530bf..7079c1d0d8 100644
--- a/tests/admin_views/test_history_view.py
+++ b/tests/admin_views/test_history_view.py
@@ -66,7 +66,6 @@ class SeleniumTests(AdminSeleniumTestCase):
[self.superuser],
CHANGE,
change_message=f"Changed something {i}",
- single_object=True,
)
self.admin_login(
username="super",
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 1fa2c62353..5e1aa719c1 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -3884,21 +3884,18 @@ class AdminViewStringPrimaryKeyTest(TestCase):
[cls.m1],
2,
change_message="Changed something",
- single_object=True,
)
LogEntry.objects.log_actions(
user_pk,
[cls.m1],
1,
change_message="Added something",
- single_object=True,
)
LogEntry.objects.log_actions(
user_pk,
[cls.m1],
3,
change_message="Deleted something",
- single_object=True,
)
def setUp(self):