From c37031e9350baaf71d16951c3d9ae916288717d9 Mon Sep 17 00:00:00 2001 From: Jon Froehlich Date: Mon, 22 Jun 2026 12:37:58 -0700 Subject: [PATCH] feat(admin): add edit-action link to the media-integrity health check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Media / file integrity" data-health check listed artifacts whose pdf_file/raw_file/thumbnail is missing on disk, but — unlike the other checks (e.g. unlinked-artifacts) — gave no way to act on a row. Add a row_link so each missing-file row gets an "Open →" button to the artifact's admin edit page, where the editor can re-upload the file or clear the dead reference. Orphan-file rows have no DB object to open (they're handled by delete_unused_files), so they intentionally get no link. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../data_health/checks/media_integrity.py | 15 +++++++++++ website/tests/test_data_health.py | 27 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/website/admin/data_health/checks/media_integrity.py b/website/admin/data_health/checks/media_integrity.py index 2920a42b..656e5cca 100644 --- a/website/admin/data_health/checks/media_integrity.py +++ b/website/admin/data_health/checks/media_integrity.py @@ -16,6 +16,7 @@ import os from django.conf import settings +from django.urls import reverse from website.admin.data_health.registry import HealthCheck, register_check from website.models import Poster, Publication, Talk @@ -53,6 +54,20 @@ def get_rows(self): rows.extend(self._orphan_files(model)) return rows + def row_link(self, row): + """Deep-link a ``missing-file`` row to its artifact's admin edit page so + the editor can re-upload the file or clear the dead reference right + there (mirrors the action buttons on the other checks). + + ``orphan-file`` rows have no DB object to open — they're files on disk + that ``delete_unused_files`` would remove — so they get no link. + """ + if row.get('status') != 'missing-file' or not row.get('id'): + return None + url = reverse(f"admin:website_{row['type'].lower()}_change", + args=[row['id']]) + return ('Open →', url) + def _missing_files(self, model): """DB rows whose file field is set but the file is gone from disk.""" rows = [] diff --git a/website/tests/test_data_health.py b/website/tests/test_data_health.py index fc1df993..f082b58a 100644 --- a/website/tests/test_data_health.py +++ b/website/tests/test_data_health.py @@ -303,6 +303,33 @@ def test_flags_missing_file(self): ] self.assertTrue(hits) + def test_missing_file_row_links_to_admin_edit(self): + """A missing-file row gets an 'Open →' action to the artifact's admin + edit page; orphan-file rows (no DB object) get no link.""" + pub = self.make_publication(title="Vanishing Paper") + path = pub.pdf_file.path + if os.path.exists(path): + os.remove(path) + + check = get_check("media-integrity") + missing = next( + r + for r in check.get_rows() + if r["type"] == "Publication" + and r["id"] == pub.pk + and r["status"] == "missing-file" + ) + label, url = check.row_link(missing) + self.assertEqual(label, "Open →") + self.assertEqual( + url, reverse("admin:website_publication_change", args=[pub.pk]) + ) + + # Orphan-file rows carry no id and must not produce a link. + self.assertIsNone( + check.row_link({"type": "Publication", "id": "", "status": "orphan-file"}) + ) + class DataHealthReadOnlyTests(DatabaseTestCase): def test_get_rows_does_not_mutate_db(self):