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

Closes #4865: Replace all Count() annotations with subqueries (#5385)

* Convert circuits to use subqueries

* Convert dcim to use subqueries

* Convert extras to use subqueries

* Convert ipam to use subqueries

* Convert secrets to use subqueries

* Convert virtualization to use subqueries

* Update global search view to use subqueries where appropriate

* Remove extraneous order_by() calls
Jeremy Stretch 5 лет назад
Родитель
Сommit
f55e966c8f

+ 6 - 5
netbox/circuits/api/views.py

@@ -1,4 +1,4 @@
-from django.db.models import Count, Prefetch
+from django.db.models import Prefetch
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from rest_framework.decorators import action
 from rest_framework.decorators import action
 from rest_framework.response import Response
 from rest_framework.response import Response
@@ -10,6 +10,7 @@ from extras.api.serializers import RenderedGraphSerializer
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from extras.models import Graph
 from extras.models import Graph
 from utilities.api import ModelViewSet
 from utilities.api import ModelViewSet
+from utilities.utils import get_subquery
 from . import serializers
 from . import serializers
 
 
 
 
@@ -27,8 +28,8 @@ class CircuitsRootView(APIRootView):
 
 
 class ProviderViewSet(CustomFieldModelViewSet):
 class ProviderViewSet(CustomFieldModelViewSet):
     queryset = Provider.objects.prefetch_related('tags').annotate(
     queryset = Provider.objects.prefetch_related('tags').annotate(
-        circuit_count=Count('circuits')
-    ).order_by(*Provider._meta.ordering)
+        circuit_count=get_subquery(Circuit, 'provider')
+    )
     serializer_class = serializers.ProviderSerializer
     serializer_class = serializers.ProviderSerializer
     filterset_class = filters.ProviderFilterSet
     filterset_class = filters.ProviderFilterSet
 
 
@@ -49,8 +50,8 @@ class ProviderViewSet(CustomFieldModelViewSet):
 
 
 class CircuitTypeViewSet(ModelViewSet):
 class CircuitTypeViewSet(ModelViewSet):
     queryset = CircuitType.objects.annotate(
     queryset = CircuitType.objects.annotate(
-        circuit_count=Count('circuits')
-    ).order_by(*CircuitType._meta.ordering)
+        circuit_count=get_subquery(Circuit, 'type')
+    )
     serializer_class = serializers.CircuitTypeSerializer
     serializer_class = serializers.CircuitTypeSerializer
     filterset_class = filters.CircuitTypeFilterSet
     filterset_class = filters.CircuitTypeFilterSet
 
 

+ 16 - 6
netbox/circuits/views.py

@@ -1,12 +1,12 @@
 from django.contrib import messages
 from django.contrib import messages
 from django.db import transaction
 from django.db import transaction
-from django.db.models import Count
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django_tables2 import RequestConfig
 from django_tables2 import RequestConfig
 
 
 from extras.models import Graph
 from extras.models import Graph
 from utilities.forms import ConfirmationForm
 from utilities.forms import ConfirmationForm
 from utilities.paginator import EnhancedPaginator, get_paginate_count
 from utilities.paginator import EnhancedPaginator, get_paginate_count
+from utilities.utils import get_subquery
 from utilities.views import (
 from utilities.views import (
     BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
     BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 )
@@ -20,7 +20,9 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
 #
 #
 
 
 class ProviderListView(ObjectListView):
 class ProviderListView(ObjectListView):
-    queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
+    queryset = Provider.objects.annotate(
+        count_circuits=get_subquery(Circuit, 'provider')
+    )
     filterset = filters.ProviderFilterSet
     filterset = filters.ProviderFilterSet
     filterset_form = forms.ProviderFilterForm
     filterset_form = forms.ProviderFilterForm
     table = tables.ProviderTable
     table = tables.ProviderTable
@@ -72,14 +74,18 @@ class ProviderBulkImportView(BulkImportView):
 
 
 
 
 class ProviderBulkEditView(BulkEditView):
 class ProviderBulkEditView(BulkEditView):
-    queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
+    queryset = Provider.objects.annotate(
+        count_circuits=get_subquery(Circuit, 'provider')
+    )
     filterset = filters.ProviderFilterSet
     filterset = filters.ProviderFilterSet
     table = tables.ProviderTable
     table = tables.ProviderTable
     form = forms.ProviderBulkEditForm
     form = forms.ProviderBulkEditForm
 
 
 
 
 class ProviderBulkDeleteView(BulkDeleteView):
 class ProviderBulkDeleteView(BulkDeleteView):
-    queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
+    queryset = Provider.objects.annotate(
+        count_circuits=get_subquery(Circuit, 'provider')
+    )
     filterset = filters.ProviderFilterSet
     filterset = filters.ProviderFilterSet
     table = tables.ProviderTable
     table = tables.ProviderTable
 
 
@@ -89,7 +95,9 @@ class ProviderBulkDeleteView(BulkDeleteView):
 #
 #
 
 
 class CircuitTypeListView(ObjectListView):
 class CircuitTypeListView(ObjectListView):
-    queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
+    queryset = CircuitType.objects.annotate(
+        circuit_count=get_subquery(Circuit, 'type')
+    )
     table = tables.CircuitTypeTable
     table = tables.CircuitTypeTable
 
 
 
 
@@ -109,7 +117,9 @@ class CircuitTypeBulkImportView(BulkImportView):
 
 
 
 
 class CircuitTypeBulkDeleteView(BulkDeleteView):
 class CircuitTypeBulkDeleteView(BulkDeleteView):
-    queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
+    queryset = CircuitType.objects.annotate(
+        circuit_count=get_subquery(Circuit, 'type')
+    )
     table = tables.CircuitTypeTable
     table = tables.CircuitTypeTable
 
 
 
 

+ 14 - 14
netbox/dcim/api/views.py

@@ -2,7 +2,7 @@ import socket
 from collections import OrderedDict
 from collections import OrderedDict
 
 
 from django.conf import settings
 from django.conf import settings
-from django.db.models import Count, F
+from django.db.models import F
 from django.http import HttpResponseForbidden, HttpResponse
 from django.http import HttpResponseForbidden, HttpResponse
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from drf_yasg import openapi
 from drf_yasg import openapi
@@ -109,7 +109,7 @@ class SiteViewSet(CustomFieldModelViewSet):
         vlan_count=get_subquery(VLAN, 'site'),
         vlan_count=get_subquery(VLAN, 'site'),
         circuit_count=get_subquery(Circuit, 'terminations__site'),
         circuit_count=get_subquery(Circuit, 'terminations__site'),
         virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
         virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
-    ).order_by(*Site._meta.ordering)
+    )
     serializer_class = serializers.SiteSerializer
     serializer_class = serializers.SiteSerializer
     filterset_class = filters.SiteFilterSet
     filterset_class = filters.SiteFilterSet
 
 
