Pārlūkot izejas kodu

Fixes: #5450 - Added Coalesce to viewsets for the _count functions

Daniel Sheppard 5 gadi atpakaļ
vecāks
revīzija
3c2b2f26b9

+ 1 - 0
docs/release-notes/version-2.10.md

@@ -4,6 +4,7 @@
 
 
 ### Bug Fixes
 ### Bug Fixes
 
 
+* [#5450](https://github.com/netbox-community/netbox/issues/5450) - API serializer foreign count fields do not have a default value
 * [#5453](https://github.com/netbox-community/netbox/issues/5453) - Correct change log representation when creating a cable
 * [#5453](https://github.com/netbox-community/netbox/issues/5453) - Correct change log representation when creating a cable
 * [#5458](https://github.com/netbox-community/netbox/issues/5458) - Creating a component template throws an exception
 * [#5458](https://github.com/netbox-community/netbox/issues/5458) - Creating a component template throws an exception
 * [#5461](https://github.com/netbox-community/netbox/issues/5461) - Rack Elevations throw reverse match exception
 * [#5461](https://github.com/netbox-community/netbox/issues/5461) - Rack Elevations throw reverse match exception

+ 3 - 2
netbox/circuits/api/views.py

@@ -1,4 +1,5 @@
 from django.db.models import Prefetch
 from django.db.models import Prefetch
+from django.db.models.functions import Coalesce
 from rest_framework.routers import APIRootView
 from rest_framework.routers import APIRootView
 
 
 from circuits import filters
 from circuits import filters
@@ -24,7 +25,7 @@ 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=get_subquery(Circuit, 'provider')
+        circuit_count=Coalesce(get_subquery(Circuit, 'provider'), 0)
     )
     )
     serializer_class = serializers.ProviderSerializer
     serializer_class = serializers.ProviderSerializer
     filterset_class = filters.ProviderFilterSet
     filterset_class = filters.ProviderFilterSet
@@ -36,7 +37,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
 
 
 class CircuitTypeViewSet(ModelViewSet):
 class CircuitTypeViewSet(ModelViewSet):
     queryset = CircuitType.objects.annotate(
     queryset = CircuitType.objects.annotate(
-        circuit_count=get_subquery(Circuit, 'type')
+        circuit_count=Coalesce(get_subquery(Circuit, 'type'), 0)
     )
     )
     serializer_class = serializers.CircuitTypeSerializer
     serializer_class = serializers.CircuitTypeSerializer
     filterset_class = filters.CircuitTypeFilterSet
     filterset_class = filters.CircuitTypeFilterSet

+ 20 - 19
netbox/dcim/api/views.py

@@ -3,6 +3,7 @@ from collections import OrderedDict
 
 
 from django.conf import settings
 from django.conf import settings
 from django.db.models import F
 from django.db.models import F
+from django.db.models.functions import Coalesce
 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
@@ -119,12 +120,12 @@ class SiteViewSet(CustomFieldModelViewSet):
     queryset = Site.objects.prefetch_related(
     queryset = Site.objects.prefetch_related(
         'region', 'tenant', 'tags'
         'region', 'tenant', 'tags'
     ).annotate(
     ).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'),
+        device_count=Coalesce(get_subquery(Device, 'site'), 0),
+        rack_count=Coalesce(get_subquery(Rack, 'site'), 0),
+        prefix_count=Coalesce(get_subquery(Prefix, 'site'), 0),
+        vlan_count=Coalesce(get_subquery(VLAN, 'site'), 0),
+        circuit_count=Coalesce(get_subquery(Circuit, 'terminations__site'), 0),
+        virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'cluster__site'), 0),
     )
     )
     serializer_class = serializers.SiteSerializer
     serializer_class = serializers.SiteSerializer
     filterset_class = filters.SiteFilterSet
     filterset_class = filters.SiteFilterSet
@@ -152,7 +153,7 @@ class RackGroupViewSet(ModelViewSet):
 
 
 class RackRoleViewSet(ModelViewSet):
 class RackRoleViewSet(ModelViewSet):
     queryset = RackRole.objects.annotate(
     queryset = RackRole.objects.annotate(
-        rack_count=get_subquery(Rack, 'role')
+        rack_count=Coalesce(get_subquery(Rack, 'role'), 0)
     )
     )
     serializer_class = serializers.RackRoleSerializer
     serializer_class = serializers.RackRoleSerializer
     filterset_class = filters.RackRoleFilterSet
     filterset_class = filters.RackRoleFilterSet
@@ -166,8 +167,8 @@ class RackViewSet(CustomFieldModelViewSet):
     queryset = Rack.objects.prefetch_related(
     queryset = Rack.objects.prefetch_related(
         'site', 'group__site', 'role', 'tenant', 'tags'
         'site', 'group__site', 'role', 'tenant', 'tags'
     ).annotate(
     ).annotate(
-        device_count=get_subquery(Device, 'rack'),
-        powerfeed_count=get_subquery(PowerFeed, 'rack')
+        device_count=Coalesce(get_subquery(Device, 'rack'), 0),
+        powerfeed_count=Coalesce(get_subquery(PowerFeed, 'rack'), 0)
     )
     )
     serializer_class = serializers.RackSerializer
     serializer_class = serializers.RackSerializer
     filterset_class = filters.RackFilterSet
     filterset_class = filters.RackFilterSet
@@ -240,9 +241,9 @@ class RackReservationViewSet(ModelViewSet):
 
 
 class ManufacturerViewSet(ModelViewSet):
 class ManufacturerViewSet(ModelViewSet):
     queryset = Manufacturer.objects.annotate(
     queryset = Manufacturer.objects.annotate(
-        devicetype_count=get_subquery(DeviceType, 'manufacturer'),
-        inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
-        platform_count=get_subquery(Platform, 'manufacturer')
+        devicetype_count=Coalesce(get_subquery(DeviceType, 'manufacturer'), 0),
+        inventoryitem_count=Coalesce(get_subquery(InventoryItem, 'manufacturer'), 0),
+        platform_count=Coalesce(get_subquery(Platform, 'manufacturer'), 0)
     )
     )
     serializer_class = serializers.ManufacturerSerializer
     serializer_class = serializers.ManufacturerSerializer
     filterset_class = filters.ManufacturerFilterSet
     filterset_class = filters.ManufacturerFilterSet
@@ -254,7 +255,7 @@ 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=get_subquery(Device, 'device_type')
+        device_count=Coalesce(get_subquery(Device, 'device_type'), 0)
     )
     )
     serializer_class = serializers.DeviceTypeSerializer
     serializer_class = serializers.DeviceTypeSerializer
     filterset_class = filters.DeviceTypeFilterSet
     filterset_class = filters.DeviceTypeFilterSet
