Implement get_subquery() for annotation of child object counts; Rename dcim.Site 'count_*' fields

This commit is contained in:
Jeremy Stretch 2019-04-19 16:09:22 -04:00
parent 7d41a9ccdb
commit f4bbdf30e8
6 changed files with 50 additions and 58 deletions

View File

@ -155,6 +155,8 @@ functionality provided by the front end UI.
* dcim.DeviceType: `instance_count` has been renamed to `device_count`. * 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: `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.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. * extras.Tag: Added `color` and `comments` fields to the Tag serializer.
* virtualization.VirtualMachine: The virtual machines list endpoint now includes rendered context data. * virtualization.VirtualMachine: The virtual machines list endpoint now includes rendered context data.

View File

@ -72,19 +72,20 @@ class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
time_zone = TimeZoneField(required=False) time_zone = TimeZoneField(required=False)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
count_prefixes = serializers.IntegerField(read_only=True) prefix_count = serializers.IntegerField(read_only=True)
count_vlans = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True)
count_racks = serializers.IntegerField(read_only=True) rack_count = serializers.IntegerField(read_only=True)
count_devices = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
count_circuits = serializers.IntegerField(read_only=True) circuit_count = serializers.IntegerField(read_only=True)
virtualmachine_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = Site model = Site
fields = [ fields = [
'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'count_prefixes', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count',
'count_vlans', 'count_racks', 'count_devices', 'count_circuits', 'vlan_count', 'rack_count', 'device_count', 'circuit_count', 'virtualmachine_count',
] ]

View File

@ -12,6 +12,7 @@ from rest_framework.mixins import ListModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ViewSet from rest_framework.viewsets import GenericViewSet, ViewSet
from circuits.models import Circuit
from dcim import filters from dcim import filters
from dcim.models import ( from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@ -23,9 +24,11 @@ from dcim.models import (
from extras.api.serializers import RenderedGraphSerializer from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from ipam.models import Prefix, VLAN
from utilities.api import ( from utilities.api import (
get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable, get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable,
) )
from utilities.utils import get_subquery
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from . import serializers from . import serializers
from .exceptions import MissingFilterException from .exceptions import MissingFilterException
@ -106,7 +109,18 @@ class RegionViewSet(ModelViewSet):
# #
class SiteViewSet(CustomFieldModelViewSet): 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 serializer_class = serializers.SiteSerializer
filterset_class = filters.SiteFilter filterset_class = filters.SiteFilter
@ -281,15 +295,9 @@ class DeviceBayTemplateViewSet(ModelViewSet):
# #
class DeviceRoleViewSet(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( queryset = DeviceRole.objects.annotate(
device_count=Subquery(device_count), device_count=get_subquery(Device, 'device_role'),
virtualmachine_count=Subquery(virtualmachine_count) virtualmachine_count=get_subquery(VirtualMachine, 'role')
) )
serializer_class = serializers.DeviceRoleSerializer serializer_class = serializers.DeviceRoleSerializer
filterset_class = filters.DeviceRoleFilter filterset_class = filters.DeviceRoleFilter
@ -300,15 +308,9 @@ class DeviceRoleViewSet(ModelViewSet):
# #
class PlatformViewSet(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( queryset = Platform.objects.annotate(
device_count=Subquery(device_count), device_count=get_subquery(Device, 'platform'),
virtualmachine_count=Subquery(virtualmachine_count) virtualmachine_count=get_subquery(VirtualMachine, 'platform')
) )
serializer_class = serializers.PlatformSerializer serializer_class = serializers.PlatformSerializer
filterset_class = filters.PlatformFilter filterset_class = filters.PlatformFilter

View File

@ -363,32 +363,6 @@ class Site(ChangeLoggedModel, CustomFieldModel):
def get_status_class(self): def get_status_class(self):
return STATUS_CLASSES[self.status] 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 # Racks

View File

@ -10,6 +10,7 @@ from extras.api.views import CustomFieldModelViewSet
from ipam import filters from ipam import filters
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from utilities.api import FieldChoicesViewSet, ModelViewSet from utilities.api import FieldChoicesViewSet, ModelViewSet
from utilities.utils import get_subquery
from . import serializers from . import serializers
@ -66,15 +67,9 @@ class AggregateViewSet(CustomFieldModelViewSet):
# #
class RoleViewSet(ModelViewSet): 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( queryset = Role.objects.annotate(
prefix_count=Subquery(prefix_count), prefix_count=get_subquery(Prefix, 'role'),
vlan_count=Subquery(vlan_count) vlan_count=get_subquery(VLAN, 'role')
) )
serializer_class = serializers.RoleSerializer serializer_class = serializers.RoleSerializer
filterset_class = filters.RoleFilter filterset_class = filters.RoleFilter

View File

@ -4,6 +4,7 @@ import datetime
import json import json
from django.core.serializers import serialize 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 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): 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 Return a generic JSON representation of an object using Django's built-in serializer. (This is used for things like