Просмотр исходного кода

Closes #23287: Misc improvements to CI performance (#23319)

Reuse table instances for ordering checks, use signed-cookie sessions,
and limit bulk-delete tests to three objects. Update query-count
baselines to reflect the session backend change.
Jeremy Stretch 20 часов назад
Родитель
Сommit
d7ba9051b7

+ 11 - 11
netbox/circuits/tests/query_counts.json

@@ -1,24 +1,24 @@
 {
 {
   "circuit:api_list_objects": 17,
   "circuit:api_list_objects": 17,
-  "circuit:list_objects_with_permission": 19,
+  "circuit:list_objects_with_permission": 18,
   "circuitgroup:api_list_objects": 12,
   "circuitgroup:api_list_objects": 12,
-  "circuitgroup:list_objects_with_permission": 17,
+  "circuitgroup:list_objects_with_permission": 16,
   "circuitgroupassignment:api_list_objects": 17,
   "circuitgroupassignment:api_list_objects": 17,
-  "circuitgroupassignment:list_objects_with_permission": 23,
+  "circuitgroupassignment:list_objects_with_permission": 22,
   "circuittermination:api_list_objects": 17,
   "circuittermination:api_list_objects": 17,
-  "circuittermination:list_objects_with_permission": 21,
+  "circuittermination:list_objects_with_permission": 20,
   "circuittype:api_list_objects": 12,
   "circuittype:api_list_objects": 12,
-  "circuittype:list_objects_with_permission": 17,
+  "circuittype:list_objects_with_permission": 16,
   "provider:api_list_objects": 14,
   "provider:api_list_objects": 14,
-  "provider:list_objects_with_permission": 17,
+  "provider:list_objects_with_permission": 16,
   "provideraccount:api_list_objects": 13,
   "provideraccount:api_list_objects": 13,
-  "provideraccount:list_objects_with_permission": 18,
+  "provideraccount:list_objects_with_permission": 17,
   "providernetwork:api_list_objects": 13,
   "providernetwork:api_list_objects": 13,
-  "providernetwork:list_objects_with_permission": 18,
+  "providernetwork:list_objects_with_permission": 17,
   "virtualcircuit:api_list_objects": 15,
   "virtualcircuit:api_list_objects": 15,
-  "virtualcircuit:list_objects_with_permission": 21,
+  "virtualcircuit:list_objects_with_permission": 20,
   "virtualcircuittermination:api_list_objects": 16,
   "virtualcircuittermination:api_list_objects": 16,
-  "virtualcircuittermination:list_objects_with_permission": 20,
+  "virtualcircuittermination:list_objects_with_permission": 19,
   "virtualcircuittype:api_list_objects": 12,
   "virtualcircuittype:api_list_objects": 12,
-  "virtualcircuittype:list_objects_with_permission": 17
+  "virtualcircuittype:list_objects_with_permission": 16
 }
 }

+ 3 - 3
netbox/core/tests/query_counts.json

@@ -1,8 +1,8 @@
 {
 {
   "datafile:api_list_objects": 10,
   "datafile:api_list_objects": 10,
-  "datafile:list_objects_with_permission": 17,
+  "datafile:list_objects_with_permission": 16,
   "datasource:api_list_objects": 12,
   "datasource:api_list_objects": 12,
-  "datasource:list_objects_with_permission": 17,
+  "datasource:list_objects_with_permission": 16,
   "job:api_list_objects": 11,
   "job:api_list_objects": 11,
-  "job:list_objects_with_permission": 19
+  "job:list_objects_with_permission": 18
 }
 }

+ 43 - 43
netbox/dcim/tests/query_counts.json

