From b89f280076234db39ca47a775e83163faf07c3d7 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 26 Oct 2026 15:50:54 +0300 Subject: [PATCH] fix(instances): VM list columns, hosts for viewers, flavor units - On a narrow screen the grouped list hid the User, VCPU and Memory headings but not the VMs' cells, so values sat under the wrong headings; the cells now hide with their headings. Memory says MiB. - A user with the view_instances permission sees each VM's host in the list, as the host alerts already name them; the host page link stays a superuser's. - A flavor's memory and disk are labelled MiB and GiB, and each value is at least 1, so "4" is no longer saved as a 4 MiB preset by mistake. --- instances/forms.py | 5 +++++ instances/templates/allinstances_index_grouped.html | 8 ++++---- instances/templates/allinstances_index_nongrouped.html | 9 +++++---- instances/tests/test_create_form_input.py | 9 +++++++++ instances/tests/test_index_host_alerts.py | 7 +++++++ instances/tests/test_list_rpc.py | 2 +- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/instances/forms.py b/instances/forms.py index 4b517ce5..8de34f97 100644 --- a/instances/forms.py +++ b/instances/forms.py @@ -10,6 +10,11 @@ from .utils import nic_macs class FlavorForm(forms.ModelForm): + # the units the create form fills in from a flavor + memory = forms.IntegerField(label=_("Memory (MiB)"), min_value=1) + vcpu = forms.IntegerField(label=_("vCPUs"), min_value=1) + disk = forms.IntegerField(label=_("Disk (GiB)"), min_value=1) + class Meta: model = Flavor fields = "__all__" diff --git a/instances/templates/allinstances_index_grouped.html b/instances/templates/allinstances_index_grouped.html index e4ecc879..390f6176 100644 --- a/instances/templates/allinstances_index_grouped.html +++ b/instances/templates/allinstances_index_grouped.html @@ -36,7 +36,7 @@ {% endif %} {% if app_settings.VM_DRBD_STATUS == 'True' %} - + {% endif %} {% if compute.status is True %} {{ compute.cpu_count }} @@ -62,7 +62,7 @@
{% if instance.info %}

{{ instance.title }}

{% endif %} - + {% if instance.userinstance_set.all.count > 0 %} {{ instance.userinstance_set.all.0.user }} @@ -80,8 +80,8 @@ {% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}{% else %}{% endif %}{{ instance.drbd }} {% endif %} - {{ instance.info.3 }} - {% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %} + {{ instance.info.3 }} + {% if instance.info %}{% widthratio instance.info.2 1024 1 %} MiB{% endif %} {% include 'instance_actions.html' %} diff --git a/instances/templates/allinstances_index_nongrouped.html b/instances/templates/allinstances_index_nongrouped.html index d97d4860..a8c93436 100644 --- a/instances/templates/allinstances_index_nongrouped.html +++ b/instances/templates/allinstances_index_nongrouped.html @@ -4,7 +4,7 @@ {% trans 'Name' %}
{% trans 'Description' %} - {% if request.user.is_superuser %} + {% if show_hosts %} {% trans 'Host' %}
{% trans 'User' %} {% endif %} {% trans 'Status' %} @@ -26,9 +26,10 @@
{% if instance.info %}

{{ instance.title }}

{% endif %} - {% if request.user.is_superuser %} + {% if show_hosts %} - {{ instance.compute.name }}
+ {# the host pages are a superuser's #} + {% if request.user.is_superuser %}{{ instance.compute.name }}{% else %}{{ instance.compute.name }}{% endif %}
{% if instance.userinstance_set.all.count > 0 %} {{ instance.userinstance_set.all.0.user }} @@ -48,7 +49,7 @@ {% endif %} {{ instance.info.3 }} - {% if instance.info %}{% widthratio instance.info.2 1024 1 %} MB{% endif %} + {% if instance.info %}{% widthratio instance.info.2 1024 1 %} MiB{% endif %} {% include 'instance_actions.html' %} diff --git a/instances/tests/test_create_form_input.py b/instances/tests/test_create_form_input.py index 841ddd68..231d39d1 100644 --- a/instances/tests/test_create_form_input.py +++ b/instances/tests/test_create_form_input.py @@ -307,3 +307,12 @@ class XmlRefusalTests(TestCase): def test_a_definition_libvirt_refuses(self): self.assertIn("libvirt", self.post("new1024")) + + +class FlavorFormTests(TestCase): + def test_a_flavor_is_at_least_one_of_each_in_named_units(self): + from instances.forms import FlavorForm + + form = FlavorForm({"label": "tiny", "memory": "0", "vcpu": "-1", "disk": "0"}) + self.assertEqual(set(form.errors), {"memory", "vcpu", "disk"}) + self.assertEqual((form["memory"].label, form["disk"].label), ("Memory (MiB)", "Disk (GiB)")) diff --git a/instances/tests/test_index_host_alerts.py b/instances/tests/test_index_host_alerts.py index 50ba814e..a81bf425 100644 --- a/instances/tests/test_index_host_alerts.py +++ b/instances/tests/test_index_host_alerts.py @@ -61,3 +61,10 @@ class IndexHostAlertTests(TestCase): viewer.user_permissions.add(Permission.objects.get(codename="view_instances")) response = self.get(viewer, down={"other-host"}) self.assertContains(response, "Connection Failed: no route to 10.0.0.2") + + def test_global_viewer_sees_each_vms_host_without_the_host_page(self): + viewer = get_user_model().objects.create_user(username="viewer2", password="pwd") + viewer.user_permissions.add(Permission.objects.get(codename="view_instances")) + response = self.get(viewer, down=set()) + self.assertContains(response, "mine-host") + self.assertNotContains(response, reverse("overview", args=[self.mine.id])) diff --git a/instances/tests/test_list_rpc.py b/instances/tests/test_list_rpc.py index ce5f64e5..13de14d8 100644 --- a/instances/tests/test_list_rpc.py +++ b/instances/tests/test_list_rpc.py @@ -65,7 +65,7 @@ class ListInfoCallTests(TestCase): self.assertEqual(response.status_code, 200) return response - def assert_one_info_per_vm(self, response, memory="2048 MB"): + def assert_one_info_per_vm(self, response, memory="2048 MiB"): self.assertEqual(len(self.domains), 2, "one proxy per VM row") for domain in self.domains: self.assertEqual(domain.info_calls, 1)