@@ -318,8 +319,8 @@ class DeviceBayTemplateViewSet(ModelViewSet):
 
 
 class DeviceRoleViewSet(ModelViewSet):
 class DeviceRoleViewSet(ModelViewSet):
     queryset = DeviceRole.objects.annotate(
     queryset = DeviceRole.objects.annotate(
-        device_count=get_subquery(Device, 'device_role'),
-        virtualmachine_count=get_subquery(VirtualMachine, 'role')
+        device_count=Coalesce(get_subquery(Device, 'device_role'), 0),
+        virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'role'), 0)
     )
     )
     serializer_class = serializers.DeviceRoleSerializer
     serializer_class = serializers.DeviceRoleSerializer
     filterset_class = filters.DeviceRoleFilterSet
     filterset_class = filters.DeviceRoleFilterSet
@@ -331,8 +332,8 @@ class DeviceRoleViewSet(ModelViewSet):
 
 
 class PlatformViewSet(ModelViewSet):
 class PlatformViewSet(ModelViewSet):
     queryset = Platform.objects.annotate(
     queryset = Platform.objects.annotate(
-        device_count=get_subquery(Device, 'platform'),
-        virtualmachine_count=get_subquery(VirtualMachine, 'platform')
+        device_count=Coalesce(get_subquery(Device, 'platform'), 0),
+        virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'platform'), 0)
     )
     )
     serializer_class = serializers.PlatformSerializer
     serializer_class = serializers.PlatformSerializer
     filterset_class = filters.PlatformFilterSet
     filterset_class = filters.PlatformFilterSet
@@ -596,7 +597,7 @@ 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=get_subquery(Device, 'virtual_chassis')
+        member_count=Coalesce(get_subquery(Device, 'virtual_chassis'), 0)
     )
     )
     serializer_class = serializers.VirtualChassisSerializer
     serializer_class = serializers.VirtualChassisSerializer
     filterset_class = filters.VirtualChassisFilterSet
     filterset_class = filters.VirtualChassisFilterSet
@@ -610,7 +611,7 @@ class PowerPanelViewSet(ModelViewSet):
     queryset = PowerPanel.objects.prefetch_related(
     queryset = PowerPanel.objects.prefetch_related(
         'site', 'rack_group'
         'site', 'rack_group'
     ).annotate(
     ).annotate(
-        powerfeed_count=get_subquery(PowerFeed, 'power_panel')
+        powerfeed_count=Coalesce(get_subquery(PowerFeed, 'power_panel'), 0)
     )
     )
     serializer_class = serializers.PowerPanelSerializer
     serializer_class = serializers.PowerPanelSerializer
     filterset_class = filters.PowerPanelFilterSet
     filterset_class = filters.PowerPanelFilterSet

+ 2 - 1
netbox/extras/api/views.py

@@ -1,4 +1,5 @@
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
+from django.db.models.functions import Coalesce
 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
