From 2d4f077691f9c1531d11c81f46888388e8f11a95 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Tue, 14 Oct 2025 16:26:20 +0200 Subject: [PATCH 1/5] fix: Job detail: check for a valid Task before rendering its link --- scheduler/templates/admin/scheduler/job_detail.html | 2 +- scheduler/templatetags/scheduler_tags.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scheduler/templates/admin/scheduler/job_detail.html b/scheduler/templates/admin/scheduler/job_detail.html index 14faefa..af27f33 100644 --- a/scheduler/templates/admin/scheduler/job_detail.html +++ b/scheduler/templates/admin/scheduler/job_detail.html @@ -14,7 +14,7 @@ {% block content_title %}

Job {{ job.name }} - {% if job.is_scheduled_task %} + {% if job.is_scheduled_task and job|scheduled_task %} Link to scheduled job diff --git a/scheduler/templatetags/scheduler_tags.py b/scheduler/templatetags/scheduler_tags.py index 1ad26f2..35ab9b4 100644 --- a/scheduler/templatetags/scheduler_tags.py +++ b/scheduler/templatetags/scheduler_tags.py @@ -30,10 +30,12 @@ def get_item(dictionary: Dict, key): @register.filter -def scheduled_task(job: JobModel) -> Task: - django_scheduled_task = get_scheduled_task(*job.args) - return django_scheduled_task.get_absolute_url() - +def scheduled_task(job: JobModel) -> Optional[Task]: + try: + django_scheduled_task = get_scheduled_task(*job.args) + return django_scheduled_task.get_absolute_url() + except ValueError: + return None @register.filter def job_result(job: JobModel) -> Optional[str]: From 4a63d150061184352a5ef3438bab186860c0333c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:36:40 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scheduler/templatetags/scheduler_tags.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scheduler/templatetags/scheduler_tags.py b/scheduler/templatetags/scheduler_tags.py index 35ab9b4..dccad48 100644 --- a/scheduler/templatetags/scheduler_tags.py +++ b/scheduler/templatetags/scheduler_tags.py @@ -37,6 +37,7 @@ def scheduled_task(job: JobModel) -> Optional[Task]: except ValueError: return None + @register.filter def job_result(job: JobModel) -> Optional[str]: queue = get_queue(job.queue_name) From 0ec266ae5d53f98ffa88446501bc54a2914ede4e Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Tue, 14 Oct 2025 16:39:48 +0200 Subject: [PATCH 3/5] chore: apply ruff formatting --- scheduler/templatetags/scheduler_tags.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scheduler/templatetags/scheduler_tags.py b/scheduler/templatetags/scheduler_tags.py index 35ab9b4..dccad48 100644 --- a/scheduler/templatetags/scheduler_tags.py +++ b/scheduler/templatetags/scheduler_tags.py @@ -37,6 +37,7 @@ def scheduled_task(job: JobModel) -> Optional[Task]: except ValueError: return None + @register.filter def job_result(job: JobModel) -> Optional[str]: queue = get_queue(job.queue_name) From 56294a5fde0c74468b2fd086efc278ec0cf40f87 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Thu, 16 Oct 2025 11:56:20 +0200 Subject: [PATCH 4/5] chore: test that the job detail doesnt crash --- .../test_views/test_queue_registry_jobs.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/scheduler/tests/test_views/test_queue_registry_jobs.py b/scheduler/tests/test_views/test_queue_registry_jobs.py index dcb1cf0..1137213 100644 --- a/scheduler/tests/test_views/test_queue_registry_jobs.py +++ b/scheduler/tests/test_views/test_queue_registry_jobs.py @@ -6,6 +6,8 @@ from scheduler.helpers.queues import get_queue from scheduler.tests.jobs import test_job from scheduler.tests.test_views.base import BaseTestCase +from scheduler.tests.testtools import task_factory +from scheduler.models import TaskType, Task class QueueRegistryJobsViewTest(BaseTestCase): @@ -90,3 +92,51 @@ def test_started_jobs(self): registry.add(queue.connection, job.name, time.time() + 20) res = self.client.get(reverse("queue_registry_jobs", args=[queue_name, "active"])) self.assertEqual(res.context["jobs"], [job]) + + def test_missing_task_doesnt_crash_job_detail_page(self): + """ + Ensure that when a Task gets deleted and its Job doesn't get cleaned, the + job detail page doesn't raise an exception. + """ + queue_name = "django_tasks_scheduler_test" + + # No jobs in the queue + res = self.client.get(reverse("queue_registry_jobs", args=[queue_name, "scheduled"])) + self.assertEqual(res.status_code, 200) + self.assertEqual(res.context["jobs"], []) + + task = task_factory(TaskType.ONCE, queue="django_tasks_scheduler_test") + + res = self.client.get(reverse("queue_registry_jobs", args=[queue_name, "scheduled"])) + self.assertEqual(res.status_code, 200) + job = res.context["jobs"][0] + self.assertTrue(job) + self.assertTrue(job.is_scheduled_task) + self.assertEqual(job.scheduled_task_id, task.pk) + + # Job detail page works + url = reverse("job_details", args=[job.name]) + res = self.client.get(url) + self.assertEqual(200, res.status_code) + self.assertIn("job", res.context) + self.assertEqual(res.context["job"], job) + self.assertNotContains(res, "ValueError('Invalid task type OnceTaskType')") + self.assertContains(res, "Link to scheduled job") + + # Delete all tasks in bulk, this doesn't trigger the signal + # that would delete the corresponding scheduled jobs. + Task.objects.all().delete() + + # The job lingers around :( + res = self.client.get(reverse("queue_registry_jobs", args=[queue_name, "scheduled"])) + self.assertEqual(res.status_code, 200) + self.assertTrue(job in res.context["jobs"]) + + # Job detail does't raise a 500 + url = reverse("job_details", args=[job.name]) + res = self.client.get(url) + self.assertEqual(200, res.status_code) + self.assertIn("job", res.context) + self.assertEqual(res.context["job"], job) + self.assertContains(res, "ValueError('Invalid task type OnceTaskType')") + self.assertNotContains(res, "Link to scheduled job") From ba5ac2b97c0ed4befa9e254a4529c62fb5c5a62c Mon Sep 17 00:00:00 2001 From: Daniel M Date: Thu, 16 Oct 2025 10:45:15 -0700 Subject: [PATCH 5/5] fix typo --- scheduler/tests/test_views/test_queue_registry_jobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scheduler/tests/test_views/test_queue_registry_jobs.py b/scheduler/tests/test_views/test_queue_registry_jobs.py index 1137213..64be4b0 100644 --- a/scheduler/tests/test_views/test_queue_registry_jobs.py +++ b/scheduler/tests/test_views/test_queue_registry_jobs.py @@ -132,7 +132,7 @@ def test_missing_task_doesnt_crash_job_detail_page(self): self.assertEqual(res.status_code, 200) self.assertTrue(job in res.context["jobs"]) - # Job detail does't raise a 500 + # Job detail doesn't raise a 500 url = reverse("job_details", args=[job.name]) res = self.client.get(url) self.assertEqual(200, res.status_code)