diff --git a/backend/apps/admin_data_tools/admin.py b/backend/apps/admin_data_tools/admin.py
index 1420b05f..4780e8ff 100644
--- a/backend/apps/admin_data_tools/admin.py
+++ b/backend/apps/admin_data_tools/admin.py
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
+from django.conf import settings
from django.contrib import admin
+from django.urls import reverse
from django.utils import timezone
+from django.utils.html import format_html
from ._prefect3_client import Prefect3Client
from .models import DisabledFlowSchedule
@@ -9,16 +12,47 @@
@admin.register(DisabledFlowSchedule)
class DisabledFlowScheduleAdmin(admin.ModelAdmin):
list_display = [
- "flow_name",
+ "flow_name_display",
"deployment_id",
"disabled_at",
"is_schedule_active",
"reactivated_at",
]
+ # flow_name_display already renders its own to the change page. Must be
+ # None, not [] — Django treats [] as "unset" and falls back to
+ # auto-linking the first column, nesting an around it either way.
+ list_display_links = None
list_filter = ["is_schedule_active"]
search_fields = ["flow_name"]
- readonly_fields = ["flow_name", "deployment_id", "disabled_at", "reactivated_at"]
- fields = ["flow_name", "deployment_id", "disabled_at", "is_schedule_active", "reactivated_at"]
+ readonly_fields = ["flow_name_display", "deployment_id", "disabled_at", "reactivated_at"]
+ fields = [
+ "flow_name_display",
+ "deployment_id",
+ "disabled_at",
+ "is_schedule_active",
+ "reactivated_at",
+ ]
+
+ def flow_name_display(self, obj):
+ """Flow name linking to the Django change page, plus a button that opens
+ the deployment directly in Prefect 3 — built by hand (not via
+ list_display_links) so the Prefect link isn't nested inside another
+ , which Django's automatic column-link wrapping would do."""
+ change_url = reverse(
+ f"admin:{obj._meta.app_label}_{obj._meta.model_name}_change", args=[obj.pk]
+ )
+ prefect_ui_url = settings.PREFECT3_API_URL.removesuffix("/api")
+ deployment_url = f"{prefect_ui_url}/v2/deployments/deployment/{obj.deployment_id}?tab=Runs"
+ return format_html(
+ '{} Prefect ↗',
+ change_url,
+ obj.flow_name,
+ deployment_url,
+ )
+
+ flow_name_display.short_description = "Flow Name"
+ flow_name_display.admin_order_field = "flow_name"
def save_model(self, request, obj, form, change):
if change and "is_schedule_active" in form.changed_data:
diff --git a/backend/apps/admin_data_tools/migrations/0004_alter_disabledflowschedule_options.py b/backend/apps/admin_data_tools/migrations/0004_alter_disabledflowschedule_options.py
new file mode 100644
index 00000000..e67b9df4
--- /dev/null
+++ b/backend/apps/admin_data_tools/migrations/0004_alter_disabledflowschedule_options.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 4.2.10 on 2026-08-22 09:01
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("admin_data_tools", "0003_alter_disabledflowschedule_disabled_at"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="disabledflowschedule",
+ options={
+ "ordering": ["-disabled_at"],
+ "verbose_name": "Flow Schedule",
+ "verbose_name_plural": "Flow Schedules",
+ },
+ ),
+ ]
diff --git a/backend/apps/admin_data_tools/models.py b/backend/apps/admin_data_tools/models.py
index 7e04ccff..4f86df6e 100644
--- a/backend/apps/admin_data_tools/models.py
+++ b/backend/apps/admin_data_tools/models.py
@@ -12,8 +12,8 @@ class DisabledFlowSchedule(models.Model):
class Meta:
ordering = ["-disabled_at"]
- verbose_name = "Disabled Flow Schedule"
- verbose_name_plural = "Disabled Flow Schedules"
+ verbose_name = "Flow Schedule"
+ verbose_name_plural = "Flow Schedules"
def __str__(self):
return self.flow_name