From 6c2704f97dbb71776b787247ad75595dbec3a908 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 26 Oct 2026 11:23:44 +0300 Subject: [PATCH] fix(admin): Removed VMs shows the host a VM left and confirms a delete A tombstone kept the VM's name and UUID but not its compute, so the page could not say where the VM had been. It now stores the compute's name (as text: the compute may be the one deleted) and the list shows it; records made before this have no host. Deleting a record says so, and the admin tables use scope="col" instead of the invalid span="col". Closes #217 --- admin/templates/admin/group_list.html | 4 ++-- admin/templates/admin/tombstone_list.html | 14 ++++++++------ admin/templates/admin/user_list.html | 16 ++++++++-------- admin/tests/test_tombstones.py | 6 +++++- admin/views.py | 1 + computes/tests/test_ownership.py | 3 ++- computes/utils.py | 3 ++- .../migrations/0014_instancetombstone_host.py | 18 ++++++++++++++++++ instances/models.py | 1 + 9 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 instances/migrations/0014_instancetombstone_host.py diff --git a/admin/templates/admin/group_list.html b/admin/templates/admin/group_list.html index 7d56d1ab..43b52320 100644 --- a/admin/templates/admin/group_list.html +++ b/admin/templates/admin/group_list.html @@ -26,8 +26,8 @@ - - + + diff --git a/admin/templates/admin/tombstone_list.html b/admin/templates/admin/tombstone_list.html index 48328576..f5ba1d2b 100644 --- a/admin/templates/admin/tombstone_list.html +++ b/admin/templates/admin/tombstone_list.html @@ -24,12 +24,13 @@
{% trans "Group Name" %}{% trans "Actions" %}{% trans "Group Name" %}{% trans "Actions" %}
- - - - - - + + + + + + + @@ -40,6 +41,7 @@ {% if tombstone.is_template %}{% trans "Template" %}{% endif %} + diff --git a/admin/templates/admin/user_list.html b/admin/templates/admin/user_list.html index c1ca7afb..59c4f171 100644 --- a/admin/templates/admin/user_list.html +++ b/admin/templates/admin/user_list.html @@ -29,14 +29,14 @@
{% trans "Name" %}{% trans "UUID" %}{% trans "Owners" %}{% trans "Removed" %}{% trans "Expires" %}{% trans "Actions" %}{% trans "Name" %}{% trans "UUID" %}{% trans "Host" %}{% trans "Owners" %}{% trans "Removed" %}{% trans "Expires" %}{% trans "Actions" %}
{{ tombstone.uuid }}{{ tombstone.host }} {{ tombstone.owner_names|join:", " }} {{ tombstone.removed|date:"Y-m-d H:i" }} {{ tombstone.expires|date:"Y-m-d H:i" }}
- - - - - - - - + + + + + + + + diff --git a/admin/tests/test_tombstones.py b/admin/tests/test_tombstones.py index 23d49ad1..f1a1aef5 100644 --- a/admin/tests/test_tombstones.py +++ b/admin/tests/test_tombstones.py @@ -19,6 +19,7 @@ class TombstoneTests(TestCase): self.tombstone = InstanceTombstone.objects.create( uuid=UUID, name="ts-vm", + host="ts-host", owners=[ {"user": self.owner.id, "is_change": True, "is_delete": False, "is_vnc": True}, {"user": 999999, "is_change": False, "is_delete": False, "is_vnc": False}, @@ -37,6 +38,8 @@ class TombstoneTests(TestCase): self.assertContains(response, "ts-vm") self.assertContains(response, UUID) self.assertContains(response, "ts-owner") + self.assertContains(response, "", html=True) + self.assertContains(response, '', html=True) def test_only_superusers_see_or_remove_them(self): self.client.force_login(self.owner) @@ -51,6 +54,7 @@ class TombstoneTests(TestCase): self.assertTrue(InstanceTombstone.objects.exists()) def test_a_post_removes_it(self): - response = self.client.post(reverse("admin:tombstone_delete", args=[self.tombstone.pk])) + response = self.client.post(reverse("admin:tombstone_delete", args=[self.tombstone.pk]), follow=True) self.assertRedirects(response, reverse("admin:tombstone_list")) + self.assertContains(response, "Removed VM ts-vm deleted") self.assertFalse(InstanceTombstone.objects.exists()) diff --git a/admin/views.py b/admin/views.py index bc244100..51e8ef41 100644 --- a/admin/views.py +++ b/admin/views.py @@ -338,6 +338,7 @@ def tombstone_delete(request, pk): if request.method == "POST": with audit(request, None, f"Removed VM {tombstone.name} ({tombstone.uuid})", "delete"): tombstone.delete() + messages.success(request, _("Removed VM %(name)s deleted") % {"name": tombstone.name}) return redirect("admin:tombstone_list") return render( diff --git a/computes/tests/test_ownership.py b/computes/tests/test_ownership.py index 849c0f8a..78c5bc3d 100644 --- a/computes/tests/test_ownership.py +++ b/computes/tests/test_ownership.py @@ -142,7 +142,8 @@ class OwnershipTestCase(TestCase): def test_a_host_added_again_gets_its_vms_back_with_their_owners(self): self.owned_vm(self.host_a) self.delete(self.host_a) - self.assertEqual(InstanceTombstone.objects.get().owners[0]["user"], self.owner.id) + tombstone = InstanceTombstone.objects.get() + self.assertEqual((tombstone.owners[0]["user"], tombstone.host), (self.owner.id, "host-a")) self.assertEqual(self.rows(), [ ("cd-admin", "-", "Login"), ("-", "host-a", f"VM vm ({UUID}): owners kept on the Removed VMs page"), diff --git a/computes/utils.py b/computes/utils.py index 7c3db362..5938018e 100644 --- a/computes/utils.py +++ b/computes/utils.py @@ -100,7 +100,8 @@ def _bury(inst): for ui in UserInstance.objects.filter(instance=inst) ] if owners or inst.is_template: - InstanceTombstone.objects.create(uuid=inst.uuid, name=inst.name, is_template=inst.is_template, owners=owners) + InstanceTombstone.objects.create(uuid=inst.uuid, name=inst.name, host=inst.compute.name, + is_template=inst.is_template, owners=owners) _log(inst, "owners kept on the Removed VMs page") else: _log(inst, "left the panel (no owners)") diff --git a/instances/migrations/0014_instancetombstone_host.py b/instances/migrations/0014_instancetombstone_host.py new file mode 100644 index 00000000..3347ac01 --- /dev/null +++ b/instances/migrations/0014_instancetombstone_host.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.18 on 2026-10-26 08:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('instances', '0013_instancetombstone'), + ] + + operations = [ + migrations.AddField( + model_name='instancetombstone', + name='host', + field=models.CharField(blank=True, max_length=64), + ), + ] diff --git a/instances/models.py b/instances/models.py index a025893e..02c5083c 100644 --- a/instances/models.py +++ b/instances/models.py @@ -398,6 +398,7 @@ class InstanceTombstone(models.Model): uuid = models.CharField(max_length=36, db_index=True) name = models.CharField(max_length=120) + host = models.CharField(max_length=64, blank=True) # the compute's name: the compute may be deleted is_template = models.BooleanField(default=False) owners = models.JSONField(default=list) # [{"user": id, "is_change": .., "is_delete": .., "is_vnc": ..}] removed = models.DateTimeField(auto_now_add=True)
{% trans "Username" %}{% trans "Status" %}{% trans "Last Login" %}{% trans "Template editor" %}{% trans "Superuser" %}{% trans "Can Clone" %}{% trans "API tokens" %}{% trans "Actions" %}{% trans "Username" %}{% trans "Status" %}{% trans "Last Login" %}{% trans "Template editor" %}{% trans "Superuser" %}{% trans "Can Clone" %}{% trans "API tokens" %}{% trans "Actions" %}
ts-hostHost