Files
thrillwiki_django_no_react/shared/media/admin.py
pacnpal edcd8f2076 Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
2025-12-23 16:41:42 -05:00

203 lines
6.8 KiB
Python

"""
Django admin configuration for the Media (shared) application.
This module provides admin interfaces for photo management with
bulk operations and content type linking.
Performance targets:
- List views: < 10 queries
- Page load time: < 500ms for 100 records
"""
from django.contrib import admin
from django.utils.html import format_html
from .models import Photo
@admin.register(Photo)
class PhotoAdmin(admin.ModelAdmin):
"""
Admin interface for Photo management.
Provides photo administration with:
- Thumbnail previews in list view
- Content type linking
- Bulk primary photo management
- Alt text validation warnings
Query optimizations:
- select_related: content_type
"""
list_display = (
"thumbnail_preview",
"content_type_display",
"content_object_link",
"caption_preview",
"is_primary_badge",
"has_alt_text",
"created_at",
)
list_filter = ("content_type", "is_primary", "created_at")
list_select_related = ["content_type"]
search_fields = ("caption", "alt_text", "object_id")
readonly_fields = ("thumbnail_preview", "created_at", "updated_at")
list_per_page = 50
show_full_result_count = False
ordering = ("-created_at",)
fieldsets = (
(
"Image",
{
"fields": ("image", "thumbnail_preview"),
"description": "Upload and preview the photo.",
},
),
(
"Related Object",
{
"fields": ("content_type", "object_id"),
"description": "The object this photo belongs to (park, ride, etc.).",
},
),
(
"Photo Details",
{
"fields": ("caption", "alt_text", "is_primary"),
"description": "Caption and accessibility information.",
},
),
(
"Metadata",
{
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
},
),
)
@admin.display(description="Preview")
def thumbnail_preview(self, obj):
"""Display thumbnail preview of the photo."""
if obj.image:
return format_html(
'<img src="{}" style="max-height: 60px; max-width: 100px; '
'border-radius: 4px; object-fit: cover;" loading="lazy" />',
obj.image.url,
)
return format_html('<span style="color: gray;">No image</span>')
@admin.display(description="Type")
def content_type_display(self, obj):
"""Display content type in a readable format."""
if obj.content_type:
return f"{obj.content_type.app_label}.{obj.content_type.model}"
return "-"
@admin.display(description="Object")
def content_object_link(self, obj):
"""Create a link to the related object's admin page."""
try:
content_obj = obj.content_object
if content_obj:
from django.urls import reverse
app_label = obj.content_type.app_label
model_name = obj.content_type.model
try:
url = reverse(
f"admin:{app_label}_{model_name}_change",
args=[content_obj.pk],
)
return format_html(
'<a href="{}">{}</a>',
url,
str(content_obj)[:30],
)
except Exception:
return str(content_obj)[:30]
return "-"
except Exception:
return format_html('<span style="color: red;">Not found</span>')
@admin.display(description="Caption")
def caption_preview(self, obj):
"""Display truncated caption."""
if obj.caption:
return obj.caption[:40] + "..." if len(obj.caption) > 40 else obj.caption
return "-"
@admin.display(description="Primary")
def is_primary_badge(self, obj):
"""Display primary status with badge."""
if obj.is_primary:
return format_html(
'<span style="background-color: green; color: white; padding: 2px 8px; '
'border-radius: 4px; font-size: 11px;">Primary</span>'
)
return format_html(
'<span style="background-color: gray; color: white; padding: 2px 8px; '
'border-radius: 4px; font-size: 11px;">-</span>'
)
@admin.display(description="Alt", boolean=True)
def has_alt_text(self, obj):
"""Indicate if photo has alt text for accessibility."""
return bool(obj.alt_text)
@admin.action(description="Set as primary photo")
def set_primary(self, request, queryset):
"""Set selected photos as primary for their objects."""
for photo in queryset:
# Unset other primary photos for the same object
Photo.objects.filter(
content_type=photo.content_type,
object_id=photo.object_id,
is_primary=True,
).exclude(pk=photo.pk).update(is_primary=False)
photo.is_primary = True
photo.save(update_fields=["is_primary"])
self.message_user(request, f"Set {queryset.count()} photos as primary.")
@admin.action(description="Remove primary status")
def remove_primary(self, request, queryset):
"""Remove primary status from selected photos."""
updated = queryset.update(is_primary=False)
self.message_user(request, f"Removed primary status from {updated} photos.")
@admin.action(description="Flag missing alt text")
def flag_missing_alt(self, request, queryset):
"""List photos missing alt text for accessibility."""
missing = queryset.filter(alt_text__isnull=True) | queryset.filter(alt_text="")
count = missing.count()
if count:
self.message_user(
request,
f"Found {count} photos missing alt text. Please add alt text for accessibility.",
level="WARNING",
)
else:
self.message_user(request, "All selected photos have alt text.")
def get_actions(self, request):
"""Add custom actions to the admin."""
actions = super().get_actions(request)
actions["set_primary"] = (
self.set_primary,
"set_primary",
"Set as primary photo",
)
actions["remove_primary"] = (
self.remove_primary,
"remove_primary",
"Remove primary status",
)
actions["flag_missing_alt"] = (
self.flag_missing_alt,
"flag_missing_alt",
"Flag missing alt text",
)
return actions