from django.contrib import admin from django.contrib.admin import AdminSite from django.utils.html import format_html from django.urls import reverse from django.utils.safestring import mark_safe from django_fsm_log.models import StateLog from .models import EditSubmission, PhotoSubmission class ModerationAdminSite(AdminSite): site_header = "ThrillWiki Moderation" site_title = "ThrillWiki Moderation" index_title = "Moderation Dashboard" def has_permission(self, request): """Only allow moderators and above to access this admin site""" return request.user.is_authenticated and request.user.role in [ "MODERATOR", "ADMIN", "SUPERUSER", ] moderation_site = ModerationAdminSite(name="moderation") class EditSubmissionAdmin(admin.ModelAdmin): list_display = [ "id", "user_link", "content_type", "content_link", "status", "created_at", "handled_by", ] list_filter = ["status", "content_type", "created_at"] search_fields = ["user__username", "reason", "source", "notes"] readonly_fields = [ "user", "content_type", "object_id", "changes", "created_at", ] def user_link(self, obj): url = reverse("admin:accounts_user_change", args=[obj.user.id]) return format_html('{}', url, obj.user.username) user_link.short_description = "User" def content_link(self, obj): if hasattr(obj.content_object, "get_absolute_url"): url = obj.content_object.get_absolute_url() return format_html('{}', url, str(obj.content_object)) return str(obj.content_object) content_link.short_description = "Content" def save_model(self, request, obj, form, change): if "status" in form.changed_data: if obj.status == "APPROVED": obj.approve(request.user) elif obj.status == "REJECTED": obj.reject(request.user) elif obj.status == "ESCALATED": obj.escalate(request.user) super().save_model(request, obj, form, change) class PhotoSubmissionAdmin(admin.ModelAdmin): list_display = [ "id", "user_link", "content_type", "content_link", "photo_preview", "status", "created_at", "handled_by", ] list_filter = ["status", "content_type", "created_at"] search_fields = ["user__username", "caption", "notes"] readonly_fields = [ "user", "content_type", "object_id", "photo_preview", "created_at", ] def user_link(self, obj): url = reverse("admin:accounts_user_change", args=[obj.user.id]) return format_html('{}', url, obj.user.username) user_link.short_description = "User" def content_link(self, obj): if hasattr(obj.content_object, "get_absolute_url"): url = obj.content_object.get_absolute_url() return format_html('{}', url, str(obj.content_object)) return str(obj.content_object) content_link.short_description = "Content" def photo_preview(self, obj): if obj.photo: return format_html( '', obj.photo.url, ) return "" photo_preview.short_description = "Photo Preview" def save_model(self, request, obj, form, change): if "status" in form.changed_data: if obj.status == "APPROVED": obj.approve(request.user, obj.notes) elif obj.status == "REJECTED": obj.reject(request.user, obj.notes) super().save_model(request, obj, form, change) class HistoryEventAdmin(admin.ModelAdmin): """Admin interface for viewing model history events""" list_display = [ "pgh_label", "pgh_created_at", "get_object_link", "get_context", ] list_filter = ["pgh_label", "pgh_created_at"] readonly_fields = [ "pgh_label", "pgh_obj_id", "pgh_data", "pgh_context", "pgh_created_at", ] date_hierarchy = "pgh_created_at" def get_object_link(self, obj): """Display a link to the related object if possible""" if obj.pgh_obj and hasattr(obj.pgh_obj, "get_absolute_url"): url = obj.pgh_obj.get_absolute_url() return format_html('{}', url, str(obj.pgh_obj)) return str(obj.pgh_obj or "") get_object_link.short_description = "Object" def get_context(self, obj): """Format the context data nicely""" if not obj.pgh_context: return "-" html = [""] for key, value in obj.pgh_context.items(): html.append(f"") html.append("
{key}{value}
") return mark_safe("".join(html)) get_context.short_description = "Context" class StateLogAdmin(admin.ModelAdmin): """Admin interface for FSM transition logs.""" list_display = [ 'id', 'timestamp', 'get_model_name', 'get_object_link', 'state', 'transition', 'get_user_link', ] list_filter = [ 'content_type', 'state', 'transition', 'timestamp', ] search_fields = [ 'state', 'transition', 'description', 'by__username', ] readonly_fields = [ 'timestamp', 'content_type', 'object_id', 'state', 'transition', 'by', 'description', ] date_hierarchy = 'timestamp' ordering = ['-timestamp'] def get_model_name(self, obj): """Get the model name from content type.""" return obj.content_type.model get_model_name.short_description = 'Model' def get_object_link(self, obj): """Create link to the actual object.""" if obj.content_object: # Try to get absolute URL if available if hasattr(obj.content_object, 'get_absolute_url'): url = obj.content_object.get_absolute_url() else: url = '#' return format_html('{}', url, str(obj.content_object)) return f"ID: {obj.object_id}" get_object_link.short_description = 'Object' def get_user_link(self, obj): """Create link to the user who performed the transition.""" if obj.by: url = reverse('admin:accounts_user_change', args=[obj.by.id]) return format_html('{}', url, obj.by.username) return '-' get_user_link.short_description = 'User' # Register with moderation site only moderation_site.register(EditSubmission, EditSubmissionAdmin) moderation_site.register(PhotoSubmission, PhotoSubmissionAdmin) moderation_site.register(StateLog, StateLogAdmin) # We will register concrete event models as they are created during migrations # Example: moderation_site.register(DesignerEvent, HistoryEventAdmin)