from django.contrib import admin from django.utils.html import format_html from django.utils import timezone from submissions.models import ( SteamAPIKey, SteamCollection, SteamCollectionItem, Submission, PuzzleResponse, SubmissionFile, ) @admin.register(SteamAPIKey) class SteamAPIKeyAdmin(admin.ModelAdmin): list_display = ["name", "masked_api_key", "is_active", "last_used", "created_at"] list_filter = ["is_active", "created_at", "last_used"] search_fields = ["name", "description"] readonly_fields = ["created_at", "updated_at", "last_used", "masked_api_key"] fieldsets = ( ("Basic Information", {"fields": ("name", "description", "is_active")}), ( "API Key", { "fields": ("api_key", "masked_api_key"), "description": "Get your Steam API key from https://steamcommunity.com/dev/apikey", }, ), ( "Metadata", { "fields": ("created_at", "updated_at", "last_used"), "classes": ("collapse",), }, ), ) def masked_api_key(self, obj): """Display masked API key in admin""" if obj.api_key: return format_html( '{}', obj.masked_key, ) return "No key set" masked_api_key.short_description = "API Key (Masked)" def get_queryset(self, request): """Only superusers can see API keys""" qs = super().get_queryset(request) if not request.user.is_superuser: return qs.none() return qs def has_view_permission(self, request, obj=None): """Only superusers can view API keys""" return request.user.is_superuser def has_add_permission(self, request): """Only superusers can add API keys""" return request.user.is_superuser def has_change_permission(self, request, obj=None): """Only superusers can change API keys""" return request.user.is_superuser def has_delete_permission(self, request, obj=None): """Only superusers can delete API keys""" return request.user.is_superuser @admin.register(SteamCollection) class SteamCollectionAdmin(admin.ModelAdmin): list_display = [ "title", "steam_id", "author_name", "total_items", "current_favorites", "last_fetched", "is_active", ] list_filter = ["is_active", "last_fetched", "created_at"] search_fields = ["title", "steam_id", "author_name", "description"] readonly_fields = ["steam_id", "created_at", "updated_at", "last_fetched"] fieldsets = ( ( "Basic Information", {"fields": ("steam_id", "url", "title", "description", "is_active")}, ), ("Author Information", {"fields": ("author_name", "author_steam_id")}), ( "Statistics", { "fields": ( "total_items", "unique_visitors", "current_favorites", "total_favorites", ) }, ), ( "Timestamps", { "fields": ( "steam_created_date", "steam_updated_date", "created_at", "updated_at", "last_fetched", ) }, ), ("Status", {"fields": ("fetch_error",)}), ) @admin.register(SteamCollectionItem) class SteamCollectionItemAdmin(admin.ModelAdmin): list_display = [ "title", "steam_item_id", "collection", "author_name", "order_index", ] list_filter = ["collection", "created_at"] search_fields = ["title", "steam_item_id", "author_name", "description"] readonly_fields = ["created_at", "updated_at"] fieldsets = ( ( "Basic Information", { "fields": ( "collection", "steam_item_id", "title", "description", "order_index", ) }, ), ("Author Information", {"fields": ("author_name", "author_steam_id")}), ("Metadata", {"fields": ("tags",)}), ("Timestamps", {"fields": ("created_at", "updated_at")}), ("Points factor", {"fields": ("points_factor", "points_value")}), ) class SubmissionFileInline(admin.TabularInline): model = SubmissionFile extra = 0 readonly_fields = ["file_size", "content_type", "ocr_processed", "created_at"] fields = [ "file", "original_filename", "file_size", "content_type", "ocr_processed", "ocr_error", ] class PuzzleResponseInline(admin.TabularInline): model = PuzzleResponse extra = 0 readonly_fields = ["created_at", "updated_at"] fields = [ "puzzle", "puzzle_name", "cost", "cycles", "area", "needs_manual_validation", "ocr_confidence_cost", "ocr_confidence_cycles", "ocr_confidence_area", ] @admin.register(Submission) class SubmissionAdmin(admin.ModelAdmin): list_display = [ "id", "user", "total_responses", "needs_validation", "manual_validation_requested", "is_validated", "created_at", ] list_filter = [ "is_validated", "manual_validation_requested", "created_at", "updated_at", ] search_fields = ["id", "user__username", "notes"] readonly_fields = [ "id", "created_at", "updated_at", "total_responses", "needs_validation", ] inlines = [PuzzleResponseInline] fieldsets = ( ("Basic Information", {"fields": ("id", "user", "notes")}), ( "Validation", { "fields": ( "manual_validation_requested", "is_validated", "validated_by", "validated_at", ) }, ), ( "Statistics", { "fields": ("total_responses", "needs_validation"), "classes": ("collapse",), }, ), ( "Timestamps", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}, ), ) actions = ["mark_as_validated"] def mark_as_validated(self, request, queryset): """Mark selected submissions as validated""" updated = 0 for submission in queryset: if not submission.is_validated: submission.is_validated = True submission.validated_by = request.user submission.validated_at = timezone.now() submission.save() # Also mark all responses as not needing validation submission.responses.update(needs_manual_validation=False) updated += 1 self.message_user(request, f"{updated} submissions marked as validated.") mark_as_validated.short_description = "Mark selected submissions as validated" @admin.register(PuzzleResponse) class PuzzleResponseAdmin(admin.ModelAdmin): list_display = [ "puzzle_name", "submission", "puzzle", "cost", "cycles", "area", "needs_manual_validation", "created_at", ] list_filter = ["needs_manual_validation", "puzzle__collection", "created_at"] search_fields = [ "puzzle_name", "submission__id", "puzzle__title", "cost", "cycles", "area", ] readonly_fields = ["created_at", "updated_at"] inlines = [SubmissionFileInline] fieldsets = ( ("Basic Information", {"fields": ("submission", "puzzle", "puzzle_name")}), ( "OCR Data", { "fields": ( "cost", "cycles", "area", "ocr_confidence_cost", "ocr_confidence_cycles", "ocr_confidence_area", ) }, ), ( "Validation", { "fields": ( "needs_manual_validation", "validated_cost", "validated_cycles", "validated_area", ) }, ), ( "Timestamps", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}, ), ) actions = ["mark_for_validation", "clear_validation_flag"] def mark_for_validation(self, request, queryset): """Mark selected responses as needing validation""" updated = queryset.update(needs_manual_validation=True) self.message_user(request, f"{updated} responses marked for validation.") def clear_validation_flag(self, request, queryset): """Clear validation flag for selected responses""" updated = queryset.update(needs_manual_validation=False) self.message_user( request, f"{updated} responses cleared from validation queue." ) mark_for_validation.short_description = "Mark as needing validation" clear_validation_flag.short_description = "Clear validation flag" @admin.register(SubmissionFile) class SubmissionFileAdmin(admin.ModelAdmin): list_display = [ "original_filename", "response", "file_size_display", "content_type", "ocr_processed", "created_at", ] list_filter = ["content_type", "ocr_processed", "created_at"] search_fields = [ "original_filename", "response__puzzle_name", "response__submission__id", ] readonly_fields = [ "file_size", "content_type", "ocr_processed", "created_at", "updated_at", "file_url", ] fieldsets = ( ( "File Information", { "fields": ( "file", "original_filename", "file_size", "content_type", "file_url", ) }, ), ("OCR Processing", {"fields": ("ocr_processed", "ocr_raw_data", "ocr_error")}), ("Relationships", {"fields": ("response",)}), ( "Timestamps", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}, ), ) def file_size_display(self, obj): """Display file size in human readable format""" if obj.file_size < 1024: return f"{obj.file_size} B" elif obj.file_size < 1024 * 1024: return f"{obj.file_size / 1024:.1f} KB" else: return f"{obj.file_size / (1024 * 1024):.1f} MB" file_size_display.short_description = "File Size"