(feature) Add schedule editing view for hosts
Add a staff-only Django form for creating and updating host schedules using the SQL-backed ScheduleConfig model. Link the form from host detail pages, validate cron expressions with the existing scheduler parser, and preserve scheduler/CLI behavior by writing to the same source of truth. Cover default rendering, schedule creation, updates, and invalid cron handling with view tests.
This commit is contained in:
@@ -67,6 +67,7 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "15 2 * * *")
|
||||
self.assertContains(response, "20260519-021500Z__ABCDEFGH")
|
||||
self.assertContains(response, "Discover snapshots")
|
||||
self.assertContains(response, "Edit schedule")
|
||||
|
||||
def test_host_detail_returns_404_for_unknown_host(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
@@ -152,6 +153,84 @@ class ViewTests(TestCase):
|
||||
self.assertRedirects(response, reverse("host_detail", args=[host.host]))
|
||||
self.assertContains(response, "Retention kind must be scheduled, manual, or all.")
|
||||
|
||||
def test_schedule_form_renders_defaults_for_new_schedule(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
response = self.client.get(reverse("edit_host_schedule", args=[host.host]))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Create Schedule")
|
||||
self.assertContains(response, "15 2 * * *")
|
||||
self.assertContains(response, "Save schedule")
|
||||
|
||||
def test_schedule_form_creates_schedule(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
response = self.client.post(
|
||||
reverse("edit_host_schedule", args=[host.host]),
|
||||
{
|
||||
"cron_expr": "30 3 * * *",
|
||||
"user": "root",
|
||||
"enabled": "on",
|
||||
"prune": "on",
|
||||
"prune_max_delete": "4",
|
||||
"prune_protect_bases": "on",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("host_detail", args=[host.host]))
|
||||
self.assertContains(response, "Schedule saved for web-01.")
|
||||
schedule = ScheduleConfig.objects.get(host=host)
|
||||
self.assertEqual(schedule.cron_expr, "30 3 * * *")
|
||||
self.assertTrue(schedule.enabled)
|
||||
self.assertTrue(schedule.prune)
|
||||
self.assertEqual(schedule.prune_max_delete, 4)
|
||||
self.assertTrue(schedule.prune_protect_bases)
|
||||
|
||||
def test_schedule_form_updates_existing_schedule(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
schedule = ScheduleConfig.objects.create(host=host, cron_expr="15 2 * * *", enabled=True, prune=True)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("edit_host_schedule", args=[host.host]),
|
||||
{
|
||||
"cron_expr": "45 4 * * 1",
|
||||
"user": "backup",
|
||||
"prune_max_delete": "8",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("host_detail", args=[host.host]))
|
||||
schedule.refresh_from_db()
|
||||
self.assertEqual(schedule.cron_expr, "45 4 * * 1")
|
||||
self.assertEqual(schedule.user, "backup")
|
||||
self.assertFalse(schedule.enabled)
|
||||
self.assertFalse(schedule.prune)
|
||||
self.assertEqual(schedule.prune_max_delete, 8)
|
||||
|
||||
def test_schedule_form_rejects_invalid_cron(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
response = self.client.post(
|
||||
reverse("edit_host_schedule", args=[host.host]),
|
||||
{
|
||||
"cron_expr": "bad cron",
|
||||
"user": "root",
|
||||
"enabled": "on",
|
||||
"prune_max_delete": "10",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "cron expression must have exactly 5 fields")
|
||||
self.assertFalse(ScheduleConfig.objects.filter(host=host).exists())
|
||||
|
||||
def _snapshot(self, host: HostConfig, dirname: str) -> SnapshotRecord:
|
||||
started_at = datetime.strptime(dirname.split("__", 1)[0], "%Y%m%d-%H%M%SZ").replace(tzinfo=timezone.utc)
|
||||
return SnapshotRecord.objects.create(
|
||||
|
||||
Reference in New Issue
Block a user