@@ -1,98 +1,98 @@
 {
 {
   "cable:api_list_objects": 23,
   "cable:api_list_objects": 23,
-  "cable:list_objects_with_permission": 21,
+  "cable:list_objects_with_permission": 20,
   "cablebundle:api_list_objects": 12,
   "cablebundle:api_list_objects": 12,
-  "cablebundle:list_objects_with_permission": 17,
+  "cablebundle:list_objects_with_permission": 16,
   "cabletermination:api_list_objects": 16,
   "cabletermination:api_list_objects": 16,
-  "consoleconnection:list_objects_with_permission": 29,
+  "consoleconnection:list_objects_with_permission": 28,
   "consoleport:api_list_objects": 13,
   "consoleport:api_list_objects": 13,
-  "consoleport:list_objects_with_permission": 18,
+  "consoleport:list_objects_with_permission": 17,
   "consoleporttemplate:api_list_objects": 11,
   "consoleporttemplate:api_list_objects": 11,
   "consoleserverport:api_list_objects": 13,
   "consoleserverport:api_list_objects": 13,
-  "consoleserverport:list_objects_with_permission": 18,
+  "consoleserverport:list_objects_with_permission": 17,
   "consoleserverporttemplate:api_list_objects": 11,
   "consoleserverporttemplate:api_list_objects": 11,
   "coolingfeed:api_list_objects": 14,
   "coolingfeed:api_list_objects": 14,
-  "coolingfeed:list_objects_with_permission": 19,
+  "coolingfeed:list_objects_with_permission": 18,
   "coolingintake:api_list_objects": 13,
   "coolingintake:api_list_objects": 13,
-  "coolingintake:list_objects_with_permission": 18,
+  "coolingintake:list_objects_with_permission": 17,
   "coolingintaketemplate:api_list_objects": 11,
   "coolingintaketemplate:api_list_objects": 11,
   "coolingoutflow:api_list_objects": 13,
   "coolingoutflow:api_list_objects": 13,
-  "coolingoutflow:list_objects_with_permission": 19,
+  "coolingoutflow:list_objects_with_permission": 18,
   "coolingoutflowtemplate:api_list_objects": 11,
   "coolingoutflowtemplate:api_list_objects": 11,
   "coolingsource:api_list_objects": 14,
   "coolingsource:api_list_objects": 14,
-  "coolingsource:list_objects_with_permission": 19,
+  "coolingsource:list_objects_with_permission": 18,
   "device:api_list_objects": 19,
   "device:api_list_objects": 19,
-  "device:list_objects_with_permission": 22,
+  "device:list_objects_with_permission": 21,
   "devicebay:api_list_objects": 13,
   "devicebay:api_list_objects": 13,
-  "devicebay:list_objects_with_permission": 18,
+  "devicebay:list_objects_with_permission": 17,
   "devicebaytemplate:api_list_objects": 11,
   "devicebaytemplate:api_list_objects": 11,
   "devicerole:api_list_objects": 12,
   "devicerole:api_list_objects": 12,
-  "devicerole:list_objects_with_permission": 17,
+  "devicerole:list_objects_with_permission": 16,
   "devicetype:api_list_objects": 13,
   "devicetype:api_list_objects": 13,
-  "devicetype:list_objects_with_permission": 18,
+  "devicetype:list_objects_with_permission": 17,
   "frontport:api_list_objects": 14,
   "frontport:api_list_objects": 14,
-  "frontport:list_objects_with_permission": 22,
+  "frontport:list_objects_with_permission": 21,
   "frontporttemplate:api_list_objects": 12,
   "frontporttemplate:api_list_objects": 12,
   "interface:api_list_objects": 23,
   "interface:api_list_objects": 23,
-  "interface:list_objects_with_permission": 18,
-  "interfaceconnection:list_objects_with_permission": 41,
+  "interface:list_objects_with_permission": 17,
+  "interfaceconnection:list_objects_with_permission": 40,
   "interfacetemplate:api_list_objects": 11,
   "interfacetemplate:api_list_objects": 11,
   "inventoryitem:api_list_objects": 19,
   "inventoryitem:api_list_objects": 19,
-  "inventoryitem:list_objects_with_permission": 20,
+  "inventoryitem:list_objects_with_permission": 19,
   "inventoryitemrole:api_list_objects": 12,
   "inventoryitemrole:api_list_objects": 12,
-  "inventoryitemrole:list_objects_with_permission": 17,
+  "inventoryitemrole:list_objects_with_permission": 16,
   "inventoryitemtemplate:api_list_objects": 13,
   "inventoryitemtemplate:api_list_objects": 13,
   "location:api_list_objects": 14,
   "location:api_list_objects": 14,
-  "location:list_objects_with_permission": 19,
+  "location:list_objects_with_permission": 18,
   "macaddress:api_list_objects": 16,
   "macaddress:api_list_objects": 16,
-  "macaddress:list_objects_with_permission": 21,
+  "macaddress:list_objects_with_permission": 20,
   "manufacturer:api_list_objects": 12,
   "manufacturer:api_list_objects": 12,
-  "manufacturer:list_objects_with_permission": 17,
+  "manufacturer:list_objects_with_permission": 16,
   "module:api_list_objects": 19,
   "module:api_list_objects": 19,
-  "module:list_objects_with_permission": 21,
+  "module:list_objects_with_permission": 20,
   "modulebay:api_list_objects": 16,
   "modulebay:api_list_objects": 16,
-  "modulebay:list_objects_with_permission": 18,
+  "modulebay:list_objects_with_permission": 17,
   "modulebaytemplate:api_list_objects": 13,
   "modulebaytemplate:api_list_objects": 13,
   "modulebaytype:api_list_objects": 13,
   "modulebaytype:api_list_objects": 13,
-  "modulebaytype:list_objects_with_permission": 18,
+  "modulebaytype:list_objects_with_permission": 17,
   "moduletype:api_list_objects": 15,
   "moduletype:api_list_objects": 15,
-  "moduletype:list_objects_with_permission": 19,
+  "moduletype:list_objects_with_permission": 18,
   "moduletypeprofile:api_list_objects": 12,
   "moduletypeprofile:api_list_objects": 12,
-  "moduletypeprofile:list_objects_with_permission": 17,
+  "moduletypeprofile:list_objects_with_permission": 16,
   "platform:api_list_objects": 12,
   "platform:api_list_objects": 12,
-  "platform:list_objects_with_permission": 18,
-  "powerconnection:list_objects_with_permission": 29,
+  "platform:list_objects_with_permission": 17,
+  "powerconnection:list_objects_with_permission": 28,
   "powerfeed:api_list_objects": 14,
   "powerfeed:api_list_objects": 14,
-  "powerfeed:list_objects_with_permission": 19,
+  "powerfeed:list_objects_with_permission": 18,
   "poweroutlet:api_list_objects": 13,
   "poweroutlet:api_list_objects": 13,
-  "poweroutlet:list_objects_with_permission": 19,
+  "poweroutlet:list_objects_with_permission": 18,
   "poweroutlettemplate:api_list_objects": 11,
   "poweroutlettemplate:api_list_objects": 11,
   "powerpanel:api_list_objects": 14,
   "powerpanel:api_list_objects": 14,
-  "powerpanel:list_objects_with_permission": 19,
+  "powerpanel:list_objects_with_permission": 18,
   "powerport:api_list_objects": 13,
   "powerport:api_list_objects": 13,
-  "powerport:list_objects_with_permission": 18,
+  "powerport:list_objects_with_permission": 17,
   "powerporttemplate:api_list_objects": 11,
   "powerporttemplate:api_list_objects": 11,
   "rack:api_list_objects": 16,
   "rack:api_list_objects": 16,
-  "rack:list_objects_with_permission": 26,
+  "rack:list_objects_with_permission": 25,
   "rackgroup:api_list_objects": 12,
   "rackgroup:api_list_objects": 12,
-  "rackgroup:list_objects_with_permission": 17,
+  "rackgroup:list_objects_with_permission": 16,
   "rackreservation:api_list_objects": 14,
   "rackreservation:api_list_objects": 14,
-  "rackreservation:list_objects_with_permission": 20,
+  "rackreservation:list_objects_with_permission": 19,
   "rackrole:api_list_objects": 12,
   "rackrole:api_list_objects": 12,
-  "rackrole:list_objects_with_permission": 17,
+  "rackrole:list_objects_with_permission": 16,
   "racktype:api_list_objects": 13,
   "racktype:api_list_objects": 13,
-  "racktype:list_objects_with_permission": 18,
+  "racktype:list_objects_with_permission": 17,
   "rearport:api_list_objects": 14,
   "rearport:api_list_objects": 14,
-  "rearport:list_objects_with_permission": 19,
+  "rearport:list_objects_with_permission": 18,
   "rearporttemplate:api_list_objects": 12,
   "rearporttemplate:api_list_objects": 12,
   "region:api_list_objects": 12,
   "region:api_list_objects": 12,
-  "region:list_objects_with_permission": 17,
+  "region:list_objects_with_permission": 16,
   "site:api_list_objects": 16,
   "site:api_list_objects": 16,
-  "site:list_objects_with_permission": 19,
+  "site:list_objects_with_permission": 18,
   "sitegroup:api_list_objects": 12,
   "sitegroup:api_list_objects": 12,
-  "sitegroup:list_objects_with_permission": 17,
+  "sitegroup:list_objects_with_permission": 16,
   "virtualchassis:api_list_objects": 15,
   "virtualchassis:api_list_objects": 15,
-  "virtualchassis:list_objects_with_permission": 18,
+  "virtualchassis:list_objects_with_permission": 17,
   "virtualdevicecontext:api_list_objects": 13,
   "virtualdevicecontext:api_list_objects": 13,
-  "virtualdevicecontext:list_objects_with_permission": 17
+  "virtualdevicecontext:list_objects_with_permission": 16
 }
 }

