diff --git a/computes/views.py b/computes/views.py index f25b5953..59508bc6 100644 --- a/computes/views.py +++ b/computes/views.py @@ -104,9 +104,7 @@ def instances(request, compute_id): compute = get_object_or_404(Compute, pk=compute_id) utils.refresh_instance_database(compute) - instances = Instance.objects.filter(compute=compute).prefetch_related( - "userinstance_set" - ) + instances = Instance.objects.filter(compute=compute).order_by("name").prefetch_related("userinstance_set") return render( request, "computes/instances.html", diff --git a/instances/tests/test_states.py b/instances/tests/test_states.py index 70d6b39b..6c479b84 100644 --- a/instances/tests/test_states.py +++ b/instances/tests/test_states.py @@ -133,6 +133,20 @@ class StateTests(TestCase): self.assertEqual([r.xpath("td[@data-sort]/@data-sort") for r in rows[1:2]], [["2", "2097152"]]) + def test_the_lists_show_the_vms_by_name(self): + # the first VM made comes last by name: the database's own order is not the list's + Instance.objects.filter(pk=self.vms[0].pk).update(name="st-vm-9") + by_name = [LABELS[state] for state in [*list(LABELS)[1:], 0]] + for style in ("grouped", "nongrouped"): + AppSettings.objects.filter(key="VIEW_INSTANCES_LIST_STYLE").update(value=style) + page = html.fromstring(self.get(self.admin, reverse("instances:index"))) + with self.subTest(style=style): + self.assertEqual(page.xpath("//tbody//tr//@data-state"), by_name) + page = html.fromstring(self.get(self.admin, reverse("instances", args=[self.compute.id]))) + names = [name.strip() for name in page.xpath("//tbody//tr//a[starts-with(normalize-space(), 'st-vm-')]/text()")] + self.assertEqual(names, sorted(names)) + + class DetailStateTests(TestCase): """The VM page names every state and offers a forced power off where only that helps.""" diff --git a/instances/views.py b/instances/views.py index 33b41800..60d6cbef 100755 --- a/instances/views.py +++ b/instances/views.py @@ -27,7 +27,7 @@ from django.contrib.auth.models import User from django.core import signing from django.core.exceptions import PermissionDenied, ValidationError from django.http import Http404, HttpResponse, JsonResponse -from django.db.models import prefetch_related_objects +from django.db.models import Prefetch, prefetch_related_objects from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone @@ -72,16 +72,18 @@ def index(request): computes = list(Compute.objects.all().order_by("name")) for compute in computes: utils.refr(compute) - prefetch_related_objects(computes, "instance_set__userinstance_set") + # each host's VMs by name: the database keeps no order of its own + prefetch_related_objects(computes, Prefetch("instance_set", queryset=Instance.objects.order_by("name")), + "instance_set__userinstance_set") show_hosts = request.user.is_superuser or request.user.has_perm("instances.view_instances") own_hosts_down = False if show_hosts: - instances = Instance.objects.all().prefetch_related("userinstance_set") + instances = Instance.objects.order_by("name").prefetch_related("userinstance_set") else: instances = Instance.objects.filter( userinstance__user=request.user - ).prefetch_related("userinstance_set") + ).order_by("name").prefetch_related("userinstance_set") # other users get no host names or connection errors, only whether # a host of their own VMs is down own = set(instances.values_list("compute_id", flat=True))