@@ -146,8 +146,8 @@ class RackGroupViewSet(ModelViewSet):
 
 
 class RackRoleViewSet(ModelViewSet):
 class RackRoleViewSet(ModelViewSet):
     queryset = RackRole.objects.annotate(
     queryset = RackRole.objects.annotate(
-        rack_count=Count('racks')
-    ).order_by(*RackRole._meta.ordering)
+        rack_count=get_subquery(Rack, 'role')
+    )
     serializer_class = serializers.RackRoleSerializer
     serializer_class = serializers.RackRoleSerializer
     filterset_class = filters.RackRoleFilterSet
     filterset_class = filters.RackRoleFilterSet
 
 
@@ -162,7 +162,7 @@ class RackViewSet(CustomFieldModelViewSet):
     ).annotate(
     ).annotate(
         device_count=get_subquery(Device, 'rack'),
         device_count=get_subquery(Device, 'rack'),
         powerfeed_count=get_subquery(PowerFeed, 'rack')
         powerfeed_count=get_subquery(PowerFeed, 'rack')
-    ).order_by(*Rack._meta.ordering)
+    )
     serializer_class = serializers.RackSerializer
     serializer_class = serializers.RackSerializer
     filterset_class = filters.RackFilterSet
     filterset_class = filters.RackFilterSet
 
 
@@ -237,7 +237,7 @@ class ManufacturerViewSet(ModelViewSet):
         devicetype_count=get_subquery(DeviceType, 'manufacturer'),
         devicetype_count=get_subquery(DeviceType, 'manufacturer'),
         inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
         inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
         platform_count=get_subquery(Platform, 'manufacturer')
         platform_count=get_subquery(Platform, 'manufacturer')
-    ).order_by(*Manufacturer._meta.ordering)
+    )
     serializer_class = serializers.ManufacturerSerializer
     serializer_class = serializers.ManufacturerSerializer
     filterset_class = filters.ManufacturerFilterSet
     filterset_class = filters.ManufacturerFilterSet
 
 
@@ -248,8 +248,8 @@ class ManufacturerViewSet(ModelViewSet):
 
 
 class DeviceTypeViewSet(CustomFieldModelViewSet):
 class DeviceTypeViewSet(CustomFieldModelViewSet):
     queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
-        device_count=Count('instances')
-    ).order_by(*DeviceType._meta.ordering)
+        device_count=get_subquery(Device, 'device_type')
+    )
     serializer_class = serializers.DeviceTypeSerializer
     serializer_class = serializers.DeviceTypeSerializer
     filterset_class = filters.DeviceTypeFilterSet
     filterset_class = filters.DeviceTypeFilterSet
 
 
@@ -314,7 +314,7 @@ class DeviceRoleViewSet(ModelViewSet):
     queryset = DeviceRole.objects.annotate(
     queryset = DeviceRole.objects.annotate(
         device_count=get_subquery(Device, 'device_role'),
         device_count=get_subquery(Device, 'device_role'),
         virtualmachine_count=get_subquery(VirtualMachine, 'role')
         virtualmachine_count=get_subquery(VirtualMachine, 'role')
-    ).order_by(*DeviceRole._meta.ordering)
+    )
     serializer_class = serializers.DeviceRoleSerializer
     serializer_class = serializers.DeviceRoleSerializer
     filterset_class = filters.DeviceRoleFilterSet
     filterset_class = filters.DeviceRoleFilterSet
 
 
@@ -327,7 +327,7 @@ class PlatformViewSet(ModelViewSet):
     queryset = Platform.objects.annotate(
     queryset = Platform.objects.annotate(
         device_count=get_subquery(Device, 'platform'),
         device_count=get_subquery(Device, 'platform'),
         virtualmachine_count=get_subquery(VirtualMachine, 'platform')
         virtualmachine_count=get_subquery(VirtualMachine, 'platform')
-    ).order_by(*Platform._meta.ordering)
+    )
     serializer_class = serializers.PlatformSerializer
     serializer_class = serializers.PlatformSerializer
     filterset_class = filters.PlatformFilterSet
     filterset_class = filters.PlatformFilterSet
 
 
