""" Django Admin configuration for media models. """ from django.contrib import admin from django.contrib.contenttypes.admin import GenericTabularInline from django.utils.html import format_html from django.utils.safestring import mark_safe from django.db.models import Count, Q from .models import Photo @admin.register(Photo) class PhotoAdmin(admin.ModelAdmin): """Admin interface for Photo model with enhanced features.""" list_display = [ 'thumbnail_preview', 'title', 'photo_type', 'moderation_status', 'entity_info', 'uploaded_by', 'dimensions', 'file_size_display', 'created' ] list_filter = [ 'moderation_status', 'is_approved', 'photo_type', 'is_featured', 'is_public', 'created' ] search_fields = [ 'title', 'description', 'cloudflare_image_id', 'uploaded_by__email', 'uploaded_by__username' ] readonly_fields = [ 'id', 'created', 'modified', 'content_type', 'object_id', 'moderated_at' ] raw_id_fields = ['uploaded_by', 'moderated_by'] fieldsets = ( ('CloudFlare Image', { 'fields': ( 'cloudflare_image_id', 'cloudflare_url', 'cloudflare_thumbnail_url' ) }), ('Metadata', { 'fields': ('title', 'description', 'credit', 'photo_type') }), ('Associated Entity', { 'fields': ('content_type', 'object_id') }), ('Upload Information', { 'fields': ('uploaded_by',) }), ('Moderation', { 'fields': ( 'moderation_status', 'is_approved', 'moderated_by', 'moderated_at', 'moderation_notes' ) }), ('Image Details', { 'fields': ('width', 'height', 'file_size'), 'classes': ('collapse',) }), ('Display Settings', { 'fields': ('display_order', 'is_featured', 'is_public') }), ('System', { 'fields': ('id', 'created', 'modified'), 'classes': ('collapse',) }), ) date_hierarchy = 'created' actions = ['approve_photos', 'reject_photos', 'flag_photos', 'make_featured', 'remove_featured'] def get_queryset(self, request): """Optimize queryset with select_related.""" qs = super().get_queryset(request) return qs.select_related( 'uploaded_by', 'moderated_by', 'content_type' ) def thumbnail_preview(self, obj): """Display thumbnail preview in list view.""" if obj.cloudflare_url: # Use thumbnail variant for preview from apps.media.services import CloudFlareService cf = CloudFlareService() thumbnail_url = cf.get_image_url(obj.cloudflare_image_id, 'thumbnail') return format_html( '', thumbnail_url ) return "-" thumbnail_preview.short_description = "Preview" def entity_info(self, obj): """Display entity information.""" if obj.content_type and obj.object_id: entity = obj.content_object if entity: entity_type = obj.content_type.model entity_name = getattr(entity, 'name', str(entity)) return format_html( '{}
{}', entity_name, entity_type.upper() ) return format_html('Not attached') entity_info.short_description = "Entity" def dimensions(self, obj): """Display image dimensions.""" if obj.width and obj.height: return f"{obj.width}×{obj.height}" return "-" dimensions.short_description = "Size" def file_size_display(self, obj): """Display file size in human-readable format.""" if obj.file_size: size_kb = obj.file_size / 1024 if size_kb > 1024: return f"{size_kb / 1024:.1f} MB" return f"{size_kb:.1f} KB" return "-" file_size_display.short_description = "File Size" def changelist_view(self, request, extra_context=None): """Add statistics to changelist.""" extra_context = extra_context or {} # Get photo statistics stats = Photo.objects.aggregate( total=Count('id'), pending=Count('id', filter=Q(moderation_status='pending')), approved=Count('id', filter=Q(moderation_status='approved')), rejected=Count('id', filter=Q(moderation_status='rejected')), flagged=Count('id', filter=Q(moderation_status='flagged')), ) extra_context['photo_stats'] = stats return super().changelist_view(request, extra_context) def approve_photos(self, request, queryset): """Bulk approve selected photos.""" count = 0 for photo in queryset: photo.approve(moderator=request.user, notes='Bulk approved') count += 1 self.message_user(request, f"{count} photo(s) approved successfully.") approve_photos.short_description = "Approve selected photos" def reject_photos(self, request, queryset): """Bulk reject selected photos.""" count = 0 for photo in queryset: photo.reject(moderator=request.user, notes='Bulk rejected') count += 1 self.message_user(request, f"{count} photo(s) rejected.") reject_photos.short_description = "Reject selected photos" def flag_photos(self, request, queryset): """Bulk flag selected photos for review.""" count = 0 for photo in queryset: photo.flag(moderator=request.user, notes='Flagged for review') count += 1 self.message_user(request, f"{count} photo(s) flagged for review.") flag_photos.short_description = "Flag selected photos" def make_featured(self, request, queryset): """Mark selected photos as featured.""" count = queryset.update(is_featured=True) self.message_user(request, f"{count} photo(s) marked as featured.") make_featured.short_description = "Mark as featured" def remove_featured(self, request, queryset): """Remove featured status from selected photos.""" count = queryset.update(is_featured=False) self.message_user(request, f"{count} photo(s) removed from featured.") remove_featured.short_description = "Remove featured status" # Inline admin for use in entity admin pages class PhotoInline(GenericTabularInline): """Inline admin for photos in entity pages.""" model = Photo ct_field = 'content_type' ct_fk_field = 'object_id' extra = 0 fields = ['thumbnail_preview', 'title', 'photo_type', 'moderation_status', 'display_order'] readonly_fields = ['thumbnail_preview'] can_delete = True def thumbnail_preview(self, obj): """Display thumbnail preview in inline.""" if obj.cloudflare_url: from apps.media.services import CloudFlareService cf = CloudFlareService() thumbnail_url = cf.get_image_url(obj.cloudflare_image_id, 'thumbnail') return format_html( '', thumbnail_url ) return "-" thumbnail_preview.short_description = "Preview"