+ 16 - 16
netbox/extras/tests/query_counts.json

@@ -1,37 +1,37 @@
 {
 {
   "bookmark:api_list_objects": 12,
   "bookmark:api_list_objects": 12,
-  "bookmark:list_objects_with_permission": 17,
+  "bookmark:list_objects_with_permission": 16,
   "configcontext:api_list_objects": 22,
   "configcontext:api_list_objects": 22,
-  "configcontext:list_objects_with_permission": 16,
+  "configcontext:list_objects_with_permission": 15,
   "configcontextprofile:api_list_objects": 12,
   "configcontextprofile:api_list_objects": 12,
-  "configcontextprofile:list_objects_with_permission": 16,
+  "configcontextprofile:list_objects_with_permission": 15,
   "configtemplate:api_list_objects": 10,
   "configtemplate:api_list_objects": 10,
-  "configtemplate:list_objects_with_permission": 17,
+  "configtemplate:list_objects_with_permission": 16,
   "customfield:api_list_objects": 10,
   "customfield:api_list_objects": 10,
-  "customfield:list_objects_with_permission": 19,
+  "customfield:list_objects_with_permission": 18,
   "customfieldchoiceset:api_list_objects": 9,
   "customfieldchoiceset:api_list_objects": 9,
-  "customfieldchoiceset:list_objects_with_permission": 16,
+  "customfieldchoiceset:list_objects_with_permission": 15,
   "customlink:api_list_objects": 10,
   "customlink:api_list_objects": 10,
-  "customlink:list_objects_with_permission": 18,
+  "customlink:list_objects_with_permission": 17,
   "eventrule:api_list_objects": 15,
   "eventrule:api_list_objects": 15,
-  "eventrule:list_objects_with_permission": 20,
+  "eventrule:list_objects_with_permission": 19,
   "exporttemplate:api_list_objects": 10,
   "exporttemplate:api_list_objects": 10,
-  "exporttemplate:list_objects_with_permission": 19,
+  "exporttemplate:list_objects_with_permission": 18,
   "imageattachment:api_list_objects": 11,
   "imageattachment:api_list_objects": 11,
-  "imageattachment:list_objects_with_permission": 21,
+  "imageattachment:list_objects_with_permission": 20,
   "journalentry:api_list_objects": 15,
   "journalentry:api_list_objects": 15,
-  "journalentry:list_objects_with_permission": 21,
+  "journalentry:list_objects_with_permission": 20,
   "notification:api_list_objects": 12,
   "notification:api_list_objects": 12,
   "notificationgroup:api_list_objects": 11,
   "notificationgroup:api_list_objects": 11,
-  "notificationgroup:list_objects_with_permission": 18,
+  "notificationgroup:list_objects_with_permission": 17,
   "savedfilter:api_list_objects": 10,
   "savedfilter:api_list_objects": 10,
-  "savedfilter:list_objects_with_permission": 19,
+  "savedfilter:list_objects_with_permission": 18,
   "subscription:api_list_objects": 12,
   "subscription:api_list_objects": 12,
   "tableconfig:api_list_objects": 11,
   "tableconfig:api_list_objects": 11,
-  "tableconfig:list_objects_with_permission": 19,
+  "tableconfig:list_objects_with_permission": 18,
   "tag:api_list_objects": 10,
   "tag:api_list_objects": 10,
-  "tag:list_objects_with_permission": 18,
+  "tag:list_objects_with_permission": 17,
   "taggeditem:api_list_objects": 12,
   "taggeditem:api_list_objects": 12,
   "webhook:api_list_objects": 12,
   "webhook:api_list_objects": 12,
-  "webhook:list_objects_with_permission": 17
+  "webhook:list_objects_with_permission": 16
 }
 }

