From fbe93d9aeee14a289102fb041f66c582dfabeb7b Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 22 Oct 2026 11:02:54 +0300 Subject: [PATCH] fix(templates): each delete page says what deleting its object does The shared delete page asked only whether to delete the object. Each view now gives its consequence: a deleted compute's VMs keep running but the panel forgets them and their owners, a deleted user's VMs stay, a group's members keep their accounts, a VM's owner loses only the access, a removed VM's record no longer brings its owners back, an SSH key stays on the VMs that have it, a flavor only goes as a size template. Co-Authored-By: Claude Opus 5.5 (1M context) --- accounts/views.py | 4 ++- admin/views.py | 8 +++-- computes/views.py | 5 +++- instances/views.py | 2 +- templates/common/confirm_delete.html | 2 +- webvirtcloud/tests/test_delete_pages.py | 40 +++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 webvirtcloud/tests/test_delete_pages.py diff --git a/accounts/views.py b/accounts/views.py index fcdccd36..c2340a1b 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -93,6 +93,7 @@ def ssh_key_delete(request, pk): { "object": ssh_key, "title": _("Delete SSH key"), + "consequence": _("VMs that already have it keep it; VMs created from now on do not get it."), }, ) @@ -200,7 +201,8 @@ def user_instance_delete(request, pk): return render( request, "common/confirm_delete.html", - {"object": user_instance}, + {"object": user_instance, + "consequence": _("The user loses access to this VM; the VM itself is not changed.")}, ) diff --git a/admin/views.py b/admin/views.py index 3a6c462f..44791e29 100644 --- a/admin/views.py +++ b/admin/views.py @@ -76,7 +76,7 @@ def group_delete(request, pk): return render( request, "common/confirm_delete.html", - {"object": group}, + {"object": group, "consequence": _("Its members stay but lose the permissions it gave them.")}, ) @@ -182,7 +182,8 @@ def user_delete(request, pk): return render( request, "common/confirm_delete.html", - {"object": user}, + {"object": user, + "consequence": _("Their access to VMs, SSH keys and settings go; the VMs themselves stay.")}, ) @@ -237,7 +238,8 @@ def tombstone_delete(request, pk): return render( request, "common/confirm_delete.html", - {"object": tombstone}, + {"object": tombstone, + "consequence": _("Its owners no longer get the VM back if its UUID shows up again.")}, ) diff --git a/computes/views.py b/computes/views.py index d614eefb..4a3a1427 100644 --- a/computes/views.py +++ b/computes/views.py @@ -6,6 +6,7 @@ from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from instances.models import Instance from libvirt import libvirtError from operations import claims @@ -127,7 +128,9 @@ def compute_delete(request, compute_id): return render( request, "common/confirm_delete.html", - {"object": compute}, + {"object": compute, + "consequence": _("Its VMs keep running on the host, but the panel forgets them and who owns them; " + "added again, the host's VMs come back without owners.")}, ) diff --git a/instances/views.py b/instances/views.py index b5be7e12..15dd59ae 100755 --- a/instances/views.py +++ b/instances/views.py @@ -2202,5 +2202,5 @@ def flavor_delete(request, pk): return render( request, "common/confirm_delete.html", - {"object": flavor}, + {"object": flavor, "consequence": _("Only the size template goes; VMs made from it keep their size.")}, ) diff --git a/templates/common/confirm_delete.html b/templates/common/confirm_delete.html index 9e701dde..60a43907 100644 --- a/templates/common/confirm_delete.html +++ b/templates/common/confirm_delete.html @@ -13,7 +13,7 @@
{% csrf_token %}
- {%trans "Are you sure you want to delete" %} "{{ object }}"? + {% blocktrans %}Delete "{{ object }}"?{% endblocktrans %} {{ consequence }}
diff --git a/webvirtcloud/tests/test_delete_pages.py b/webvirtcloud/tests/test_delete_pages.py new file mode 100644 index 00000000..07c0ee65 --- /dev/null +++ b/webvirtcloud/tests/test_delete_pages.py @@ -0,0 +1,40 @@ +"""Each delete page says what deleting its object does, besides naming it.""" + +from accounts.models import UserInstance, UserSSHKey +from computes.models import Compute +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.test import TestCase +from django.urls import reverse +from instances.models import Flavor, Instance, InstanceTombstone +from lxml import html + +KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGJ5dGVzLWZvci10aGUtZGVsZXRlLXBhZ2UtdGVzdA== page@example.com" + + +class DeletePageTests(TestCase): + def test_every_delete_page_says_what_deleting_does(self): + admin = get_user_model().objects.create_superuser("del-admin", "da@example.com", "pw") + user = get_user_model().objects.create_user("del-user", password="pw") + compute = Compute.objects.create(name="del-host", hostname="192.0.2.9", login="u", password="p", type=1) + vm = Instance.objects.create(compute=compute, name="del-vm", uuid="d0000000-0000-4000-8000-000000000001") + pages = { + reverse("compute_delete", args=[compute.id]): "the panel forgets them and who owns them", + reverse("admin:user_delete", args=[user.id]): "the VMs themselves stay", + reverse("admin:group_delete", args=[Group.objects.create(name="del-group").id]): "lose the permissions", + reverse("admin:tombstone_delete", args=[InstanceTombstone.objects.create(uuid=vm.uuid, name="gone").id]): + "no longer get the VM back", + reverse("accounts:user_instance_delete", args=[UserInstance.objects.create(user=user, instance=vm).id]): + "the VM itself is not changed", + reverse("instances:flavor_delete", args=[Flavor.objects.create(label="del-flavor", memory=512, vcpu=1, disk=1).id]): + "VMs made from it keep their size", + } + key = UserSSHKey.objects.create(user=admin, keyname="del-key", keypublic=KEY) + pages[reverse("accounts:ssh_key_delete", args=[key.id])] = "VMs that already have it keep it" + self.client.force_login(admin) + for url, consequence in pages.items(): + with self.subTest(url=url): + alert = html.fromstring(self.client.get(url).content).xpath("//div[contains(@class, 'alert-warning')]") + self.assertEqual(len(alert), 1) + self.assertIn(consequence, alert[0].text_content()) + self.assertNotIn("Are you sure", alert[0].text_content())