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().
This commit is contained in:
Jacob Rief 2023-02-08 18:37:32 +01:00 committed by GitHub
parent 1964e4367f
commit 473283d241
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 76 additions and 13 deletions

View File

@ -336,6 +336,7 @@ class AdminSite:
"available_apps": self.get_app_list(request), "available_apps": self.get_app_list(request),
"is_popup": False, "is_popup": False,
"is_nav_sidebar_enabled": self.enable_nav_sidebar, "is_nav_sidebar_enabled": self.enable_nav_sidebar,
"log_entries": self.get_log_entries(request),
} }
def password_change(self, request, extra_context=None): def password_change(self, request, extra_context=None):
@ -588,6 +589,11 @@ class AdminSite:
context, 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): class DefaultAdminSite(LazyObject):
def _setup(self): def _setup(self):

View File

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

View File

@ -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_popup``: whether the current page is displayed in a popup window
* ``is_nav_sidebar_enabled``: :attr:`AdminSite.enable_nav_sidebar` * ``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) .. 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 Raises ``django.contrib.admin.sites.NotRegistered`` if a model isn't
already registered. 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-to-urlconf:
Hooking ``AdminSite`` instances into your URLconf Hooking ``AdminSite`` instances into your URLconf

View File

@ -43,7 +43,8 @@ Minor features
:mod:`django.contrib.admin` :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` :mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

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

View File

@ -35,3 +35,19 @@ site = admin.AdminSite(name="admin")
site.register(Article) site.register(Article)
site.register(ArticleProxy) site.register(ArticleProxy)
site.register(Site, SiteAdmin) 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)

View File

@ -10,7 +10,7 @@ from django.urls import reverse
from django.utils import translation from django.utils import translation
from django.utils.html import escape 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") @override_settings(ROOT_URLCONF="admin_utils.urls")
@ -318,3 +318,30 @@ class LogEntryTests(TestCase):
with self.subTest(action_flag=action_flag): with self.subTest(action_flag=action_flag):
log = LogEntry(action_flag=action_flag) log = LogEntry(action_flag=action_flag)
self.assertEqual(log.get_action_flag_display(), display_name) 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")

View File

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