+ 17 - 17
netbox/ipam/tests/query_counts.json

@@ -1,37 +1,37 @@
 {
 {
   "aggregate:api_list_objects": 13,
   "aggregate:api_list_objects": 13,
-  "aggregate:list_objects_with_permission": 21,
+  "aggregate:list_objects_with_permission": 20,
   "asn:api_list_objects": 16,
   "asn:api_list_objects": 16,
-  "asn:list_objects_with_permission": 21,
+  "asn:list_objects_with_permission": 20,
   "asnrange:api_list_objects": 14,
   "asnrange:api_list_objects": 14,
-  "asnrange:list_objects_with_permission": 19,
+  "asnrange:list_objects_with_permission": 18,
   "fhrpgroup:api_list_objects": 13,
   "fhrpgroup:api_list_objects": 13,
-  "fhrpgroup:list_objects_with_permission": 18,
+  "fhrpgroup:list_objects_with_permission": 17,
   "fhrpgroupassignment:api_list_objects": 18,
   "fhrpgroupassignment:api_list_objects": 18,
   "ipaddress:api_list_objects": 13,
   "ipaddress:api_list_objects": 13,
-  "ipaddress:list_objects_with_permission": 18,
+  "ipaddress:list_objects_with_permission": 17,
   "iprange:api_list_objects": 12,
   "iprange:api_list_objects": 12,
-  "iprange:list_objects_with_permission": 17,
+  "iprange:list_objects_with_permission": 16,
   "prefix:api_list_objects": 12,
   "prefix:api_list_objects": 12,
-  "prefix:list_objects_with_permission": 26,
+  "prefix:list_objects_with_permission": 25,
   "rir:api_list_objects": 12,
   "rir:api_list_objects": 12,
-  "rir:list_objects_with_permission": 17,
+  "rir:list_objects_with_permission": 16,
   "role:api_list_objects": 12,
   "role:api_list_objects": 12,
-  "role:list_objects_with_permission": 17,
+  "role:list_objects_with_permission": 16,
   "routetarget:api_list_objects": 12,
   "routetarget:api_list_objects": 12,
-  "routetarget:list_objects_with_permission": 18,
+  "routetarget:list_objects_with_permission": 17,
   "service:api_list_objects": 15,
   "service:api_list_objects": 15,
-  "service:list_objects_with_permission": 18,
+  "service:list_objects_with_permission": 17,
   "servicetemplate:api_list_objects": 12,
   "servicetemplate:api_list_objects": 12,
-  "servicetemplate:list_objects_with_permission": 17,
+  "servicetemplate:list_objects_with_permission": 16,
   "vlan:api_list_objects": 14,
   "vlan:api_list_objects": 14,
-  "vlan:list_objects_with_permission": 21,
+  "vlan:list_objects_with_permission": 20,
   "vlangroup:api_list_objects": 12,
   "vlangroup:api_list_objects": 12,
-  "vlangroup:list_objects_with_permission": 22,
+  "vlangroup:list_objects_with_permission": 21,
   "vlantranslationpolicy:api_list_objects": 13,
   "vlantranslationpolicy:api_list_objects": 13,
-  "vlantranslationpolicy:list_objects_with_permission": 17,
+  "vlantranslationpolicy:list_objects_with_permission": 16,
   "vlantranslationrule:api_list_objects": 13,
   "vlantranslationrule:api_list_objects": 13,
-  "vlantranslationrule:list_objects_with_permission": 18,
+  "vlantranslationrule:list_objects_with_permission": 17,
   "vrf:api_list_objects": 20,
   "vrf:api_list_objects": 20,
-  "vrf:list_objects_with_permission": 17
+  "vrf:list_objects_with_permission": 16
 }
 }

