Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions django_admin_react/api/views/recent_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from django_admin_react.api.permissions import forbidden_response
from django_admin_react.api.permissions import is_admin_user
from django_admin_react.api.registry import get_admin_site
from django_admin_react.audit import user_log_entries

# Default / ceiling for the number of entries returned. Django's index
# shows 10; the ceiling keeps a hand-crafted ``?limit=`` from scanning
Expand Down Expand Up @@ -60,12 +61,13 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:

# ``is_admin_user`` guarantees an authenticated user, so pk is set
# (it may be int or str for a custom user model — both are valid
# lookups). Scoped to this user only — Django's own index panel
# filters the same way.
# lookups). The LogEntry query lives in ``django_admin_react.audit``
# (outside ``api/``): LogEntry is Django's own audit table, not a
# consumer model, so the get_queryset rule is inapplicable — and
# keeping ``.objects.filter`` out of ``api/`` honours SECURITY §3
# rule 10 at the file-system level (see audit.py + test_s15).
user_pk = cast("str | int", request.user.pk)
entries = list(
LogEntry.objects.filter(user__pk=user_pk).order_by("-action_time")[: _limit(request)]
)
entries = user_log_entries(user_pk, _limit(request))
body = {"actions": [_serialize_action(e, admin_site, request) for e in entries]}
response = JsonResponse(body, status=200)
response["Cache-Control"] = "no-store"
Expand Down
16 changes: 16 additions & 0 deletions django_admin_react/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

- :func:`object_log_entries` — the ``LogEntry`` queryset for one object,
newest-first, with the acting user pre-fetched.
- :func:`user_log_entries` — a user's own recent ``LogEntry`` rows
(the index "Recent actions" feed), newest-first.
"""

from __future__ import annotations
Expand All @@ -40,3 +42,17 @@ def object_log_entries(obj: Model) -> QuerySet[LogEntry]:
.select_related("user")
.order_by("-action_time")
)


def user_log_entries(user_pk: str | int, limit: int) -> list[LogEntry]:
"""Return ``user_pk``'s most recent ``LogEntry`` rows, newest first.

Backs the index "Recent actions" panel — the same per-user scope
Django's ``AdminSite.index`` applies
(``LogEntry.objects.filter(user=request.user)``). ``limit`` is sliced
in the database; callers must gate the request first and pass an
authenticated user's pk (int or str, per the user model). The
``user`` filter is the security boundary: the feed never surfaces
another user's actions.
"""
return list(LogEntry.objects.filter(user__pk=user_pk).order_by("-action_time")[:limit])