"""
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"