+ 6 - 6
netbox/tenancy/tests/query_counts.json

@@ -1,14 +1,14 @@
 {
 {
   "contact:api_list_objects": 13,
   "contact:api_list_objects": 13,
-  "contact:list_objects_with_permission": 18,
+  "contact:list_objects_with_permission": 17,
   "contactassignment:api_list_objects": 16,
   "contactassignment:api_list_objects": 16,
-  "contactassignment:list_objects_with_permission": 22,
+  "contactassignment:list_objects_with_permission": 21,
   "contactgroup:api_list_objects": 13,
   "contactgroup:api_list_objects": 13,
-  "contactgroup:list_objects_with_permission": 17,
+  "contactgroup:list_objects_with_permission": 16,
   "contactrole:api_list_objects": 12,
   "contactrole:api_list_objects": 12,
-  "contactrole:list_objects_with_permission": 17,
+  "contactrole:list_objects_with_permission": 16,
   "tenant:api_list_objects": 13,
   "tenant:api_list_objects": 13,
-  "tenant:list_objects_with_permission": 18,
+  "tenant:list_objects_with_permission": 17,
   "tenantgroup:api_list_objects": 13,
   "tenantgroup:api_list_objects": 13,
-  "tenantgroup:list_objects_with_permission": 17
+  "tenantgroup:list_objects_with_permission": 16
 }
 }

