208 lines
6.2 KiB
Python
208 lines
6.2 KiB
Python
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
|