@@ -619,8 +619,8 @@ class CableViewSet(ModelViewSet):
 
 
 class VirtualChassisViewSet(ModelViewSet):
 class VirtualChassisViewSet(ModelViewSet):
     queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
     queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
-        member_count=Count('members', distinct=True)
-    ).order_by(*VirtualChassis._meta.ordering)
+        member_count=get_subquery(Device, 'virtual_chassis')
+    )
     serializer_class = serializers.VirtualChassisSerializer
     serializer_class = serializers.VirtualChassisSerializer
     filterset_class = filters.VirtualChassisFilterSet
     filterset_class = filters.VirtualChassisFilterSet
 
 
@@ -633,8 +633,8 @@ class PowerPanelViewSet(ModelViewSet):
     queryset = PowerPanel.objects.prefetch_related(
     queryset = PowerPanel.objects.prefetch_related(
         'site', 'rack_group'
         'site', 'rack_group'
     ).annotate(
     ).annotate(
-        powerfeed_count=Count('powerfeeds')
-    ).order_by(*PowerPanel._meta.ordering)
+        powerfeed_count=get_subquery(PowerFeed, 'power_panel')
+    )
     serializer_class = serializers.PowerPanelSerializer
     serializer_class = serializers.PowerPanelSerializer
     filterset_class = filters.PowerPanelFilterSet
     filterset_class = filters.PowerPanelFilterSet
 
 

+ 23 - 19
netbox/dcim/views.py

@@ -4,7 +4,7 @@ from django.contrib import messages
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.db import transaction
 from django.db import transaction
-from django.db.models import Count, F, Prefetch
+from django.db.models import F, Prefetch
 from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
 from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.urls import reverse
@@ -263,7 +263,9 @@ class RackGroupBulkDeleteView(BulkDeleteView):
 #
 #
 
 
 class RackRoleListView(ObjectListView):
 class RackRoleListView(ObjectListView):
-    queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
+    queryset = RackRole.objects.annotate(
+        rack_count=get_subquery(Rack, 'role')
+    )
     table = tables.RackRoleTable
     table = tables.RackRoleTable
 
 
 
 
@@ -283,7 +285,9 @@ class RackRoleBulkImportView(BulkImportView):
 
 
 
 
 class RackRoleBulkDeleteView(BulkDeleteView):
 class RackRoleBulkDeleteView(BulkDeleteView):
-    queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
+    queryset = RackRole.objects.annotate(
+        rack_count=get_subquery(Rack, 'role')
+    )
     table = tables.RackRoleTable
     table = tables.RackRoleTable
 
 
 
 
@@ -295,8 +299,8 @@ class RackListView(ObjectListView):
     queryset = Rack.objects.prefetch_related(
     queryset = Rack.objects.prefetch_related(
         'site', 'group', 'tenant', 'role', 'devices__device_type'
         'site', 'group', 'tenant', 'role', 'devices__device_type'
     ).annotate(
     ).annotate(
-        device_count=Count('devices')
-    ).order_by(*Rack._meta.ordering)
+        device_count=get_subquery(Device, 'rack')
+    )
     filterset = filters.RackFilterSet
     filterset = filters.RackFilterSet
     filterset_form = forms.RackFilterForm
     filterset_form = forms.RackFilterForm
     table = tables.RackDetailTable
     table = tables.RackDetailTable
@@ -507,8 +511,8 @@ class ManufacturerBulkImportView(BulkImportView):
 
 
 class ManufacturerBulkDeleteView(BulkDeleteView):
 class ManufacturerBulkDeleteView(BulkDeleteView):
     queryset = Manufacturer.objects.annotate(
     queryset = Manufacturer.objects.annotate(
-        devicetype_count=Count('device_types')
-    ).order_by(*Manufacturer._meta.ordering)
+        devicetype_count=get_subquery(DeviceType, 'manufacturer')
+    )
     table = tables.ManufacturerTable
     table = tables.ManufacturerTable
 
 
 
 
@@ -518,8 +522,8 @@ class ManufacturerBulkDeleteView(BulkDeleteView):
 
 
 class DeviceTypeListView(ObjectListView):
 class DeviceTypeListView(ObjectListView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
-        instance_count=Count('instances')
-    ).order_by(*DeviceType._meta.ordering)
+        instance_count=get_subquery(Device, 'device_type')
+    )
     filterset = filters.DeviceTypeFilterSet
     filterset = filters.DeviceTypeFilterSet
     filterset_form = forms.DeviceTypeFilterForm
     filterset_form = forms.DeviceTypeFilterForm
     table = tables.DeviceTypeTable
     table = tables.DeviceTypeTable
@@ -628,8 +632,8 @@ class DeviceTypeImportView(ObjectImportView):
 
 
 class DeviceTypeBulkEditView(BulkEditView):
 class DeviceTypeBulkEditView(BulkEditView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
-        instance_count=Count('instances')
-    ).order_by(*DeviceType._meta.ordering)
+        instance_count=get_subquery(Device, 'device_type')
+    )
     filterset = filters.DeviceTypeFilterSet
     filterset = filters.DeviceTypeFilterSet
     table = tables.DeviceTypeTable
     table = tables.DeviceTypeTable
     form = forms.DeviceTypeBulkEditForm
     form = forms.DeviceTypeBulkEditForm