+ 6 - 6
netbox/users/tests/query_counts.json

@@ -1,14 +1,14 @@
 {
 {
   "group:api_list_objects": 10,
   "group:api_list_objects": 10,
-  "group:list_objects_with_permission": 16,
+  "group:list_objects_with_permission": 15,
   "objectpermission:api_list_objects": 12,
   "objectpermission:api_list_objects": 12,
-  "objectpermission:list_objects_with_permission": 17,
+  "objectpermission:list_objects_with_permission": 16,
   "owner:api_list_objects": 11,
   "owner:api_list_objects": 11,
-  "owner:list_objects_with_permission": 18,
+  "owner:list_objects_with_permission": 17,
   "ownergroup:api_list_objects": 9,
   "ownergroup:api_list_objects": 9,
-  "ownergroup:list_objects_with_permission": 16,
+  "ownergroup:list_objects_with_permission": 15,
   "token:api_list_objects": 10,
   "token:api_list_objects": 10,
-  "token:list_objects_with_permission": 17,
+  "token:list_objects_with_permission": 16,
   "user:api_list_objects": 12,
   "user:api_list_objects": 12,
-  "user:list_objects_with_permission": 16
+  "user:list_objects_with_permission": 15
 }
 }

+ 4 - 1
netbox/utilities/testing/base.py

@@ -8,7 +8,7 @@ from django.core.exceptions import FieldDoesNotExist
 from django.db import transaction
 from django.db import transaction
 from django.db.models import DateField, DateTimeField, JSONField, ManyToManyField, ManyToManyRel
 from django.db.models import DateField, DateTimeField, JSONField, ManyToManyField, ManyToManyRel
 from django.forms.models import model_to_dict
 from django.forms.models import model_to_dict
-from django.test import Client
+from django.test import Client, override_settings
 from django.test import TestCase as _TestCase
 from django.test import TestCase as _TestCase
 from netaddr import IPNetwork
 from netaddr import IPNetwork
 from taggit.managers import TaggableManager
 from taggit.managers import TaggableManager
@@ -28,6 +28,9 @@ __all__ = (
 )
 )
 
 
 
 
+# Store test client sessions in signed cookies rather than the database, which spares each test the several
+# queries otherwise needed to log in its user
+@override_settings(SESSION_ENGINE='django.contrib.sessions.backends.signed_cookies')
 class TestCase(_TestCase):
 class TestCase(_TestCase):
     user_permissions = ()
     user_permissions = ()
 
 

+ 20 - 14
netbox/utilities/testing/tables.py

@@ -85,7 +85,8 @@ class ModelTableTestCase(TestCase):
         smoke test.
         smoke test.
         """
         """
         if cls.queryset_sources is not None:
         if cls.queryset_sources is not None:
-            return tuple(cls.queryset_sources)
+            # Clone each QuerySet to avoid caching results on the class-level instances
+            return tuple((label, queryset.all()) for label, queryset in cls.queryset_sources)
 
 
         model = cls.table._meta.model
         model = cls.table._meta.model
         app_label = model._meta.app_label
         app_label = model._meta.app_label
@@ -111,12 +112,12 @@ class ModelTableTestCase(TestCase):
 
 
         return tuple(sources)
         return tuple(sources)
 
 
-    def iter_orderable_columns(self, queryset):
+    def iter_orderable_columns(self, table):
         """
         """
