Browse Source

Fixed #34303 –- Allowed customizing admin site log entry list.

Added AdminSite.get_log_entries() as an override point and made this
available to the template via each_context().
Jacob Rief 2 years ago
parent
commit
473283d241

+ 6 - 0
django/contrib/admin/sites.py

@@ -336,6 +336,7 @@ class AdminSite:
             "available_apps": self.get_app_list(request),
             "is_popup": False,
             "is_nav_sidebar_enabled": self.enable_nav_sidebar,
+            "log_entries": self.get_log_entries(request),
         }
 
     def password_change(self, request, extra_context=None):
@@ -588,6 +589,11 @@ class AdminSite:
             context,
         )
 
+    def get_log_entries(self, request):
+        from django.contrib.admin.models import LogEntry
+
+        return LogEntry.objects.select_related("content_type", "user")
+
 
 class DefaultAdminSite(LazyObject):
     def _setup(self):

+ 4 - 8
django/contrib/admin/templatetags/log.py

@@ -1,5 +1,4 @@
 from django import template
-from django.contrib.admin.models import LogEntry
 
 register = template.Library()
 
@@ -12,16 +11,13 @@ class AdminLogNode(template.Node):
         return "<GetAdminLog Node>"
 
     def render(self, context):
-        if self.user is None:
-            entries = LogEntry.objects.all()
-        else:
+        entries = context["log_entries"]
+        if self.user is not None:
             user_id = self.user
             if not user_id.isdigit():
                 user_id = context[self.user].pk
-            entries = LogEntry.objects.filter(user__pk=user_id)
-        context[self.varname] = entries.select_related("content_type", "user")[
-            : int(self.limit)
-        ]
+            entries = entries.filter(user__pk=user_id)
+        context[self.varname] = entries[: int(self.limit)]
         return ""
 
 

+ 10 - 0
docs/ref/contrib/admin/index.txt

@@ -2832,6 +2832,7 @@ Templates can override or extend base admin templates as described in
 
     * ``is_popup``: whether the current page is displayed in a popup window
     * ``is_nav_sidebar_enabled``: :attr:`AdminSite.enable_nav_sidebar`
+    * ``log_entries``: :meth:`.AdminSite.get_log_entries`
 
 .. method:: AdminSite.get_app_list(request, app_label=None)
 
@@ -2889,6 +2890,15 @@ Templates can override or extend base admin templates as described in
     Raises ``django.contrib.admin.sites.NotRegistered`` if a model isn't
     already registered.
 
+.. method:: AdminSite.get_log_entries(request)
+
+    .. versionadded:: 5.0
+
+    Returns a queryset for the related
+    :class:`~django.contrib.admin.models.LogEntry` instances, shown on the site
+    index page. This method can be overridden to filter the log entries by
+    other criteria.
+
 .. _hooking-adminsite-to-urlconf:
 
 Hooking ``AdminSite`` instances into your URLconf

+ 2 - 1
docs/releases/5.0.txt

@@ -43,7 +43,8 @@ Minor features
 :mod:`django.contrib.admin`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-* ...
+* The new :meth:`.AdminSite.get_log_entries` method allows customizing the
+  queryset for the site's listed log entries.
 
 :mod:`django.contrib.admindocs`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ 8 - 2
tests/admin_changelist/tests.py

@@ -1595,7 +1595,12 @@ class GetAdminLogTests(TestCase):
         {% get_admin_log %} works if the user model's primary key isn't named
         'id'.
         """
-        context = Context({"user": CustomIdUser()})
+        context = Context(
+            {
+                "user": CustomIdUser(),
+                "log_entries": LogEntry.objects.all(),
+            }
+        )
         template = Template(
             "{% load log %}{% get_admin_log 10 as admin_log for_user user %}"
         )
@@ -1608,6 +1613,7 @@ class GetAdminLogTests(TestCase):
         user.save()
         ct = ContentType.objects.get_for_model(User)
         LogEntry.objects.log_action(user.pk, ct.pk, user.pk, repr(user), 1)
+        context = Context({"log_entries": LogEntry.objects.all()})
         t = Template(
             "{% load log %}"
             "{% get_admin_log 100 as admin_log %}"
@@ -1615,7 +1621,7 @@ class GetAdminLogTests(TestCase):
             "{{ entry|safe }}"
             "{% endfor %}"
         )
-        self.assertEqual(t.render(Context({})), "Added “<User: jondoe>”.")
+        self.assertEqual(t.render(context), "Added “<User: jondoe>”.")
 
     def test_missing_args(self):
         msg = "'get_admin_log' statements require two arguments"

+ 16 - 0
tests/admin_utils/admin.py

@@ -35,3 +35,19 @@ site = admin.AdminSite(name="admin")
 site.register(Article)
 site.register(ArticleProxy)
 site.register(Site, SiteAdmin)
+
+
+class CustomAdminSite(admin.AdminSite):
+    def get_log_entries(self, request):
+        from django.contrib.contenttypes.models import ContentType
+
+        log_entries = super().get_log_entries(request)
+        return log_entries.filter(
+            content_type__in=ContentType.objects.get_for_models(
+                *self._registry.keys()
+            ).values()
+        )
+
+
+custom_site = CustomAdminSite(name="custom_admin")
+custom_site.register(Article)

+ 28 - 1
tests/admin_utils/test_logentry.py

@@ -10,7 +10,7 @@ from django.urls import reverse
 from django.utils import translation
 from django.utils.html import escape
 
-from .models import Article, ArticleProxy, Site
+from .models import Article, ArticleProxy, Car, Site
 
 
 @override_settings(ROOT_URLCONF="admin_utils.urls")
@@ -318,3 +318,30 @@ class LogEntryTests(TestCase):
             with self.subTest(action_flag=action_flag):
                 log = LogEntry(action_flag=action_flag)
                 self.assertEqual(log.get_action_flag_display(), display_name)
+
+    def test_hook_get_log_entries(self):
+        LogEntry.objects.log_action(
+            self.user.pk,
+            ContentType.objects.get_for_model(Article).pk,
+            self.a1.pk,
+            "Article changed",
+            CHANGE,
+            change_message="Article changed message",
+        )
+        c1 = Car.objects.create()
+        LogEntry.objects.log_action(
+            self.user.pk,
+            ContentType.objects.get_for_model(Car).pk,
+            c1.pk,
+            "Car created",
+            ADDITION,
+            change_message="Car created message",
+        )
+        response = self.client.get(reverse("admin:index"))
+        self.assertContains(response, "Article changed")
+        self.assertContains(response, "Car created")
+
+        # site "custom_admin" only renders log entries of registered models
+        response = self.client.get(reverse("custom_admin:index"))
+        self.assertContains(response, "Article changed")
+        self.assertNotContains(response, "Car created")

+ 2 - 1
tests/admin_utils/urls.py

@@ -1,7 +1,8 @@
 from django.urls import path
 
-from .admin import site
+from .admin import custom_site, site
 
 urlpatterns = [
     path("test_admin/admin/", site.urls),
+    path("test_admin/custom_admin/", custom_site.urls),
 ]