diff --git a/atlas/admin.py b/atlas/admin.py index 2e8fc42a..ab1dabb1 100755 --- a/atlas/admin.py +++ b/atlas/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from django.contrib import messages from .models import ( Case, CaseDetail, @@ -29,12 +30,19 @@ from django.forms import ModelForm from reversion.admin import VersionAdmin from django.forms.widgets import RadioSelect +from django.utils.html import format_html +from django.urls import reverse from tinymce.widgets import TinyMCE # from generic.models import Examination, Sign, Site, Condition from generic.models import Examination +try: + from django_tasks_db.models import DBTaskResult +except ImportError: + DBTaskResult = None + # Register your models here. # admin.site.register(Examination) # admin.site.register(Site) @@ -152,3 +160,185 @@ class APITokenAdmin(admin.ModelAdmin): admin.site.register(APIToken, APITokenAdmin) + + +if DBTaskResult is not None: + try: + # Ensure package admin registration has run before we customize it. + import django_tasks_db.admin # noqa: F401 + except Exception: + pass + + def traceback_link(self, obj): + if not obj.traceback: + return "-" + url = reverse( + f"admin:{obj._meta.app_label}_{obj._meta.model_name}_change", + args=[obj.pk], + ) + return format_html('View full traceback', url) + + traceback_link.short_description = "Traceback" + + def traceback_pretty(self, obj): + if not obj.traceback: + return "" + return format_html( + '
{}
', + obj.traceback, + ) + + traceback_pretty.short_description = "Traceback" + + @admin.action(description="Reset FAILED tasks to READY") + def reset_failed_to_ready(self, request, queryset): + failed_qs = queryset.filter(status="FAILED") + updated = failed_qs.update( + status="READY", + started_at=None, + finished_at=None, + worker_ids="", + exception_class_path="", + traceback="", + return_value=None, + ) + self.message_user( + request, + f"Reset {updated} failed task(s) to READY.", + level=messages.SUCCESS, + ) + + @admin.action(description="Reset selected tasks to READY (except RUNNING)") + def reset_selected_to_ready(self, request, queryset): + blocked = queryset.filter(status="RUNNING").count() + reset_qs = queryset.exclude(status="RUNNING") + updated = reset_qs.update( + status="READY", + started_at=None, + finished_at=None, + worker_ids="", + exception_class_path="", + traceback="", + return_value=None, + ) + msg = f"Reset {updated} task(s) to READY." + if blocked: + msg += f" Skipped {blocked} running task(s)." + self.message_user(request, msg, level=messages.SUCCESS) + + @admin.action(description="Delete selected COMPLETED tasks") + def delete_selected_completed(self, request, queryset): + completed_qs = queryset.filter(status="SUCCESSFUL") + deleted, _ = completed_qs.delete() + self.message_user( + request, + f"Deleted {deleted} completed task record(s).", + level=messages.SUCCESS, + ) + + @admin.action(description="Delete selected FAILED tasks") + def delete_selected_failed(self, request, queryset): + failed_qs = queryset.filter(status="FAILED") + deleted, _ = failed_qs.delete() + self.message_user( + request, + f"Deleted {deleted} failed task record(s).", + level=messages.SUCCESS, + ) + + def has_add_permission(self, request): + return False + + task_admin = admin.site._registry.get(DBTaskResult) + if task_admin is not None: + admin_cls = task_admin.__class__ + admin_cls.list_display = ( + "id", + "task_path", + "status", + "queue_name", + "priority", + "enqueued_at", + "started_at", + "finished_at", + "worker_ids", + "exception_class_path", + "traceback_link", + ) + admin_cls.list_filter = ("status", "queue_name", "task_path", "backend_name") + admin_cls.search_fields = ( + "id", + "task_path", + "queue_name", + "worker_ids", + "exception_class_path", + "traceback", + ) + admin_cls.ordering = ("-enqueued_at",) + admin_cls.actions = ( + "reset_failed_to_ready", + "reset_selected_to_ready", + "delete_selected_completed", + "delete_selected_failed", + ) + admin_cls.readonly_fields = ( + "id", + "status", + "enqueued_at", + "started_at", + "finished_at", + "args_kwargs", + "priority", + "task_path", + "worker_ids", + "queue_name", + "backend_name", + "run_after", + "return_value", + "exception_class_path", + "traceback_pretty", + ) + admin_cls.fieldsets = ( + ( + "Task", + { + "fields": ( + "id", + "task_path", + "status", + "queue_name", + "priority", + "backend_name", + ) + }, + ), + ( + "Timing", + { + "fields": ( + "enqueued_at", + "run_after", + "started_at", + "finished_at", + "worker_ids", + ) + }, + ), + ( + "Payload", + {"fields": ("args_kwargs", "return_value")}, + ), + ( + "Error", + { + "fields": ("exception_class_path", "traceback_pretty"), + }, + ), + ) + admin_cls.traceback_link = traceback_link + admin_cls.traceback_pretty = traceback_pretty + admin_cls.reset_failed_to_ready = reset_failed_to_ready + admin_cls.reset_selected_to_ready = reset_selected_to_ready + admin_cls.delete_selected_completed = delete_selected_completed + admin_cls.delete_selected_failed = delete_selected_failed + admin_cls.has_add_permission = has_add_permission diff --git a/atlas/tasks.py b/atlas/tasks.py index 8e355f8e..afe62320 100644 --- a/atlas/tasks.py +++ b/atlas/tasks.py @@ -343,7 +343,7 @@ def series_reconstruct_task( ds_new.PixelData = arr2d.tobytes() ds_new.PixelSpacing = [float(cfg["pixel_spacing_out"][0]), float(cfg["pixel_spacing_out"][1])] ds_new.ImageOrientationPatient = cfg["image_orientation_out"] - ds_new.ImagePositionPatient = cfg["position_for_index"](idx) + ds_new.ImagePositionPatient = cfg["position_fn"](idx) ds_new.SliceThickness = float(slab_thickness) ds_new.SpacingBetweenSlices = float(cfg["spacing_between_slices_out"])