From f4bbdf30e86bed4f2ffd93cf8460e56abcb47c84 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Apr 2019 16:09:22 -0400 Subject: [PATCH] Implement get_subquery() for annotation of child object counts; Rename dcim.Site 'count_*' fields --- CHANGELOG.md | 2 ++ netbox/dcim/api/serializers.py | 15 +++++++------- netbox/dcim/api/views.py | 36 ++++++++++++++++++---------------- netbox/dcim/models.py | 26 ------------------------ netbox/ipam/api/views.py | 11 +++-------- netbox/utilities/utils.py | 18 +++++++++++++++++ 6 files changed, 50 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5b402bd..9dd560861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -155,6 +155,8 @@ functionality provided by the front end UI. * dcim.DeviceType: `instance_count` has been renamed to `device_count`. * dcim.Interface: `form_factor` has been renamed to `type`. Backward compatibility for `form_factor` will be maintained until NetBox v2.7. * dcim.Interface: The `type` filter has been renamed to `kind`. +* dcim.Site: The `count_*` read-only fields have been renamed to `*_count` for consistency with other objects. +* dcim.Site: Added the `virtualmachine_count` read-only field. * extras.Tag: Added `color` and `comments` fields to the Tag serializer. * virtualization.VirtualMachine: The virtual machines list endpoint now includes rendered context data. diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 46d46b0a1..033dd3188 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -72,19 +72,20 @@ class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer): tenant = NestedTenantSerializer(required=False, allow_null=True) time_zone = TimeZoneField(required=False) tags = TagListSerializerField(required=False) - count_prefixes = serializers.IntegerField(read_only=True) - count_vlans = serializers.IntegerField(read_only=True) - count_racks = serializers.IntegerField(read_only=True) - count_devices = serializers.IntegerField(read_only=True) - count_circuits = serializers.IntegerField(read_only=True) + prefix_count = serializers.IntegerField(read_only=True) + vlan_count = serializers.IntegerField(read_only=True) + rack_count = serializers.IntegerField(read_only=True) + device_count = serializers.IntegerField(read_only=True) + circuit_count = serializers.IntegerField(read_only=True) + virtualmachine_count = serializers.IntegerField(read_only=True) class Meta: model = Site fields = [ 'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', - 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'count_prefixes', - 'count_vlans', 'count_racks', 'count_devices', 'count_circuits', + 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count', + 'vlan_count', 'rack_count', 'device_count', 'circuit_count', 'virtualmachine_count', ] diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index c46ffc02a..16373c83f 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -12,6 +12,7 @@ from rest_framework.mixins import ListModelMixin from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet, ViewSet +from circuits.models import Circuit from dcim import filters from dcim.models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, @@ -23,9 +24,11 @@ from dcim.models import ( from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE +from ipam.models import Prefix, VLAN from utilities.api import ( get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable, ) +from utilities.utils import get_subquery from virtualization.models import VirtualMachine from . import serializers from .exceptions import MissingFilterException @@ -106,7 +109,18 @@ class RegionViewSet(ModelViewSet): # class SiteViewSet(CustomFieldModelViewSet): - queryset = Site.objects.select_related('region', 'tenant').prefetch_related('tags') + queryset = Site.objects.select_related( + 'region', 'tenant' + ).prefetch_related( + 'tags' + ).annotate( + device_count=get_subquery(Device, 'site'), + rack_count=get_subquery(Rack, 'site'), + prefix_count=get_subquery(Prefix, 'site'), + vlan_count=get_subquery(VLAN, 'site'), + circuit_count=get_subquery(Circuit, 'terminations__site'), + virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'), + ) serializer_class = serializers.SiteSerializer filterset_class = filters.SiteFilter @@ -281,15 +295,9 @@ class DeviceBayTemplateViewSet(ModelViewSet): # class DeviceRoleViewSet(ModelViewSet): - device_count = Device.objects.filter( - device_role=OuterRef('pk') - ).order_by().values('device_role').annotate(c=Count('*')).values('c') - virtualmachine_count = VirtualMachine.objects.filter( - role=OuterRef('pk') - ).order_by().values('role').annotate(c=Count('*')).values('c') queryset = DeviceRole.objects.annotate( - device_count=Subquery(device_count), - virtualmachine_count=Subquery(virtualmachine_count) + device_count=get_subquery(Device, 'device_role'), + virtualmachine_count=get_subquery(VirtualMachine, 'role') ) serializer_class = serializers.DeviceRoleSerializer filterset_class = filters.DeviceRoleFilter @@ -300,15 +308,9 @@ class DeviceRoleViewSet(ModelViewSet): # class PlatformViewSet(ModelViewSet): - device_count = Device.objects.filter( - platform=OuterRef('pk') - ).order_by().values('platform').annotate(c=Count('*')).values('c') - virtualmachine_count = VirtualMachine.objects.filter( - platform=OuterRef('pk') - ).order_by().values('platform').annotate(c=Count('*')).values('c') queryset = Platform.objects.annotate( - device_count=Subquery(device_count), - virtualmachine_count=Subquery(virtualmachine_count) + device_count=get_subquery(Device, 'platform'), + virtualmachine_count=get_subquery(VirtualMachine, 'platform') ) serializer_class = serializers.PlatformSerializer filterset_class = filters.PlatformFilter diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index d1eb0d98d..ae76e369c 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -363,32 +363,6 @@ class Site(ChangeLoggedModel, CustomFieldModel): def get_status_class(self): return STATUS_CLASSES[self.status] - @property - def count_prefixes(self): - return self.prefixes.count() - - @property - def count_vlans(self): - return self.vlans.count() - - @property - def count_racks(self): - return Rack.objects.filter(site=self).count() - - @property - def count_devices(self): - return Device.objects.filter(site=self).count() - - @property - def count_circuits(self): - from circuits.models import Circuit - return Circuit.objects.filter(terminations__site=self).count() - - @property - def count_vms(self): - from virtualization.models import VirtualMachine - return VirtualMachine.objects.filter(cluster__site=self).count() - # # Racks diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 962ce0e84..7ba319cbb 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -10,6 +10,7 @@ from extras.api.views import CustomFieldModelViewSet from ipam import filters from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.utils import get_subquery from . import serializers @@ -66,15 +67,9 @@ class AggregateViewSet(CustomFieldModelViewSet): # class RoleViewSet(ModelViewSet): - prefix_count = Prefix.objects.filter( - role=OuterRef('pk') - ).order_by().values('role').annotate(c=Count('*')).values('c') - vlan_count = VLAN.objects.filter( - role=OuterRef('pk') - ).order_by().values('role').annotate(c=Count('*')).values('c') queryset = Role.objects.annotate( - prefix_count=Subquery(prefix_count), - vlan_count=Subquery(vlan_count) + prefix_count=get_subquery(Prefix, 'role'), + vlan_count=get_subquery(VLAN, 'role') ) serializer_class = serializers.RoleSerializer filterset_class = filters.RoleFilter diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index ff8ec6297..4644c432e 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -4,6 +4,7 @@ import datetime import json from django.core.serializers import serialize +from django.db.models import Count, OuterRef, Subquery from dcim.constants import LENGTH_UNIT_CENTIMETER, LENGTH_UNIT_FOOT, LENGTH_UNIT_INCH, LENGTH_UNIT_METER @@ -71,6 +72,23 @@ def model_names_to_filter_dict(names): } +def get_subquery(model, field): + """ + Return a Subquery suitable for annotating a child object count. + """ + subquery = Subquery( + model.objects.filter( + **{field: OuterRef('pk')} + ).order_by().values( + field + ).annotate( + c=Count('*') + ).values('c') + ) + + return subquery + + def serialize_object(obj, extra=None): """ Return a generic JSON representation of an object using Django's built-in serializer. (This is used for things like