-        Yield the names of all orderable columns for *queryset*, excluding
-        any listed in ``excluded_orderable_columns``.
+        Yield the names of all orderable columns in *table*, excluding any
+        listed in ``excluded_orderable_columns``.
         """
         """
-        for column in self.get_table(queryset).columns:
+        for column in table.columns:
             if not column.orderable:
             if not column.orderable:
                 continue
                 continue
             if column.name in self.excluded_orderable_columns:
             if column.name in self.excluded_orderable_columns:
@@ -138,20 +139,25 @@ class TableTestCases:
 
 
         def test_every_orderable_column_renders(self):
         def test_every_orderable_column_renders(self):
             """
             """
-            Verify that each declared ordering can be applied without error.
+            Smoke test: verify that each orderable column's ordering can be
+            applied and executed (against empty querysets) without error.
 
 
-            This is intentionally a smoke test. It validates ordering against
-            the configured queryset sources but does not create model
-            instances by default, so it complements rather than replaces
-            data-backed rendering tests for tables whose behavior depends on
-            populated querysets.
+            Each table is instantiated and rendered once per source, as both
+            are expensive and independent of ordering. Its initial queryset is
+            then restored before each ordering is applied and executed.
             """
             """
             request = self.get_request()
             request = self.get_request()
 
 
             for source_name, queryset in self.get_queryset_sources():
             for source_name, queryset in self.get_queryset_sources():
-                for column_name in self.iter_orderable_columns(queryset):
+                table = self.get_table(queryset)
+                initial_queryset = table.data.data
+
+                with self.subTest(source=source_name):
+                    table.as_html(request)
+
+                for column_name in self.iter_orderable_columns(table):
                     for direction, prefix in (("asc", ""), ("desc", "-")):
                     for direction, prefix in (("asc", ""), ("desc", "-")):
                         with self.subTest(source=source_name, column=column_name, direction=direction):
                         with self.subTest(source=source_name, column=column_name, direction=direction):
-                            table = self.get_table(queryset)
+                            table.data.data = initial_queryset
                             table.order_by = f"{prefix}{column_name}"
                             table.order_by = f"{prefix}{column_name}"
-                            table.as_html(request)
+                            list(table.data)

+ 3 - 3
netbox/utilities/testing/views.py

@@ -1046,7 +1046,7 @@ class ViewTestCases:
                 self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 403)
                 self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 403)
 
 
         def test_bulk_delete_objects_with_permission(self):
         def test_bulk_delete_objects_with_permission(self):
-            pk_list = list(self._get_queryset().values_list('pk', flat=True))[:3]
+            pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
             data = {
             data = {
                 'pk': pk_list,
                 'pk': pk_list,
                 'confirm': True,
                 'confirm': True,
@@ -1083,7 +1083,7 @@ class ViewTestCases:
                         message=data['changelog_message'])
                         message=data['changelog_message'])
 
 
         def test_bulk_delete_objects_with_constrained_permission(self):
         def test_bulk_delete_objects_with_constrained_permission(self):