@@ -102,7 +103,7 @@ class ExportTemplateViewSet(ModelViewSet):
 
 
 class TagViewSet(ModelViewSet):
 class TagViewSet(ModelViewSet):
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
-        tagged_items=get_subquery(TaggedItem, 'tag')
+        tagged_items=Coalesce(get_subquery(TaggedItem, 'tag'), 0)
     )
     )
     serializer_class = serializers.TagSerializer
     serializer_class = serializers.TagSerializer
     filterset_class = filters.TagFilterSet
     filterset_class = filters.TagFilterSet

+ 8 - 7
netbox/ipam/api/views.py

@@ -1,4 +1,5 @@
 from django.conf import settings
 from django.conf import settings
+from django.db.models.functions import Coalesce
 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
@@ -32,8 +33,8 @@ class VRFViewSet(CustomFieldModelViewSet):
     queryset = VRF.objects.prefetch_related('tenant').prefetch_related(
     queryset = VRF.objects.prefetch_related('tenant').prefetch_related(
         'import_targets', 'export_targets', 'tags'
         'import_targets', 'export_targets', 'tags'
     ).annotate(
     ).annotate(
-        ipaddress_count=get_subquery(IPAddress, 'vrf'),
-        prefix_count=get_subquery(Prefix, 'vrf')
+        ipaddress_count=Coalesce(get_subquery(IPAddress, 'vrf'), 0),
+        prefix_count=Coalesce(get_subquery(Prefix, 'vrf'), 0)
     )
     )
     serializer_class = serializers.VRFSerializer
     serializer_class = serializers.VRFSerializer
     filterset_class = filters.VRFFilterSet
     filterset_class = filters.VRFFilterSet
@@ -55,7 +56,7 @@ class RouteTargetViewSet(CustomFieldModelViewSet):
 
 
 class RIRViewSet(ModelViewSet):
 class RIRViewSet(ModelViewSet):
     queryset = RIR.objects.annotate(
     queryset = RIR.objects.annotate(
-        aggregate_count=get_subquery(Aggregate, 'rir')
+        aggregate_count=Coalesce(get_subquery(Aggregate, 'rir'), 0)
     )
     )
     serializer_class = serializers.RIRSerializer
     serializer_class = serializers.RIRSerializer
     filterset_class = filters.RIRFilterSet
     filterset_class = filters.RIRFilterSet
@@ -77,8 +78,8 @@ class AggregateViewSet(CustomFieldModelViewSet):
 
 
 class RoleViewSet(ModelViewSet):
 class RoleViewSet(ModelViewSet):
     queryset = Role.objects.annotate(
     queryset = Role.objects.annotate(
-        prefix_count=get_subquery(Prefix, 'role'),
-        vlan_count=get_subquery(VLAN, 'role')
+        prefix_count=Coalesce(get_subquery(Prefix, 'role'), 0),
+        vlan_count=Coalesce(get_subquery(VLAN, 'role'), 0)
     )
     )
     serializer_class = serializers.RoleSerializer
     serializer_class = serializers.RoleSerializer
     filterset_class = filters.RoleFilterSet
     filterset_class = filters.RoleFilterSet
@@ -272,7 +273,7 @@ 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=get_subquery(VLAN, 'group')
+        vlan_count=Coalesce(get_subquery(VLAN, 'group'), 0)
     )
     )
     serializer_class = serializers.VLANGroupSerializer
     serializer_class = serializers.VLANGroupSerializer
     filterset_class = filters.VLANGroupFilterSet
     filterset_class = filters.VLANGroupFilterSet
@@ -286,7 +287,7 @@ class VLANViewSet(CustomFieldModelViewSet):
     queryset = VLAN.objects.prefetch_related(
     queryset = VLAN.objects.prefetch_related(
         'site', 'group', 'tenant', 'role', 'tags'
         'site', 'group', 'tenant', 'role', 'tags'
     ).annotate(
     ).annotate(
-        prefix_count=get_subquery(Prefix, 'vlan')
+        prefix_count=Coalesce(get_subquery(Prefix, 'vlan'), 0)
     )
     )
     serializer_class = serializers.VLANSerializer
     serializer_class = serializers.VLANSerializer
     filterset_class = filters.VLANFilterSet
     filterset_class = filters.VLANFilterSet

+ 2 - 1
netbox/secrets/api/views.py

@@ -1,6 +1,7 @@
 import base64
 import base64
 
 
 from Crypto.PublicKey import RSA
 from Crypto.PublicKey import RSA
+from django.db.models.functions import Coalesce
 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