@@ -637,8 +641,8 @@ class DeviceTypeBulkEditView(BulkEditView):
 
 
 class DeviceTypeBulkDeleteView(BulkDeleteView):
 class DeviceTypeBulkDeleteView(BulkDeleteView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
-        instance_count=Count('instances')
-    ).order_by(*DeviceType._meta.ordering)
+        instance_count=get_subquery(Device, 'device_type')
+    )
     filterset = filters.DeviceTypeFilterSet
     filterset = filters.DeviceTypeFilterSet
     table = tables.DeviceTypeTable
     table = tables.DeviceTypeTable
 
 
@@ -2198,8 +2202,8 @@ class InterfaceConnectionsListView(ObjectListView):
 
 
 class VirtualChassisListView(ObjectListView):
 class VirtualChassisListView(ObjectListView):
     queryset = VirtualChassis.objects.prefetch_related('master').annotate(
     queryset = VirtualChassis.objects.prefetch_related('master').annotate(
-        member_count=Count('members', distinct=True)
-    ).order_by(*VirtualChassis._meta.ordering)
+        member_count=get_subquery(Device, 'virtual_chassis')
+    )
     table = tables.VirtualChassisTable
     table = tables.VirtualChassisTable
     filterset = filters.VirtualChassisFilterSet
     filterset = filters.VirtualChassisFilterSet
     filterset_form = forms.VirtualChassisFilterForm
     filterset_form = forms.VirtualChassisFilterForm
@@ -2430,8 +2434,8 @@ class PowerPanelListView(ObjectListView):
     queryset = PowerPanel.objects.prefetch_related(
     queryset = PowerPanel.objects.prefetch_related(
         'site', 'rack_group'
         'site', 'rack_group'
     ).annotate(
     ).annotate(
-        powerfeed_count=Count('powerfeeds')
-    ).order_by(*PowerPanel._meta.ordering)
+        powerfeed_count=get_subquery(PowerFeed, 'power_panel')
+    )
     filterset = filters.PowerPanelFilterSet
     filterset = filters.PowerPanelFilterSet
     filterset_form = forms.PowerPanelFilterForm
     filterset_form = forms.PowerPanelFilterForm
     table = tables.PowerPanelTable
     table = tables.PowerPanelTable
@@ -2482,8 +2486,8 @@ class PowerPanelBulkDeleteView(BulkDeleteView):
     queryset = PowerPanel.objects.prefetch_related(
     queryset = PowerPanel.objects.prefetch_related(
         'site', 'rack_group'
         'site', 'rack_group'
     ).annotate(
     ).annotate(
-        rack_count=Count('powerfeeds')
-    ).order_by(*PowerPanel._meta.ordering)
+        powerfeed_count=get_subquery(PowerFeed, 'power_panel')
+    )
     filterset = filters.PowerPanelFilterSet
     filterset = filters.PowerPanelFilterSet
     table = tables.PowerPanelTable
     table = tables.PowerPanelTable
 
 

+ 4 - 5
netbox/extras/api/views.py

@@ -1,7 +1,6 @@
 from collections import OrderedDict
 from collections import OrderedDict
 
 
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.db.models import Count
 from django.http import Http404
 from django.http import Http404
 from django_rq.queues import get_connection
 from django_rq.queues import get_connection
 from rest_framework import status
 from rest_framework import status
@@ -15,14 +14,14 @@ from rq import Worker
 from extras import filters
 from extras import filters
 from extras.choices import JobResultStatusChoices
 from extras.choices import JobResultStatusChoices
 from extras.models import (
 from extras.models import (
-    ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, JobResult, Tag,
+    ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem,
 )
 )
 from extras.reports import get_report, get_reports, run_report
 from extras.reports import get_report, get_reports, run_report
 from extras.scripts import get_script, get_scripts, run_script
 from extras.scripts import get_script, get_scripts, run_script
 from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
 from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
 from utilities.exceptions import RQWorkerNotRunningException
 from utilities.exceptions import RQWorkerNotRunningException
 from utilities.metadata import ContentTypeMetadata
 from utilities.metadata import ContentTypeMetadata
-from utilities.utils import copy_safe_request
+from utilities.utils import copy_safe_request, get_subquery
 from . import serializers
 from . import serializers
 
 
 
 
@@ -149,8 +148,8 @@ class ExportTemplateViewSet(ModelViewSet):
 
 
 class TagViewSet(ModelViewSet):
 class TagViewSet(ModelViewSet):
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
-        tagged_items=Count('extras_taggeditem_items')
-    ).order_by(*Tag._meta.ordering)
+        tagged_items=get_subquery(TaggedItem, 'tag')
+    )
     serializer_class = serializers.TagSerializer
     serializer_class = serializers.TagSerializer
     filterset_class = filters.TagFilterSet
     filterset_class = filters.TagFilterSet
 
 

+ 9 - 9
netbox/extras/views.py

@@ -1,7 +1,7 @@
 from django import template
 from django import template
 from django.contrib import messages
 from django.contrib import messages
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.db.models import Count, Prefetch, Q
+from django.db.models import Prefetch, Q
 from django.http import Http404, HttpResponseForbidden
 from django.http import Http404, HttpResponseForbidden
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.views.generic import View
 from django.views.generic import View
