from django.contrib import admin from django.urls import reverse from .models import Submission, SubmissionFile, ImageCrop, Scope from .signals import broadcast_scope_updates class ImageCropInline(admin.TabularInline): model = ImageCrop extra = 1 fields = [ "label", "x1", "y1", "x2", "y2", "ocr_status", "created_at", ] readonly_fields = ["created_at"] class SubmissionFileInline(admin.TabularInline): model = SubmissionFile extra = 0 fields = [ "original_filename", "file_size", "content_type", "ocr_status", "created_at", ] readonly_fields = [ "file_size", "content_type", "created_at", ] def get_queryset(self, request): return super().get_queryset(request) @admin.register(Scope) class ScopeAdmin(admin.ModelAdmin): list_display = [ "name", "event_game", "external_id", "is_active", "assigned_user_count", "synced_at", "created_at", ] list_filter = ["is_active", "event_game__event", "event_game__game"] search_fields = ["name", "external_id", "event_game__game__name"] readonly_fields = ["id", "created_at", "updated_at", "synced_at"] filter_horizontal = ["assigned_users"] autocomplete_fields = ["event_game"] def get_queryset(self, request): return ( super() .get_queryset(request) .select_related("event_game", "event_game__event", "event_game__game") .prefetch_related("assigned_users") ) @admin.display(description="Assigned users") def assigned_user_count(self, obj): return obj.assigned_users.count() def view_on_site(self, obj): return reverse( "submissions.view", kwargs={ "event_path": obj.event_game.event.path, "game_path": obj.event_game.game.path, "scope_id": obj.id, }, ) # Players on a scope edited here get moved like with the AAndT sync (see # broadcast_scope_updates): state before the save is kept on the instance, # compared once the assigned users (m2m) are saved too in save_related. def save_model(self, request, obj, form, change): previous = Scope.objects.filter(pk=obj.pk).first() if change else None obj._state_before_save = ( ( previous.is_active, set(previous.assigned_users.values_list("id", flat=True)), ) if previous else (False, set()) ) super().save_model(request, obj, form, change) def save_related(self, request, form, formsets, change): super().save_related(request, form, formsets, change) scope = form.instance was_active, previous_user_ids = scope._state_before_save user_ids = set(scope.assigned_users.values_list("id", flat=True)) if scope.is_active != was_active or user_ids != previous_user_ids: broadcast_scope_updates({scope: user_ids | previous_user_ids}) def delete_model(self, request, obj): self.delete_queryset(request, Scope.objects.filter(pk=obj.pk)) def delete_queryset(self, request, queryset): changes = { scope: {user.id for user in scope.assigned_users.all()} for scope in queryset.prefetch_related("assigned_users") } for scope in changes: scope.is_active = False super().delete_queryset(request, queryset) broadcast_scope_updates(changes) @admin.register(Submission) class SubmissionAdmin(admin.ModelAdmin): list_display = ["id_short", "scope", "game", "user", "notes_preview", "created_at"] list_filter = [ "scope__event_game__game", "scope__event_game__event", "created_at", "updated_at", ] search_fields = [ "id", "user__email", "notes", "scope__event_game__game__name", "scope__name", ] readonly_fields = ["id", "created_at", "updated_at"] autocomplete_fields = ["scope"] inlines = [SubmissionFileInline] def get_queryset(self, request): return ( super() .get_queryset(request) .select_related("user", "scope", "scope__event_game__game") ) @admin.display(description="Game", ordering="scope__event_game__game") def game(self, obj): return obj.scope.event_game.game @admin.display(description="ID") def id_short(self, obj): return str(obj.id)[:8] @admin.display(description="Notes") def notes_preview(self, obj): if obj.notes: return obj.notes[:50] + "..." if len(obj.notes) > 50 else obj.notes return "-" @admin.register(SubmissionFile) class SubmissionFileAdmin(admin.ModelAdmin): list_display = [ "original_filename", "submission_id", "file_size", "content_type", "ocr_status", "created_at", ] list_filter = ["content_type", "ocr_status", "created_at"] search_fields = ["original_filename", "submission__id"] readonly_fields = ["file_size", "content_type", "created_at", "updated_at"] inlines = [ImageCropInline] def get_queryset(self, request): return super().get_queryset(request).select_related("submission") @admin.display(description="Submission ID") def submission_id(self, obj): return str(obj.submission.id)[:8] @admin.register(ImageCrop) class ImageCropAdmin(admin.ModelAdmin): list_display = [ "label_or_coords", "file_name", "ocr_status", "created_at", ] list_filter = ["ocr_status", "created_at"] search_fields = ["label", "submission_file__original_filename"] readonly_fields = ["created_at", "updated_at"] def get_queryset(self, request): return super().get_queryset(request).select_related("submission_file") @admin.display(description="Crop") def label_or_coords(self, obj): return obj.label or f"({obj.x1}, {obj.y1}, {obj.x2}, {obj.y2})" @admin.display(description="File") def file_name(self, obj): return obj.submission_file.original_filename