structured_search

This commit is contained in:
Arjuna Del Toso 2024-10-29 23:36:56 -04:00
parent 5da57d08d2
commit b9faed9b7e

View file

@ -1,5 +1,6 @@
from functools import cached_property
import re
from django.apps import apps
from django.contrib import admin
from django.contrib.auth import get_user_model
@ -11,6 +12,10 @@ from auditlog.filters import CIDFilter, ResourceTypeFilter
from auditlog.mixins import LogEntryAdminMixin
from auditlog.models import LogEntry
STRUCTURED_SEARCH_PATTERN = re.compile(
r"^(?P<model_name>[A-Za-z_][A-Za-z0-9_]*):(?P<id>[1-9][0-9]*)$"
)
@admin.register(LogEntry)
class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
@ -61,54 +66,63 @@ class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
self.request = request
queryset = super().get_queryset(request=request)
# Check for the `ss` parameter for structured search
structured_search = request.GET.get("ss")
if structured_search:
# Parse structured search term as 'ModelName:id'
try:
model_name, object_id = structured_search.split(":")
object_id = int(object_id)
except (ValueError, TypeError):
# If the format is incorrect, return an empty queryset and show a message
if not getattr(request, "_message_shown", False):
self.message_user(
request,
"Structured search format must be 'ModelName:id'.",
level="warning",
)
request._message_shown = True
return queryset.none()
# Custom tampering with Django Admin search.
# We support a structured search here to allow
# searching for log entries related to a specific object.
search = request.GET.get("q")
if search:
match = STRUCTURED_SEARCH_PATTERN.match(search)
if match:
try:
model_name = match.group("model_name")
object_id = int(match.group("id"))
except (ValueError, TypeError):
# If the format is incorrect, return an empty queryset and show a message
if not getattr(request, "_message_shown", False):
self.message_user(
request,
"Structured search format must be 'ModelName:id'.",
level="warning",
)
request._message_shown = True
return queryset.none()
# Attempt to retrieve the specified model
try:
model = apps.get_model(app_label="api", model_name=model_name)
if not model:
raise LookupError
except LookupError:
if not getattr(request, "_message_shown", False):
self.message_user(
request,
f"Model '{model_name}' does not exist.",
level="warning",
# Attempt to retrieve the specified model
try:
model = apps.get_model(
app_label="api", model_name=model_name
)
request._message_shown = True
return queryset.none()
if not model:
raise LookupError
except LookupError:
if not getattr(request, "_message_shown", False):
self.message_user(
request,
f"Model '{model_name}' does not exist.",
level="warning",
)
request._message_shown = True
return queryset.none()
# Attempt to retrieve the object and filter log entries
try:
model.objects.only("id").get(pk=object_id) # Lookup.
content_type = ContentType.objects.get_for_model(model)
queryset = queryset.filter(
content_type=content_type, object_id=object_id
)
except ObjectDoesNotExist:
if not getattr(request, "_message_shown", False):
self.message_user(
request,
f"{model_name} instance with ID {object_id} does not exist.",
level="warning",
# Attempt to retrieve the object and filter log entries
try:
model.objects.only("id").get(pk=object_id) # Lookup.
content_type = ContentType.objects.get_for_model(model)
queryset = queryset.filter(
content_type=content_type, object_id=object_id
)
request._message_shown = True
return queryset.none()
# We need to remove `q` from the request to prevent Django
# from trying to search again with structured search query.
request.GET = request.GET.copy()
request.GET.pop("q", None)
except ObjectDoesNotExist:
if not getattr(request, "_message_shown", False):
self.message_user(
request,
f"{model_name} instance with ID {object_id} does not exist.",
level="warning",
)
request._message_shown = True
return queryset.none()
return queryset # Return filtered or default queryset based on the presence of `ss`