@@ -13,7 +13,7 @@ from dcim.models import DeviceRole, Platform, Region, Site
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import ConfirmationForm
 from utilities.forms import ConfirmationForm
 from utilities.paginator import EnhancedPaginator, get_paginate_count
 from utilities.paginator import EnhancedPaginator, get_paginate_count
-from utilities.utils import copy_safe_request, shallow_compare_dict
+from utilities.utils import copy_safe_request, get_subquery, shallow_compare_dict
 from utilities.views import (
 from utilities.views import (
     BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
     BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
     ContentTypePermissionRequiredMixin,
     ContentTypePermissionRequiredMixin,
@@ -21,7 +21,7 @@ from utilities.views import (
 from virtualization.models import Cluster, ClusterGroup
 from virtualization.models import Cluster, ClusterGroup
 from . import filters, forms, tables
 from . import filters, forms, tables
 from .choices import JobResultStatusChoices
 from .choices import JobResultStatusChoices
-from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag
+from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem
 from .reports import get_report, get_reports, run_report
 from .reports import get_report, get_reports, run_report
 from .scripts import get_scripts, run_script
 from .scripts import get_scripts, run_script
 
 
@@ -32,8 +32,8 @@ from .scripts import get_scripts, run_script
 
 
 class TagListView(ObjectListView):
 class TagListView(ObjectListView):
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
-        items=Count('extras_taggeditem_items')
-    ).order_by(*Tag._meta.ordering)
+        items=get_subquery(TaggedItem, 'tag')
+    )
     filterset = filters.TagFilterSet
     filterset = filters.TagFilterSet
     filterset_form = forms.TagFilterForm
     filterset_form = forms.TagFilterForm
     table = tables.TagTable
     table = tables.TagTable
@@ -57,16 +57,16 @@ class TagBulkImportView(BulkImportView):
 
 
 class TagBulkEditView(BulkEditView):
 class TagBulkEditView(BulkEditView):
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
-        items=Count('extras_taggeditem_items')
-    ).order_by(*Tag._meta.ordering)
+        items=get_subquery(TaggedItem, 'tag')
+    )
     table = tables.TagTable
     table = tables.TagTable
     form = forms.TagBulkEditForm
     form = forms.TagBulkEditForm
 
 
 
 
 class TagBulkDeleteView(BulkDeleteView):
 class TagBulkDeleteView(BulkDeleteView):
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
-        items=Count('extras_taggeditem_items')
-    ).order_by(*Tag._meta.ordering)
+        items=get_subquery(TaggedItem, 'tag')
+    )
     table = tables.TagTable
     table = tables.TagTable
 
 
 
 

+ 9 - 10
netbox/ipam/api/views.py

@@ -1,5 +1,4 @@
 from django.conf import settings
 from django.conf import settings
-from django.db.models import Count
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from django_pglocks import advisory_lock
 from django_pglocks import advisory_lock
 from drf_yasg.utils import swagger_auto_schema
 from drf_yasg.utils import swagger_auto_schema
@@ -33,7 +32,7 @@ class VRFViewSet(CustomFieldModelViewSet):
     queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').annotate(
     queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').annotate(
         ipaddress_count=get_subquery(IPAddress, 'vrf'),
         ipaddress_count=get_subquery(IPAddress, 'vrf'),
         prefix_count=get_subquery(Prefix, 'vrf')
         prefix_count=get_subquery(Prefix, 'vrf')
-    ).order_by(*VRF._meta.ordering)
+    )
     serializer_class = serializers.VRFSerializer
     serializer_class = serializers.VRFSerializer
     filterset_class = filters.VRFFilterSet
     filterset_class = filters.VRFFilterSet
 
 
@@ -44,8 +43,8 @@ class VRFViewSet(CustomFieldModelViewSet):
 
 
 class RIRViewSet(ModelViewSet):
 class RIRViewSet(ModelViewSet):
     queryset = RIR.objects.annotate(
     queryset = RIR.objects.annotate(
-        aggregate_count=Count('aggregates')
-    ).order_by(*RIR._meta.ordering)
+        aggregate_count=get_subquery(Aggregate, 'rir')
+    )
     serializer_class = serializers.RIRSerializer
     serializer_class = serializers.RIRSerializer
     filterset_class = filters.RIRFilterSet
     filterset_class = filters.RIRFilterSet
 
 
@@ -68,7 +67,7 @@ class RoleViewSet(ModelViewSet):
     queryset = Role.objects.annotate(
     queryset = Role.objects.annotate(
         prefix_count=get_subquery(Prefix, 'role'),
         prefix_count=get_subquery(Prefix, 'role'),
         vlan_count=get_subquery(VLAN, 'role')
         vlan_count=get_subquery(VLAN, 'role')
-    ).order_by(*Role._meta.ordering)
+    )
     serializer_class = serializers.RoleSerializer
     serializer_class = serializers.RoleSerializer
     filterset_class = filters.RoleFilterSet
     filterset_class = filters.RoleFilterSet
 
 
@@ -80,7 +79,7 @@ class RoleViewSet(ModelViewSet):
 class PrefixViewSet(CustomFieldModelViewSet):
 class PrefixViewSet(CustomFieldModelViewSet):
     queryset = Prefix.objects.prefetch_related(
     queryset = Prefix.objects.prefetch_related(
         'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
         'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
-    ).order_by(*Prefix._meta.ordering)
+    )
     serializer_class = serializers.PrefixSerializer
     serializer_class = serializers.PrefixSerializer
     filterset_class = filters.PrefixFilterSet
     filterset_class = filters.PrefixFilterSet
 
 
