diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 34477900f1..61be31d890 100644 --- a/django/contrib/admin/sites.py +++ b/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): diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py index 098aa640d5..00cd40ff8a 100644 --- a/django/contrib/admin/templatetags/log.py +++ b/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 "" 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 "" diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f6db3dca4f..eb6ae1ecc2 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/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 diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index a6f827e472..8a2499e7d4 100644 --- a/docs/releases/5.0.txt +++ b/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` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index a2db24fea1..413d6d4d7f 100644 --- a/tests/admin_changelist/tests.py +++ b/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 “”.") + self.assertEqual(t.render(context), "Added “”.") def test_missing_args(self): msg = "'get_admin_log' statements require two arguments" diff --git a/tests/admin_utils/admin.py b/tests/admin_utils/admin.py index 967f37faf3..a7cfcfa6b9 100644 --- a/tests/admin_utils/admin.py +++ b/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) diff --git a/tests/admin_utils/test_logentry.py b/tests/admin_utils/test_logentry.py index dfa7962b1e..b700fe54b9 100644 --- a/tests/admin_utils/test_logentry.py +++ b/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") diff --git a/tests/admin_utils/urls.py b/tests/admin_utils/urls.py index 1b6a97e4bd..69db6afc46 100644 --- a/tests/admin_utils/urls.py +++ b/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), ]