""" 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( '', obj.image.url, ) return format_html('No image') @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( '{}', url, str(content_obj)[:30], ) except Exception: return str(content_obj)[:30] return "-" except Exception: return format_html('Not found') @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( 'Primary' ) return format_html( '-' ) @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