migrate to opus-magnum app
This commit is contained in:
@@ -0,0 +1,389 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.utils import timezone
|
||||
from opus_magnum.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(
|
||||
'<code style="background: #f8f9fa; padding: 2px 4px; border-radius: 3px;">{}</code>',
|
||||
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",
|
||||
"accepting_submissions",
|
||||
]
|
||||
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", "accepting_submissions")}),
|
||||
)
|
||||
|
||||
|
||||
@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"
|
||||
Reference in New Issue
Block a user