@@ -35,7 +36,7 @@ class SecretsRootView(APIRootView):
 
 
 class SecretRoleViewSet(ModelViewSet):
 class SecretRoleViewSet(ModelViewSet):
     queryset = SecretRole.objects.annotate(
     queryset = SecretRole.objects.annotate(
-        secret_count=get_subquery(Secret, 'role')
+        secret_count=Coalesce(get_subquery(Secret, 'role'), 0)
     )
     )
     serializer_class = serializers.SecretRoleSerializer
     serializer_class = serializers.SecretRoleSerializer
     filterset_class = filters.SecretRoleFilterSet
     filterset_class = filters.SecretRoleFilterSet

+ 8 - 7
netbox/tenancy/api/views.py

@@ -1,3 +1,4 @@
+from django.db.models.functions import Coalesce
 from rest_framework.routers import APIRootView
 from rest_framework.routers import APIRootView
 
 
 from circuits.models import Circuit
 from circuits.models import Circuit
@@ -46,13 +47,13 @@ class TenantViewSet(CustomFieldModelViewSet):
     ).annotate(
     ).annotate(
         circuit_count=get_subquery(Circuit, 'tenant'),
         circuit_count=get_subquery(Circuit, 'tenant'),
         device_count=get_subquery(Device, 'tenant'),
         device_count=get_subquery(Device, 'tenant'),
-        ipaddress_count=get_subquery(IPAddress, 'tenant'),
-        prefix_count=get_subquery(Prefix, 'tenant'),
-        rack_count=get_subquery(Rack, 'tenant'),
-        site_count=get_subquery(Site, 'tenant'),
-        virtualmachine_count=get_subquery(VirtualMachine, 'tenant'),
-        vlan_count=get_subquery(VLAN, 'tenant'),
-        vrf_count=get_subquery(VRF, 'tenant')
+        ipaddress_count=Coalesce(get_subquery(IPAddress, 'tenant'), 0),
+        prefix_count=Coalesce(get_subquery(Prefix, 'tenant'), 0),
+        rack_count=Coalesce(get_subquery(Rack, 'tenant'), 0),
+        site_count=Coalesce(get_subquery(Site, 'tenant'), 0),
+        virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'tenant'), 0),
+        vlan_count=Coalesce(get_subquery(VLAN, 'tenant'), 0),
+        vrf_count=Coalesce(get_subquery(VRF, 'tenant'), 0)
     )
     )
     serializer_class = serializers.TenantSerializer
     serializer_class = serializers.TenantSerializer
     filterset_class = filters.TenantFilterSet
     filterset_class = filters.TenantFilterSet

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

@@ -1,3 +1,4 @@
+from django.db.models.functions import Coalesce
 from rest_framework.routers import APIRootView
 from rest_framework.routers import APIRootView
 
 
 from dcim.models import Device
 from dcim.models import Device
@@ -22,7 +23,7 @@ class VirtualizationRootView(APIRootView):
 
 
 class ClusterTypeViewSet(ModelViewSet):
 class ClusterTypeViewSet(ModelViewSet):
     queryset = ClusterType.objects.annotate(
     queryset = ClusterType.objects.annotate(
-        cluster_count=get_subquery(Cluster, 'type')
+        cluster_count=Coalesce(get_subquery(Cluster, 'type'), 0)
     )
     )
     serializer_class = serializers.ClusterTypeSerializer
     serializer_class = serializers.ClusterTypeSerializer
     filterset_class = filters.ClusterTypeFilterSet
     filterset_class = filters.ClusterTypeFilterSet
@@ -30,7 +31,7 @@ class ClusterTypeViewSet(ModelViewSet):
 
 
 class ClusterGroupViewSet(ModelViewSet):
 class ClusterGroupViewSet(ModelViewSet):
     queryset = ClusterGroup.objects.annotate(
     queryset = ClusterGroup.objects.annotate(
-        cluster_count=get_subquery(Cluster, 'group')
+        cluster_count=Coalesce(get_subquery(Cluster, 'group'), 0)
     )
     )
     serializer_class = serializers.ClusterGroupSerializer
     serializer_class = serializers.ClusterGroupSerializer
     filterset_class = filters.ClusterGroupFilterSet
     filterset_class = filters.ClusterGroupFilterSet
@@ -40,8 +41,8 @@ class ClusterViewSet(CustomFieldModelViewSet):
     queryset = Cluster.objects.prefetch_related(
     queryset = Cluster.objects.prefetch_related(
         'type', 'group', 'tenant', 'site', 'tags'
         'type', 'group', 'tenant', 'site', 'tags'
     ).annotate(
     ).annotate(
-        device_count=get_subquery(Device, 'cluster'),
-        virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
+        device_count=Coalesce(get_subquery(Device, 'cluster'), 0),
+        virtualmachine_count=Coalesce(get_subquery(VirtualMachine, 'cluster'), 0)
     )
     )
     serializer_class = serializers.ClusterSerializer
     serializer_class = serializers.ClusterSerializer
     filterset_class = filters.ClusterFilterSet
     filterset_class = filters.ClusterFilterSet