-            pk_list = self._get_queryset().values_list('pk', flat=True)
+            pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
             data = {
             data = {
                 'pk': pk_list,
                 'pk': pk_list,
                 'confirm': True,
                 'confirm': True,
@@ -1111,7 +1111,7 @@ class ViewTestCases:
 
 
             # Bulk delete permitted objects
             # Bulk delete permitted objects
             self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
             self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
-            self.assertEqual(self._get_queryset().count(), 0)
+            self.assertFalse(self._get_queryset().filter(pk__in=pk_list).exists())
 
 
     class BulkRenameObjectsViewTestCase(ModelViewTestCase):
     class BulkRenameObjectsViewTestCase(ModelViewTestCase):
         """
         """

+ 7 - 7
netbox/virtualization/tests/query_counts.json

@@ -1,16 +1,16 @@
 {
 {
   "cluster:api_list_objects": 14,
   "cluster:api_list_objects": 14,
-  "cluster:list_objects_with_permission": 19,
+  "cluster:list_objects_with_permission": 18,
   "clustergroup:api_list_objects": 12,
   "clustergroup:api_list_objects": 12,
-  "clustergroup:list_objects_with_permission": 17,
+  "clustergroup:list_objects_with_permission": 16,
   "clustertype:api_list_objects": 12,
   "clustertype:api_list_objects": 12,
-  "clustertype:list_objects_with_permission": 17,
+  "clustertype:list_objects_with_permission": 16,
   "virtualdisk:api_list_objects": 13,
   "virtualdisk:api_list_objects": 13,
-  "virtualdisk:list_objects_with_permission": 18,
+  "virtualdisk:list_objects_with_permission": 17,
   "virtualmachine:api_list_objects": 17,
   "virtualmachine:api_list_objects": 17,
-  "virtualmachine:list_objects_with_permission": 21,
+  "virtualmachine:list_objects_with_permission": 20,
   "virtualmachinetype:api_list_objects": 13,
   "virtualmachinetype:api_list_objects": 13,
-  "virtualmachinetype:list_objects_with_permission": 18,
+  "virtualmachinetype:list_objects_with_permission": 17,
   "vminterface:api_list_objects": 18,
   "vminterface:api_list_objects": 18,
-  "vminterface:list_objects_with_permission": 18
+  "vminterface:list_objects_with_permission": 17
 }
 }

+ 10 - 10
netbox/vpn/tests/query_counts.json

@@ -1,22 +1,22 @@
 {
 {
   "ikepolicy:api_list_objects": 13,
   "ikepolicy:api_list_objects": 13,
-  "ikepolicy:list_objects_with_permission": 18,
+  "ikepolicy:list_objects_with_permission": 17,
   "ikeproposal:api_list_objects": 12,
   "ikeproposal:api_list_objects": 12,
-  "ikeproposal:list_objects_with_permission": 17,
+  "ikeproposal:list_objects_with_permission": 16,
   "ipsecpolicy:api_list_objects": 13,
   "ipsecpolicy:api_list_objects": 13,
-  "ipsecpolicy:list_objects_with_permission": 18,
+  "ipsecpolicy:list_objects_with_permission": 17,
   "ipsecprofile:api_list_objects": 14,
   "ipsecprofile:api_list_objects": 14,
-  "ipsecprofile:list_objects_with_permission": 19,
+  "ipsecprofile:list_objects_with_permission": 18,
   "ipsecproposal:api_list_objects": 12,
   "ipsecproposal:api_list_objects": 12,
-  "ipsecproposal:list_objects_with_permission": 17,
+  "ipsecproposal:list_objects_with_permission": 16,
   "l2vpn:api_list_objects": 14,
   "l2vpn:api_list_objects": 14,
-  "l2vpn:list_objects_with_permission": 17,
+  "l2vpn:list_objects_with_permission": 16,
   "l2vpntermination:api_list_objects": 15,
   "l2vpntermination:api_list_objects": 15,
-  "l2vpntermination:list_objects_with_permission": 22,
+  "l2vpntermination:list_objects_with_permission": 21,
   "tunnel:api_list_objects": 13,
   "tunnel:api_list_objects": 13,
-  "tunnel:list_objects_with_permission": 18,
+  "tunnel:list_objects_with_permission": 17,
   "tunnelgroup:api_list_objects": 12,
   "tunnelgroup:api_list_objects": 12,
-  "tunnelgroup:list_objects_with_permission": 17,
+  "tunnelgroup:list_objects_with_permission": 16,
   "tunneltermination:api_list_objects": 17,
   "tunneltermination:api_list_objects": 17,
-  "tunneltermination:list_objects_with_permission": 25
+  "tunneltermination:list_objects_with_permission": 24
 }
 }

+ 3 - 3
netbox/wireless/tests/query_counts.json

@@ -1,8 +1,8 @@
 {
 {
   "wirelesslan:api_list_objects": 12,
   "wirelesslan:api_list_objects": 12,
-  "wirelesslan:list_objects_with_permission": 18,
+  "wirelesslan:list_objects_with_permission": 17,
   "wirelesslangroup:api_list_objects": 12,
   "wirelesslangroup:api_list_objects": 12,
-  "wirelesslangroup:list_objects_with_permission": 18,
+  "wirelesslangroup:list_objects_with_permission": 17,
   "wirelesslink:api_list_objects": 17,
   "wirelesslink:api_list_objects": 17,
-  "wirelesslink:list_objects_with_permission": 21
+  "wirelesslink:list_objects_with_permission": 20
 }
 }