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"])