(feature) Add Django retention apply flow
Expose retention apply from the host retention plan page so planned snapshot deletions can be executed from the Django UI. The form requires explicit host confirmation, carries through the selected retention kind and base-protection setting, and uses max_delete as a deletion guard. The view delegates to the SQL retention apply service and reports predictable pobsync errors back through Django messages instead of surfacing a server error. Add view coverage for confirmed deletion, invalid confirmation, and POST-only enforcement.
This commit is contained in:
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from pobsync.util import write_yaml_atomic
|
||||
@@ -458,6 +458,75 @@ 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_retention_apply_deletes_planned_snapshot_after_confirmation(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
with TemporaryDirectory() as tmp:
|
||||
home = Path(tmp) / "home"
|
||||
host = HostConfig.objects.create(
|
||||
host="web-01",
|
||||
address="web-01.example.test",
|
||||
retention_daily=0,
|
||||
retention_weekly=0,
|
||||
retention_monthly=0,
|
||||
retention_yearly=0,
|
||||
)
|
||||
old_dir = Path(tmp) / "backups" / host.host / "scheduled" / "20260518-021500Z__OLDSNAP"
|
||||
new_dir = Path(tmp) / "backups" / host.host / "scheduled" / "20260519-021500Z__NEWSNAP"
|
||||
old_dir.mkdir(parents=True)
|
||||
new_dir.mkdir(parents=True)
|
||||
old_snapshot = self._snapshot(host, old_dir.name)
|
||||
old_snapshot.path = str(old_dir)
|
||||
old_snapshot.save(update_fields=["path"])
|
||||
new_snapshot = self._snapshot(host, new_dir.name)
|
||||
new_snapshot.path = str(new_dir)
|
||||
new_snapshot.save(update_fields=["path"])
|
||||
|
||||
with override_settings(POBSYNC_HOME=str(home)):
|
||||
response = self.client.post(
|
||||
reverse("apply_host_retention", args=[host.host]),
|
||||
{
|
||||
"kind": "scheduled",
|
||||
"max_delete": "1",
|
||||
"confirm_host": host.host,
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertFalse(old_dir.exists())
|
||||
self.assertTrue(new_dir.exists())
|
||||
|
||||
self.assertRedirects(response, f"{reverse('host_retention_plan', args=[host.host])}?kind=scheduled")
|
||||
self.assertContains(response, "Retention deleted 1 snapshot(s) for web-01.")
|
||||
self.assertFalse(SnapshotRecord.objects.filter(pk=old_snapshot.pk).exists())
|
||||
self.assertTrue(SnapshotRecord.objects.filter(pk=new_snapshot.pk).exists())
|
||||
|
||||
def test_retention_apply_rejects_bad_confirmation(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
self._snapshot(host, "20260518-021500Z__OLDSNAP")
|
||||
|
||||
response = self.client.post(
|
||||
reverse("apply_host_retention", args=[host.host]),
|
||||
{
|
||||
"kind": "scheduled",
|
||||
"max_delete": "1",
|
||||
"confirm_host": "wrong",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("host_retention_plan", args=[host.host]))
|
||||
self.assertContains(response, "Retention apply confirmation is invalid.")
|
||||
self.assertEqual(SnapshotRecord.objects.count(), 1)
|
||||
|
||||
def test_retention_apply_requires_post(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("apply_host_retention", args=[host.host]))
|
||||
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user