@@ -250,7 +249,7 @@ class PrefixViewSet(CustomFieldModelViewSet):
 class IPAddressViewSet(CustomFieldModelViewSet):
 class IPAddressViewSet(CustomFieldModelViewSet):
     queryset = IPAddress.objects.prefetch_related(
     queryset = IPAddress.objects.prefetch_related(
         'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
         'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
-    ).order_by(*IPAddress._meta.ordering)
+    )
     serializer_class = serializers.IPAddressSerializer
     serializer_class = serializers.IPAddressSerializer
     filterset_class = filters.IPAddressFilterSet
     filterset_class = filters.IPAddressFilterSet
 
 
@@ -261,8 +260,8 @@ class IPAddressViewSet(CustomFieldModelViewSet):
 
 
 class VLANGroupViewSet(ModelViewSet):
 class VLANGroupViewSet(ModelViewSet):
     queryset = VLANGroup.objects.prefetch_related('site').annotate(
     queryset = VLANGroup.objects.prefetch_related('site').annotate(
-        vlan_count=Count('vlans')
-    ).order_by(*VLANGroup._meta.ordering)
+        vlan_count=get_subquery(VLAN, 'group')
+    )
     serializer_class = serializers.VLANGroupSerializer
     serializer_class = serializers.VLANGroupSerializer
     filterset_class = filters.VLANGroupFilterSet
     filterset_class = filters.VLANGroupFilterSet
 
 
@@ -276,7 +275,7 @@ class VLANViewSet(CustomFieldModelViewSet):
         'site', 'group', 'tenant', 'role', 'tags'
         'site', 'group', 'tenant', 'role', 'tags'
     ).annotate(
     ).annotate(
         prefix_count=get_subquery(Prefix, 'vlan')
         prefix_count=get_subquery(Prefix, 'vlan')
-    ).order_by(*VLAN._meta.ordering)
+    )
     serializer_class = serializers.VLANSerializer
     serializer_class = serializers.VLANSerializer
     filterset_class = filters.VLANFilterSet
     filterset_class = filters.VLANFilterSet
 
 

+ 12 - 9
netbox/ipam/views.py

@@ -1,6 +1,5 @@
 import netaddr
 import netaddr
-from django.conf import settings
-from django.db.models import Count, Prefetch
+from django.db.models import Prefetch
 from django.db.models.expressions import RawSQL
 from django.db.models.expressions import RawSQL
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django_tables2 import RequestConfig
 from django_tables2 import RequestConfig
@@ -79,7 +78,9 @@ class VRFBulkDeleteView(BulkDeleteView):
 #
 #
 
 
 class RIRListView(ObjectListView):
 class RIRListView(ObjectListView):
-    queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
+    queryset = RIR.objects.annotate(
+        aggregate_count=get_subquery(Aggregate, 'rir')
+    )
     filterset = filters.RIRFilterSet
     filterset = filters.RIRFilterSet
     filterset_form = forms.RIRFilterForm
     filterset_form = forms.RIRFilterForm
     table = tables.RIRDetailTable
     table = tables.RIRDetailTable
@@ -172,7 +173,9 @@ class RIRBulkImportView(BulkImportView):
 
 
 
 
 class RIRBulkDeleteView(BulkDeleteView):
 class RIRBulkDeleteView(BulkDeleteView):
-    queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
+    queryset = RIR.objects.annotate(
+        aggregate_count=get_subquery(Aggregate, 'rir')
+    )
     filterset = filters.RIRFilterSet
     filterset = filters.RIRFilterSet
     table = tables.RIRTable
     table = tables.RIRTable
 
 
@@ -184,7 +187,7 @@ class RIRBulkDeleteView(BulkDeleteView):
 class AggregateListView(ObjectListView):
 class AggregateListView(ObjectListView):
     queryset = Aggregate.objects.prefetch_related('rir').annotate(
     queryset = Aggregate.objects.prefetch_related('rir').annotate(
         child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
         child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
-    ).order_by(*Aggregate._meta.ordering)
+    )
     filterset = filters.AggregateFilterSet
     filterset = filters.AggregateFilterSet
     filterset_form = forms.AggregateFilterForm
     filterset_form = forms.AggregateFilterForm
     table = tables.AggregateDetailTable
     table = tables.AggregateDetailTable
@@ -652,8 +655,8 @@ class IPAddressBulkDeleteView(BulkDeleteView):
 
 
 class VLANGroupListView(ObjectListView):
 class VLANGroupListView(ObjectListView):
     queryset = VLANGroup.objects.prefetch_related('site').annotate(
     queryset = VLANGroup.objects.prefetch_related('site').annotate(
-        vlan_count=Count('vlans')
-    ).order_by(*VLANGroup._meta.ordering)
+        vlan_count=get_subquery(VLAN, 'group')
+    )
     filterset = filters.VLANGroupFilterSet
     filterset = filters.VLANGroupFilterSet
     filterset_form = forms.VLANGroupFilterForm
     filterset_form = forms.VLANGroupFilterForm
     table = tables.VLANGroupTable
     table = tables.VLANGroupTable
@@ -676,8 +679,8 @@ class VLANGroupBulkImportView(BulkImportView):
 
 
 class VLANGroupBulkDeleteView(BulkDeleteView):
 class VLANGroupBulkDeleteView(BulkDeleteView):
     queryset = VLANGroup.objects.prefetch_related('site').annotate(
     queryset = VLANGroup.objects.prefetch_related('site').annotate(
-        vlan_count=Count('vlans')
-    ).order_by(*VLANGroup._meta.ordering)
+        vlan_count=get_subquery(VLAN, 'group')
+    )
     filterset = filters.VLANGroupFilterSet
     filterset = filters.VLANGroupFilterSet
     table = tables.VLANGroupTable
     table = tables.VLANGroupTable
 
 

+ 13 - 9
netbox/netbox/views.py

@@ -48,8 +48,8 @@ SEARCH_TYPES = OrderedDict((
     # Circuits
     # Circuits
     ('provider', {
     ('provider', {
         'queryset': Provider.objects.annotate(
         'queryset': Provider.objects.annotate(
-            count_circuits=Count('circuits')
-        ).order_by(*Provider._meta.ordering),
+            count_circuits=get_subquery(Circuit, 'provider')
+        ),
         'filterset': ProviderFilterSet,
         'filterset': ProviderFilterSet,
         'table': ProviderTable,
         'table': ProviderTable,
         'url': 'circuits:provider_list',
         'url': 'circuits:provider_list',
@@ -76,17 +76,21 @@ SEARCH_TYPES = OrderedDict((
         'url': 'dcim:rack_list',
         'url': 'dcim:rack_list',
     }),
     }),
     ('rackgroup', {
     ('rackgroup', {
-        'queryset': RackGroup.objects.prefetch_related('site').annotate(
-            rack_count=Count('racks')
-        ).order_by(*RackGroup._meta.ordering),
+        'queryset': RackGroup.objects.add_related_count(
+            RackGroup.objects.all(),
+            Rack,
+            'group',
+            'rack_count',
+            cumulative=True
+        ).prefetch_related('site'),
         'filterset': RackGroupFilterSet,
         'filterset': RackGroupFilterSet,
         'table': RackGroupTable,
         'table': RackGroupTable,
         'url': 'dcim:rackgroup_list',
         'url': 'dcim:rackgroup_list',
     }),
     }),
     ('devicetype', {
     ('devicetype', {
         'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
         'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
-            instance_count=Count('instances')
-        ).order_by(*DeviceType._meta.ordering),
+            instance_count=get_subquery(Device, 'device_type')
+        ),
         'filterset': DeviceTypeFilterSet,
         'filterset': DeviceTypeFilterSet,
         'table': DeviceTypeTable,
         'table': DeviceTypeTable,
         'url': 'dcim:devicetype_list',
         'url': 'dcim:devicetype_list',
@@ -101,8 +105,8 @@ SEARCH_TYPES = OrderedDict((
     }),
     }),
     ('virtualchassis', {
     ('virtualchassis', {
         'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
         'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
-            member_count=Count('members', distinct=True)
-        ).order_by(*VirtualChassis._meta.ordering),
+            member_count=get_subquery(Device, 'virtual_chassis')
+        ),
         'filterset': VirtualChassisFilterSet,
         'filterset': VirtualChassisFilterSet,
         'table': VirtualChassisTable,
         'table': VirtualChassisTable,
         'url': 'dcim:virtualchassis_list',
         'url': 'dcim:virtualchassis_list',

+ 3 - 3
netbox/secrets/api/views.py

@@ -1,7 +1,6 @@
 import base64
 import base64
 
 
 from Crypto.PublicKey import RSA
 from Crypto.PublicKey import RSA
-from django.db.models import Count
 from django.http import HttpResponseBadRequest
 from django.http import HttpResponseBadRequest
 from rest_framework.exceptions import ValidationError
 from rest_framework.exceptions import ValidationError
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.permissions import IsAuthenticated
@@ -13,6 +12,7 @@ from secrets import filters
 from secrets.exceptions import InvalidKey
 from secrets.exceptions import InvalidKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from utilities.api import ModelViewSet
 from utilities.api import ModelViewSet
+from utilities.utils import get_subquery
 from . import serializers
 from . import serializers
 
 
 ERR_USERKEY_MISSING = "No UserKey found for the current user."
 ERR_USERKEY_MISSING = "No UserKey found for the current user."
@@ -35,8 +35,8 @@ class SecretsRootView(APIRootView):
 
 
 class SecretRoleViewSet(ModelViewSet):
 class SecretRoleViewSet(ModelViewSet):
     queryset = SecretRole.objects.annotate(
     queryset = SecretRole.objects.annotate(
-        secret_count=Count('secrets')
-    ).order_by(*SecretRole._meta.ordering)
+        secret_count=get_subquery(Secret, 'role')
+    )
     serializer_class = serializers.SecretRoleSerializer
     serializer_class = serializers.SecretRoleSerializer
     filterset_class = filters.SecretRoleFilterSet
     filterset_class = filters.SecretRoleFilterSet
 
 

+ 7 - 3
netbox/secrets/views.py

@@ -2,11 +2,11 @@ import base64
 import logging
 import logging
 
 
 from django.contrib import messages
 from django.contrib import messages
-from django.db.models import Count
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.html import escape
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 
 
+from utilities.utils import get_subquery
 from utilities.views import (
 from utilities.views import (
     BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
     BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 )
@@ -29,7 +29,9 @@ def get_session_key(request):
 #
 #
 
 
 class SecretRoleListView(ObjectListView):
 class SecretRoleListView(ObjectListView):
-    queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
+    queryset = SecretRole.objects.annotate(
+        secret_count=get_subquery(Secret, 'role')
+    )
     table = tables.SecretRoleTable
     table = tables.SecretRoleTable
 
 
 
 
@@ -49,7 +51,9 @@ class SecretRoleBulkImportView(BulkImportView):
 
 
 
 
 class SecretRoleBulkDeleteView(BulkDeleteView):
 class SecretRoleBulkDeleteView(BulkDeleteView):
-    queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
+    queryset = SecretRole.objects.annotate(
+        secret_count=get_subquery(Secret, 'role')
+    )
     table = tables.SecretRoleTable
     table = tables.SecretRoleTable
 
 
 
 

+ 5 - 6
netbox/virtualization/api/views.py

@@ -1,4 +1,3 @@
-from django.db.models import Count
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from rest_framework.decorators import action
 from rest_framework.decorators import action
 from rest_framework.response import Response
 from rest_framework.response import Response
@@ -29,16 +28,16 @@ class VirtualizationRootView(APIRootView):
 
 
 class ClusterTypeViewSet(ModelViewSet):
 class ClusterTypeViewSet(ModelViewSet):
     queryset = ClusterType.objects.annotate(
     queryset = ClusterType.objects.annotate(
-        cluster_count=Count('clusters')
-    ).order_by(*ClusterType._meta.ordering)
+        cluster_count=get_subquery(Cluster, 'type')
+    )
     serializer_class = serializers.ClusterTypeSerializer
     serializer_class = serializers.ClusterTypeSerializer
     filterset_class = filters.ClusterTypeFilterSet
     filterset_class = filters.ClusterTypeFilterSet
 
 
 
 
 class ClusterGroupViewSet(ModelViewSet):
 class ClusterGroupViewSet(ModelViewSet):
     queryset = ClusterGroup.objects.annotate(
     queryset = ClusterGroup.objects.annotate(
-        cluster_count=Count('clusters')
-    ).order_by(*ClusterGroup._meta.ordering)
+        cluster_count=get_subquery(Cluster, 'group')
+    )
     serializer_class = serializers.ClusterGroupSerializer
     serializer_class = serializers.ClusterGroupSerializer
     filterset_class = filters.ClusterGroupFilterSet
     filterset_class = filters.ClusterGroupFilterSet
 
 
@@ -49,7 +48,7 @@ class ClusterViewSet(CustomFieldModelViewSet):
     ).annotate(
     ).annotate(
         device_count=get_subquery(Device, 'cluster'),
         device_count=get_subquery(Device, 'cluster'),
         virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
         virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
-    ).order_by(*Cluster._meta.ordering)
+    )
     serializer_class = serializers.ClusterSerializer
     serializer_class = serializers.ClusterSerializer
     filterset_class = filters.ClusterFilterSet
     filterset_class = filters.ClusterFilterSet
 
 

+ 13 - 5
netbox/virtualization/views.py

@@ -1,6 +1,6 @@
 from django.contrib import messages
 from django.contrib import messages
 from django.db import transaction
 from django.db import transaction
-from django.db.models import Count, Prefetch
+from django.db.models import Prefetch
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.urls import reverse
 
 
@@ -23,7 +23,9 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterf
 #
 #
 
 
 class ClusterTypeListView(ObjectListView):
 class ClusterTypeListView(ObjectListView):
-    queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
+    queryset = ClusterType.objects.annotate(
+        cluster_count=get_subquery(Cluster, 'type')
+    )
     table = tables.ClusterTypeTable
     table = tables.ClusterTypeTable
 
 
 
 
@@ -43,7 +45,9 @@ class ClusterTypeBulkImportView(BulkImportView):
 
 
 
 
 class ClusterTypeBulkDeleteView(BulkDeleteView):
 class ClusterTypeBulkDeleteView(BulkDeleteView):
-    queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterType._meta.ordering)
+    queryset = ClusterType.objects.annotate(
+        cluster_count=get_subquery(Cluster, 'type')
+    )
     table = tables.ClusterTypeTable
     table = tables.ClusterTypeTable
 
 
 
 
@@ -52,7 +56,9 @@ class ClusterTypeBulkDeleteView(BulkDeleteView):
 #
 #
 
 
 class ClusterGroupListView(ObjectListView):
 class ClusterGroupListView(ObjectListView):
-    queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
+    queryset = ClusterGroup.objects.annotate(
+        cluster_count=get_subquery(Cluster, 'group')
+    )
     table = tables.ClusterGroupTable
     table = tables.ClusterGroupTable
 
 
 
 
@@ -72,7 +78,9 @@ class ClusterGroupBulkImportView(BulkImportView):
 
 
 
 
 class ClusterGroupBulkDeleteView(BulkDeleteView):
 class ClusterGroupBulkDeleteView(BulkDeleteView):
-    queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')).order_by(*ClusterGroup._meta.ordering)
+    queryset = ClusterGroup.objects.annotate(
+        cluster_count=get_subquery(Cluster, 'group')
+    )
     table = tables.ClusterGroupTable
     table = tables.ClusterGroupTable